前置技能 npm 工具的简单了解

Monorepo 是一种软件开发策略,它将多个项目、模块、包或应用程序的代码存储在同一个版本控制仓库(Repository)中。这与传统的 Multirepo(多仓库)模式形成对比,后者是为每个项目或模块创建一个独立的仓库。

  • Monorepo (单一仓库): 所有代码都在一个大仓库里,比如 my-company-monorepo/,里面可能包含 packages/frontend/, packages/backend/, packages/shared-utils/, apps/mobile-app/ 等。
  • Multirepo (多仓库): 每个项目或模块都有自己的独立仓库,比如 frontend-repo/, backend-repo/, shared-utils-repo/, mobile-app-repo/。

快速启动学习

Monorepo 的核心思想是将多个项目放在一个仓库里,但要让这种模式高效、可管理,必须依赖一系列具体的工具和技术栈来解决随之而来的挑战(如依赖管理、构建、测试、发布、代码组织等)。

以下是 Monorepo 开发模式中关键的具体实现技术和工具,按功能领域划分:


1. 包管理与依赖解析 (Package Management & Dependency Resolution)

这是 Monorepo 的基础,用于管理内部包(packages)之间的依赖关系和外部依赖。

  • npm/yarn/pnpm Workspaces:

    • 作用: 这是最底层的基础设施。它们允许在单个根 package.json 中定义多个子包(位于 packages/libs/ 目录下),并自动解析和链接这些内部包之间的依赖。
    • 原理: 使用符号链接 (symlinks) 将内部包链接到 node_modules,使得 importrequire 可以像引用外部包一样引用内部包(例如 import { util } from '@myorg/shared-utils')。
    • 特点: 轻量级,是其他高级工具的基础。pnpm 因其高效的硬链接和内容寻址存储,在大型 Monorepo 中尤其受欢迎,能显著节省磁盘空间和安装时间。
  • Lerna:

    • 作用: 一个较早的、专门为 JavaScript/TypeScript Monorepo 设计的工具。它建立在 npm/yarn workspaces 之上,主要提供:
      • Bootstrapping: 安装所有包的依赖并链接内部包。
      • 版本管理: 协助管理包的版本号(支持固定/锁定版本或独立版本)。
      • 发布: 自动化发布包到 npm registry。
      • 运行脚本: 在多个包上并行运行 npm scripts (如 lerna run build)。
    • 特点: 功能全面,但本身不提供增量构建,性能优化依赖于其他工具。

2. 构建、测试与任务调度 (Build, Test & Task Orchestration)

这是解决 Monorepo 性能瓶颈(全量构建/测试太慢)的核心。关键在于增量构建影响分析

  • Turborepo:

    • 作用: 由 Vercel 开发,目前非常流行的高性能构建系统。它深度集成 npm/yarn/pnpm workspaces。
    • 核心技术:
      • 智能缓存 (Smart Caching): 将任务(如 build, test, lint)的输出缓存到本地或云端。如果输入(源码、依赖、配置)未变,直接复用缓存结果,避免重复执行。
      • 增量构建 (Incremental Builds): 结合缓存,只重新构建真正发生变化的包及其下游依赖。
      • 并行执行: 自动并行运行独立的任务。
      • 任务图 (Task Graph): 分析任务之间的依赖关系(如 app 依赖 libbuild 依赖 lint)。
      • 远程缓存: 团队成员共享缓存,极大加速 CI/CD 和新开发者环境搭建。
    • 特点: 配置简单(通常只需 turbo.json),性能提升显著,尤其适合 React、Next.js 等生态。
  • Nx:

    • 作用: 一个功能极其强大的 Monorepo 平台,由 Nrwl 开发。它不仅是一个构建工具,更是一个完整的开发框架。
    • 核心技术:
      • 计算任务图 (Computationally Generated Task Graph): Nx 会分析代码中的 import 语句来构建精确的项目依赖图
      • 影响分析 (Affected Commands): nx affected:build 等命令能精确计算出当前变更(基于 Git)影响了哪些项目,只执行这些项目的任务。
      • 分布式任务执行 (DTE): 将任务分发到多台机器或云端执行。
      • 远程缓存: 类似 Turborepo。
      • 代码生成器 (Generators): 提供大量预设的代码生成器(nx generate),用于快速创建应用、库、组件等,保证结构一致性。
      • 插件生态: 支持 Angular, React, Node.js, NestJS, Web Components, 甚至 .NET, Python 等。
    • 特点: 功能全面,学习曲线稍陡,但提供了从代码生成到部署的完整解决方案,适合大型复杂项目。
  • Bazel:

    • 作用: Google 开发的通用、可扩展的构建和测试工具,是 Google 内部超大 Monorepo 的基石。
    • 核心技术:
      • 声明式构建: 使用 BUILD 文件(基于 Starlark 语言)明确声明每个构建目标的输入、输出、依赖和构建规则。
      • 确定性构建: 保证相同输入产生相同输出。
      • 强大的缓存和远程执行: 支持本地和远程缓存/执行,效率极高。
      • 多语言支持: 原生支持 C++, Java, Python, JavaScript, Go 等。
    • 特点: 性能顶尖,可扩展性强,但配置复杂,学习成本高。适合对构建性能和可重现性有极致要求的大型组织。
  • Pants:

    • 作用: 另一个类似 Bazel 的快速、可扩展的 Python 优先的构建系统,也支持 JVM, Go, Node.js, Shell 等。
    • 特点: 设计哲学与 Bazel 类似,强调速度和可扩展性,配置也相对复杂。

