Rush StackShopBlogEvents
Skip to main content

Rush 子空间

什么是子空间?

子空间是 Rush 的一个功能,使单一的 monorepo 能够使用多个 PNPM 锁定文件进行安装。例如,如果子空间名称是 my-team,则会有一个文件夹 common/config/subspaces/my-team/,其中包含 pnpm-lock.yaml 文件和相关配置。每个 Rush 项目都只属于一个子空间,monorepo 仍然保持一个统一的 "工作区"。因此,一个项目的 package.json 文件可以使用 workspace: 来指定对其他子空间项目的依赖。

有什么好处?

通常情况下,整个 monorepo 使用单个锁定文件是最好的,因为这可以优化安装时间,并最大限度地减少管理版本冲突的维护工作。然而,在某些情况下,允许多个锁定文件有其优势:

  • 非常庞大的代码库:锁定文件可以被视为一个庞大的多变量方程,我们通过在许多项目中协调 NPM 包版本选择来消除冲突并尽量减少重复。(锁定文件浏览器 文档对此有详细说明。)将 monorepo 的依赖关系分成较小的锁定文件确实使这些方程更小、更容易解决,但增加了管理版本的整体开销。对于庞大的工程团队来说,分工比减少工作总量更重要。

  • 解耦的项目集合:一个庞大的代码库中可能有一些项目集,它们的依赖关系与代码库的其他部分不一致。例如,假设有 50 个项目构成一个使用已弃用或过时框架的遗留应用程序,没有业务动机去现代化。将这些项目移入一个子空间可以使其版本管理独立。

  • 安装测试:在发布 NPM 包时,使用 workspace:* 符号链接无法重现某些错误。例如,幽灵依赖或错误的 .npmignore 通配符会导致外部消费者的包失败,但在 monorepo 中测试同一库时可能工作正常。将测试项目移入子空间(结合注入依赖)会产生更准确的安装,从而发现此类问题,同时避免实际发布到测试 NPM 注册表的开销。

我需要多少个子空间?

我们通常建议 "尽可能少" 以尽量减少额外的版本管理开销。每个团队一个子空间 是一个合理的最大上限。尽管如此,在一个包含超过 1000 个子空间的生产环境的 monorepo 中,该功能已被成功使用。

真实世界示例

Rush Stack 在 GitHub 上的自有仓库目前配置了两个子空间:

功能设计

每个子空间必须在 common/config/subspaces.json 配置文件中进行集中注册。项目通过 rush.json 中的 subspaceName 字段添加到子空间。

每个子空间的配置位于文件夹 common/config/subspaces/<subspace-name>/ 中,可能包含以下文件:

文件作用
common-versions.jsonRush 版本覆盖
pnpm-config.jsonPNPM 版本覆盖
pnpm-lock.yamlPNPM 锁定文件
repo-state.jsonRush 生成的配置文件,用于防止手动更改锁定文件
.npmrc包管理器配置
.pnpmfile-subspace.cjs程序化版本覆盖,与 .pnpmfile.cjs 规范一致,但特定于该子空间

有些文件可以全局配置(适用于整个 monorepo),同时也可以在子空间级别配置:

子空间配置文件全局配置文件继承关系
common-versions.json启用子空间时禁止使用全局文件
pnpm-config.jsoncommon/config/rush/pnpm-config.json(仍在开发中) 子空间优先,但某些字段被忽略
pnpm-lock.yaml启用子空间时禁止使用全局文件
repo-state.json启用子空间时禁止使用全局文件
.npmrccommon/config/rush/.npmrc子空间覆盖优先
.pnpmfile-subspace.cjscommon/config/rush/.pnpmfile.cjs子空间覆盖优先

请注意以下配置文件不会移动:

  • common/config/.npmrc-publish: 该文件用于 Rush NPM 发布,无论发布的项目属于哪个子空间。
  • common/config/.pnpmfile.cjs: 该文件可应用影响 monorepo 中所有子空间的版本覆盖。为了避免跨锁定文件的混乱交互,大多数情况下最好使用 .pnpmfile-subspace.cjs

