Monorepo的实践:使用`Lerna`或`Nx`管理多个项目,实现代码共享和依赖管理。

Monorepo实践:Lerna与Nx的深度解析与应用

各位同学,大家好!今天我们来深入探讨Monorepo这种代码管理模式,以及如何利用Lerna和Nx这两个强大的工具来实践Monorepo。

什么是Monorepo?为什么要使用它?

Monorepo,顾名思义,就是将多个项目(可以是库、应用、工具等等)的代码放在同一个版本控制仓库中。这与传统的Multi-repo(每个项目一个仓库)模式形成对比。

为什么要选择Monorepo呢?它带来了许多好处:

  • 代码复用更容易: 各个项目可以方便地共享代码,减少重复代码的编写,提高开发效率。
  • 依赖管理更清晰: 统一管理所有项目的依赖,避免版本冲突,方便升级和维护。
  • 原子性变更: 跨多个项目的变更可以作为一个原子操作提交,保证一致性。例如,修改一个公共组件,同时更新所有使用它的应用,可以一次性提交。
  • 简化构建和测试: 可以使用统一的构建和测试流程,提高效率。
  • 团队协作更高效: 所有团队成员都可以访问所有代码,促进知识共享和协作。

当然,Monorepo也存在一些挑战:

  • 仓库体积大: 可能会导致仓库体积增大,clone和checkout操作变慢。
  • 权限管理复杂: 需要更精细的权限管理,避免不必要的访问。
  • 构建时间长: 构建所有项目可能会花费较长时间。

不过,通过合理的工具和实践,我们可以有效地克服这些挑战。Lerna和Nx就是为此而生的。

Lerna:专注于包管理和版本发布的工具

Lerna是一个用于管理JavaScript项目的Monorepo的工具。它主要关注的是包管理和版本发布。

Lerna的核心功能:

  • 版本管理: 自动检测哪些包发生了变更,并根据变更类型自动升级版本号。
  • 包发布: 将更新后的包发布到npm等包管理平台。
  • 依赖管理: 简化包之间的依赖管理。
  • 任务执行: 在多个包中并行执行命令。