3. 代码组织与结构 (Code Organization)

  • 目录结构约定:

    • 常见模式:/apps (存放可部署的应用), /libs/packages (存放可复用的库/模块), /tools (存放自定义脚本/工具), /docs (文档)。

    • 例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      my-monorepo/
      ├── apps/
      │ ├── web-app/
      │ ├── mobile-app/
      │ └── admin-dashboard/
      ├── libs/
      │ ├── shared-ui/
      │ ├── data-access/
      │ └── utils/
      ├── tools/
      ├── package.json (根)
      ├── turbo.json / nx.json / bazel.rc
      └── ...
  • 路径别名 (Path Aliasing):

    • 在 TypeScript (tsconfig.json) 或 Webpack/Vite 等打包工具中配置路径别名,方便跨包导入。
    • 例如:"paths": { "@myorg/shared-ui": ["libs/shared-ui/src/index.ts"] }

4. 版本管理与发布 (Versioning & Publishing)

  • 工具集成: Lerna, Nx, Turborepo 都提供了发布功能。
  • 策略:
    • 固定/锁定版本 (Fixed/Locked): 所有包共享一个版本号(如 Lerna 的默认模式)。简单,但版本号意义不大。
    • 独立版本 (Independent): 每个包独立管理自己的版本号(语义化版本)。更灵活,更能反映实际变更。
  • 自动化发布: 结合 CI/CD 流程,当主分支合并后,自动检测变更的包,生成变更日志,更新版本号,并发布到包管理器(npm, private registry)。

5. CI/CD 集成 (CI/CD Integration)

  • 核心原则: 只运行受影响的任务
  • 实现方式:
    • 利用 Nx affected, Turborepo 的缓存/影响分析、Bazel 的增量构建特性。
    • CI 脚本首先确定自上次成功构建以来哪些文件被修改(git diff),然后调用工具命令(如 nx affected:build --base=origin/main)来精确执行必要的构建、测试和 lint 任务。
    • 结合远程缓存,避免重复工作。

6. 代码质量与规范 (Code Quality & Standards)

  • 统一配置: 在根目录配置 ESLint, Prettier, Stylelint 等,并在所有子项目中继承。
  • 预提交钩子 (Pre-commit Hooks): 使用 Husky + lint-staged 在代码提交前自动格式化和检查代码,保证一致性。

总结:典型技术栈组合

  1. 轻量级/快速启动:

    • pnpm workspaces + Turborepo
    • 优点:简单易上手,性能好,现代前端项目首选。
  2. 大型复杂项目/企业级:

    • Nx (提供最完整的解决方案)
    • Bazel (追求极致性能和可重现性,多语言)
    • 优点:功能强大,可扩展性好,治理能力强。
  3. 传统/已存在:

    • yarn workspaces + Lerna
    • 优点:成熟稳定,社区支持好。

选择哪个技术栈取决于你的具体需求:项目规模、技术栈、团队偏好、对性能和功能的要求。 TurborepoNx 是当前 JavaScript/TypeScript 生态中最主流和活跃的选择。