没有子空间时,Rush 会在 common/temp/ 文件夹中生成并安装 PNPM 工作区。启用子空间后,将在类似 common/temp/<subspace-name>/ 的文件夹中分别执行。

有两种基本操作模式:

  1. 只有几个子空间: 你可以在 subspaces.json 中设置 "preventSelectingAllSubspaces": false,并且默认情况下,rush install 将安装所有子空间。

  2. 大量子空间: 如果安装所有子空间会消耗过多的时间和磁盘空间,那么你可以设置 "preventSelectingAllSubspaces": true。在此模式下,调用 rush installrush update 等命令时,用户必须以某种方式过滤子空间,例如:

    • 使用 rush install --to my-project 只安装指定项目的依赖
    • 使用 rush install --subspace my-subspace 只安装特定子空间
    • 使用 项目选择器 中的 rush install --to subspace:my-subspace 为属于某个子空间的项目安装

如何启用子空间

  1. 确保你的 rush.json 文件中指定了 "rushVersion": "5.122.0" 或更新版本,"pnpmVersion": "8.7.6" 或更新版本。

  2. 使用 subspaces.json 启用此功能并定义子空间。你可以从 subspaces.json 文档中复制此文件的模板,或者使用 rush init 生成它。在本教程中,我们将创建一个名为 install-test 的子空间,用于测试 NPM 包:

    common/config/rush/subspaces.json

    {
    "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/subspaces.schema.json",

    /**
    * 设置此标志为 "true" 以启用子空间。
    */
    "subspacesEnabled": false,

    /**
    * 当执行类似 "rush update" 的命令且没有使用 "--subspace" 或 "--to" 参数时,Rush 会安装所有子空间。
    * 在拥有大量子空间的庞大 monorepo 中,这样做会非常缓慢。
    * 通过始终要求选择参数来执行类似 "rush update" 之类的命令,可以设置 "preventSelectingAllSubspaces" 为 true 以避免此类错误。
    */
    "preventSelectingAllSubspaces": false,

    /**
    * 子空间名称列表,应为小写的字母数字单词并用连字符分隔,例如 "my-subspace"。
    * 对应的配置文件路径可能为 "common/config/subspaces/my-subspace/package-lock.yaml"。
    */
    "subspaceNames": [
    // "default" 子空间即使你没有定义它也总是存在,但为了清晰起见,让我们将其包含在内
    "default",

    "install-test" // 👈👈👈 我们的第二个子空间名称
    ]
    }
  3. 创建 default 子空间文件夹并将现有配置文件移动到那里:

    cd my-repo
    mkdir --parents common/config/subspaces/default

    # 移动这些文件:
    mv common/config/rush/common-versions.json common/config/subspaces/default/
    mv common/config/rush/pnpm-lock.yaml common/config/subspaces/default/
    mv common/config/rush/.npmrc common/config/subspaces/default/

    # 重命名此文件:
    mv common/config/rush/.pnpmfile.cjs common/config/subspaces/default/.pnpmfile-subspace.cjs
  4. 创建 install-test 子空间文件夹:

    cd my-repo
    mkdir --parents common/config/subspaces/install-test
  5. 通过编辑 rush.json 将项目分配到子空间。例如:

    rush.json

    . . .

    "projects": [
    {
    "packageName": "my-library-test",
    "projectFolder": "test-projects/my-library-test",
    "subspaceName": "install-test"
    }

    . . .

    如果任何项目省略了 "subspaceName",它们将属于 default 子空间。

  6. 更新新子空间的锁定文件:

    # 清理之前的 common/temp 文件夹
    rush purge

    # 重新生成 "default" 子空间:
    rush update --full --subspace default

    # 重新生成 "install-test" 子空间:
    rush update --full --subspace install-test

    注意: 你可以在不使用 --full 重新生成任何锁定文件的情况下迁移到子空间, 但这是一个更复杂的过程,可能需要使用脚本重写 pnpm-lock.yaml 文件中的某些路径。

另见