Lerna的使用示例:

  1. 初始化Lerna:

    npx lerna init

    这会在你的项目中创建一个lerna.json配置文件,以及一个packages目录,用于存放各个子项目。

  2. 创建子项目:

    假设我们要创建两个子项目:package-apackage-b

    mkdir packages/package-a
    mkdir packages/package-b
    cd packages/package-a
    npm init -y
    cd ../package-b
    npm init -y
    cd ..
  3. 配置lerna.json:

    {
      "packages": [
        "packages/*"
      ],
      "version": "independent",
      "npmClient": "npm",
      "useWorkspaces": true
    }
    • packages: 指定了Lerna管理的包的路径。packages/*表示packages目录下所有的子目录都是一个包。
    • version: 指定了版本管理模式。independent表示每个包独立管理版本。还可以设置为fixed,表示所有包使用相同的版本号。
    • npmClient: 指定使用的npm客户端。
    • useWorkspaces: 使用npm workspaces来管理依赖,需要npm 7+。
  4. 配置package.json (根目录):

    {
      "name": "my-monorepo",
      "private": true,
      "workspaces": [
        "packages/*"
      ],
      "scripts": {
        "bootstrap": "lerna bootstrap",
        "publish": "lerna publish",
        "test": "lerna run test"
      },
      "devDependencies": {
        "lerna": "^6.0.0"
      }
    }
    • private: true:防止根目录被意外发布到npm。
    • workspaces: 指定npm workspaces管理的包的路径。
    • scripts: 定义一些常用的脚本,例如bootstrap用于安装依赖,publish用于发布包,test用于运行测试。
  5. 安装依赖:

    npm install

    这会安装所有子项目的依赖。

  6. 在子项目之间建立依赖关系:

    假设package-b依赖于package-a。在packages/package-b/package.json中添加:

    {
      "dependencies": {
        "package-a": "1.0.0" // 或者 "package-a": "*"
      }
    }

    然后再次运行npm install,Lerna会自动将package-a链接到package-bnode_modules目录中。

  7. 运行命令:

    npm run test

    这会在所有子项目中运行test命令。

  8. 发布包:

    npm run publish

    Lerna会自动检测哪些包发生了变更,并提示你输入新的版本号。然后,它会将更新后的包发布到npm。

Lerna的局限性:

Lerna主要关注的是包管理和版本发布,它对代码共享和依赖管理的支持相对简单。对于大型Monorepo,或者需要更复杂的构建和测试流程的项目,Lerna可能不够强大。

Nx:更全面的Monorepo工具

Nx是一个更全面的Monorepo工具,它提供了更强大的代码共享、依赖管理、构建和测试功能。

Nx的核心功能:

  • 依赖图: Nx会分析你的代码,构建一个依赖图,用于确定哪些项目受到了代码变更的影响。
  • 增量构建: Nx会根据依赖图,只构建受到代码变更影响的项目,大大提高了构建速度。
  • 增量测试: Nx会根据依赖图,只测试受到代码变更影响的项目,大大提高了测试速度。
  • 代码生成: Nx提供了强大的代码生成功能,可以快速创建新的项目、组件、服务等等。
  • 代码共享: Nx支持多种代码共享方式,例如库、工具函数、UI组件等等。
  • 插件系统: Nx拥有丰富的插件系统,可以支持各种流行的前端和后端技术。

Nx的使用示例:

  1. 创建Nx工作区:

    npx create-nx-workspace@latest my-nx-monorepo --preset=integrated

    这会创建一个新的Nx工作区,并选择integrated预设。integrated预设会将所有项目放在同一个nx.json文件中管理。

  2. 创建应用和库:

    nx generate @nx/react:application my-app
    nx generate @nx/react:library my-lib

    这会创建两个项目:一个React应用my-app和一个React库my-lib

  3. 配置nx.json:

    {
      "npmScope": "my-nx-monorepo",
      "affected": {
        "defaultBase": "main"
      },
      "implicitDependencies": {
        "package.json": {
          "dependencies": "*",
          "devDependencies": "*"
        },
        ".eslintrc.json": "*"
      },
      "tasksRunnerOptions": {
        "default": {
          "runner": "@nx/nx-cloud",
          "options": {
            "cacheableOperations": [
              "build",
              "lint",
              "test",
              "e2e"
            ]
          }
        }
      },
      "targetDefaults": {
        "build": {
          "dependsOn": [
            "^build"
          ],
          "inputs": [
            "default",
            "{projectRoot}/src/**/*",
            "{projectRoot}/tsconfig.json",
            "{projectRoot}/.babelrc",
            "{projectRoot}/webpack.config.js",
            "{projectRoot}/jest.config.ts"
          ]
        }
      },
      "namedInputs": {
        "default": [
          "default"
        ],
        "production": [
          "default",
          "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
          "!{projectRoot}/tsconfig.spec.json",
          "!{projectRoot}/jest.config.ts"
        ],
         "sharedGlobals": []
      },
      "generators": {
        "@nx/react": {
          "library": {
            "style": "css",
            "linter": "eslint"
          }
        }
      },
      "defaultProject": "my-app"
    }
    • npmScope: 指定npm scope。
    • affected: 配置受影响的项目检测。
    • implicitDependencies: 配置隐式依赖,例如package.json.eslintrc.json的修改会影响所有项目。
    • tasksRunnerOptions: 配置任务运行器,可以使用Nx Cloud来缓存构建结果。
    • targetDefaults: 配置默认的目标,例如build
    • namedInputs: 定义命名的输入,用于缓存和受影响的项目检测。
    • generators: 配置生成器的默认选项。
    • defaultProject: 配置默认项目。
  4. 在应用和库之间建立依赖关系:

    假设my-app依赖于my-lib。在apps/my-app/src/app/app.tsx中导入my-lib的组件:

    import { MyLibComponent } from '@my-nx-monorepo/my-lib';
    
    function App() {
      return (
        <div>
          <h1>Welcome to my-app!</h1>
          <MyLibComponent />
        </div>
      );
    }
    
    export default App;
  5. 构建和测试:

    nx build my-app
    nx test my-lib

    Nx只会构建和测试受代码变更影响的项目。

  6. 使用Nx Console:

    Nx提供了一个强大的IDE插件Nx Console,可以方便地执行Nx命令、查看依赖图、生成代码等等。

Nx的优点:

  • 更强大的依赖管理: Nx可以分析代码,构建依赖图,只构建和测试受代码变更影响的项目。
  • 更高效的构建和测试: Nx可以利用缓存和并行执行,大大提高构建和测试速度。
  • 更丰富的代码生成功能: Nx提供了大量的代码生成器,可以快速创建各种类型的项目和组件。
  • 更友好的开发者体验: Nx Console提供了强大的IDE支持,方便开发者使用Nx。

Nx的缺点:

  • 学习曲线较陡峭: Nx的功能比较多,学习曲线相对较陡峭。
  • 配置较为复杂: Nx的配置比较复杂,需要花费一些时间来理解。

Lerna vs Nx: 如何选择?

