写在前面:这篇笔记不是教科书式的定义堆砌,而是完全我作为一个新手,从“怎么建目录”开始,一步步踩坑、质疑、直到理解 Monorepo 核心价值的心路历程。如果你也有同样的困惑,这篇文档就是为你准备的。
第一阶段:上手与困惑 —— "这玩意儿怎么跑起来?"
Q1: 我想搞个 Monorepo,第一步该敲什么命令?
不要上来就去学复杂的工具(Nx/Lerna),先用最简单的 npm 原生命令感受一下架构。
标准起手式:
- 创建根目录:
npm init -y - 创建子包:
npm init -w packages/ui -y- 注意:不需要手动去创建文件夹再进去初始化,直接用
-w(workspace) 一步到位。
- 注意:不需要手动去创建文件夹再进去初始化,直接用
Q2: npm init -w 到底帮我干了什么?
我一开始以为它只是建了个文件夹,后来发现它干了两件大事:
- 物理上:创建了
packages/ui目录和里面的package.json。 - 逻辑上:悄悄修改了根目录的
package.json,加了workspaces字段。- 感悟:这个字段就是“房东”和“租客”的合同,没有它,这就是一堆普通的文件夹。
Q3: 为什么大家都叫 packages 和 src?我能改名吗?
答案:能改,但别改。
- packages: 只是约定俗成。你想叫
libs或apps都可以,只要在package.json的workspaces里配对就行。但叫packages别人一眼就能看懂。 - src: 更是约定俗成。虽然技术上你可以把源码直接扔根目录,但为了和
dist(构建产物)区分开,用src是职业素养的体现。
第二阶段:结构与依赖 —— "依赖到底装在哪里?"
Q4: 根目录和子包的 package.json 区别在哪?
我仔细对比了一下:
- 子包:就是一个标准的 NPM 包,有
dependencies,有scripts,跟普通项目没区别。 - 根目录:它是个“壳”。它通常
private: true(不发布),且多了个灵魂字段workspaces。- 本质:根目录不再是代码的家,而是管理代码的基地。
Q5: 依赖管理最头大的问题:子包用到的库,根目录要不要装?
困惑:如果 ui 包用了 dayjs,utils 包也用了 dayjs,我是不是得装两遍?
解惑:
- 声明层面:必须装两遍。在
ui和utils里都要写上依赖。因为它们理论上是独立的。 - 物理层面:只存一份。npm 会自动把它们“提升”(Hoist)到根目录的
node_modules里。- 结果:你电脑硬盘里只有一份
dayjs,但两个包都能用。
- 结果:你电脑硬盘里只有一份
Q6: 根目录的配置文件(tsconfig/eslint),子包能用吗?
能,而且必须能。
这就是 Monorepo 的一大优势。子包通过 extends 关键字继承根目录的配置。
- 哲学思考:这是否意味着子包不独立了?
- 开发时:是的,它依赖根目录的“营养”(配置)。
- 发布时:它是独立的。构建出的 JS 代码不再包含这些开发配置,别人拿去就能用。
第三阶段:灵魂拷问 —— "我为什么要这么折腾?"
Q7: 如果只是把文件夹放在一起,Monorepo 到底图什么?
这是我最开始的质疑:这不就是把几个项目强行塞在一个文件夹里吗? 直到我遇到了这个场景:
- 以前:改了组件库 -> build -> publish/link -> 切换到业务项目 -> install/rebuild -> 重启 -> 终于看到效果(甚至可能报错)。
- 现在:左屏改组件库,右屏业务项目实时热更新。
- 结论:它不是物理上的“放在一起”,它是源码级的联通。
第四阶段:工程化与构建 —— "项目多了跑得动吗?"
Q9: 几十个包一起跑,流水线会不会炸?
绝对会炸。如果每次提交都把 20 个包全构建一遍,CI 可能会跑半小时。 这时候就需要引入增量构建的概念。
Q10: Turborepo 是什么?它是怎么救命的?
我本来以为它很难,其实它就是个任务调度器。
- 它的魔法:
- 看懂关系:它知道
A依赖B,所以它会先跑B再跑A(不用你自己写脚本控制顺序)。 - 偷懒(缓存):它会算 Hash 值。如果这个包没改过,它直接把上次生成的
dist扔给你,耗时 0秒。
- 看懂关系:它知道
- 怎么读:/ˈtɜːrboʊ ˈriːpoʊ/ (特波-瑞剖)。
Q11: Lerna 又是谁?
它是“老祖宗”。
- 以前:它是万能的神,管安装依赖,管发版。
- 现在:依赖管理被 npm/pnpm 抢了,构建被 Turbo/Nx 抢了。
- 剩余价值:它现在主要用来发版(自动更新版本号、生成 Changelog)。如果你不需要复杂的发版流程,其实可以不用它。
第五阶段:总结与避坑
Git 是一个还是多个?
一个。 这就是 "Mono" (Single) 的含义。所有代码在一个 Git 仓库里,版本号统一管理,提交记录原子化。
最大的坑是什么?
幽灵依赖 (Phantom Dependencies)。
- 现象:你忘了在子包里装
lodash,但代码居然能跑(因为根目录有)。 - 后果:上线后报错找不到模块。
- 对策:用 pnpm。它默认禁止这种行为,比 npm 更严格、更安全。
这份笔记真实记录了从迷茫到清晰的过程。Monorepo 不是银弹,但在处理多包关联开发时,它是真正的生产力神器。