特性 Lerna Nx
核心关注点 包管理和版本发布 代码共享、依赖管理、构建和测试
依赖管理 简单,基于npm workspaces 强大,可以分析代码,构建依赖图
构建和测试 简单,需要在每个包中定义脚本 强大,支持增量构建和测试,利用缓存和并行执行
代码生成 强大,提供了大量的代码生成器
开发者体验 简单易用 功能强大,但学习曲线较陡峭
适用场景 小型Monorepo,主要关注包管理和版本发布 大型Monorepo,需要更强大的代码共享、依赖管理、构建和测试功能
社区和生态系统 成熟,但相对Nx较小 活跃,拥有庞大的社区和丰富的插件

总结:

  • 如果你的Monorepo规模较小,主要关注包管理和版本发布,Lerna是一个不错的选择。
  • 如果你的Monorepo规模较大,需要更强大的代码共享、依赖管理、构建和测试功能,Nx是更好的选择。
  • 你也可以将Lerna和Nx结合使用。例如,使用Nx来管理代码共享、依赖管理、构建和测试,使用Lerna来管理包发布。

Monorepo的最佳实践

除了选择合适的工具,还有一些Monorepo的最佳实践可以帮助你更好地管理你的代码:

  • 定义清晰的模块边界: 确保每个模块都有清晰的职责和边界,避免模块之间的过度耦合。
  • 使用代码审查: 强制执行代码审查,确保代码质量。
  • 自动化构建和测试: 使用CI/CD工具自动化构建和测试流程,确保代码的稳定性和可靠性。
  • 监控和度量: 监控Monorepo的性能和健康状况,及时发现和解决问题。
  • 持续学习和改进: Monorepo是一个不断发展的领域,要持续学习和改进,不断优化你的Monorepo实践。

案例分析:一个实际的Monorepo项目结构

假设我们有一个Monorepo项目,用于构建一个在线商城。该项目包含以下模块:

  • apps/web: Web应用,用户访问的入口。
  • apps/admin: 管理后台应用,用于管理商品、订单等。
  • libs/ui: UI组件库,包含各种可复用的UI组件。
  • libs/api: API客户端库,用于与后端API进行交互。
  • libs/utils: 工具函数库,包含各种通用的工具函数。

使用Nx,我们可以这样组织项目结构:

my-store/
├── apps/
│   ├── web/
│   │   ├── src/
│   │   │   ├── app/
│   │   │   │   ├── app.tsx
│   │   │   │   └── ...
│   │   │   └── ...
│   │   ├── project.json
│   │   ├── tsconfig.json
│   │   └── ...
│   └── admin/
│       ├── src/
│       │   ├── app/
│       │   │   ├── app.tsx
│       │   │   └── ...
│       │   └── ...
│       ├── project.json
│       ├── tsconfig.json
│       └── ...
├── libs/
│   ├── ui/
│   │   ├── src/
│   │   │   ├── lib/
│   │   │   │   ├── button/
│   │   │   │   │   ├── button.tsx
│   │   │   │   │   └── ...
│   │   │   │   └── ...
│   │   │   └── index.ts
│   │   ├── project.json
│   │   ├── tsconfig.json
│   │   └── ...
│   ├── api/
│   │   ├── src/
│   │   │   ├── lib/
│   │   │   │   ├── products.ts
│   │   │   │   └── ...
│   │   │   └── index.ts
│   │   ├── project.json
│   │   ├── tsconfig.json
│   │   └── ...
│   └── utils/
│       ├── src/
│       │   ├── lib/
│       │   │   ├── format-currency.ts
│       │   │   └── ...
│       │   └── index.ts
│       ├── project.json
│       ├── tsconfig.json
│       └── ...
├── nx.json
├── package.json
├── tsconfig.base.json
└── ...
  • 每个应用和库都有自己的src目录,用于存放源代码。
  • 每个应用和库都有自己的project.json文件,用于配置Nx的构建、测试等任务。
  • nx.json文件用于配置Nx的工作区。
  • package.json文件用于管理项目依赖。
  • tsconfig.base.json文件用于配置TypeScript的编译选项。

通过这种结构,我们可以清晰地组织代码,方便代码共享和依赖管理。

Monorepo的优势与挑战并存

Monorepo是一种强大的代码管理模式,可以提高代码复用率、简化依赖管理、促进团队协作。Lerna和Nx是两种常用的Monorepo工具,可以帮助你更好地实践Monorepo。选择合适的工具,并遵循最佳实践,可以帮助你克服Monorepo的挑战,充分发挥其优势。

最后的一点建议

在选择Monorepo之前,请仔细评估你的项目需求和团队情况。Monorepo并非银弹,它只适用于特定的场景。如果你的项目规模较小,或者团队成员较少,Multi-repo可能更适合你。只有当你确定Monorepo能够解决你的问题,并为你带来实际的价值时,才应该选择它。同时,保持学习和实践,不断优化你的Monorepo实践,才能真正发挥Monorepo的优势。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注