什么是 Monorepo
作为前端工作者,可能听说过微前端,也听说过 Monorepo。有时候我们会混为一谈。
简单的说,微前端是「聚」,Monorepo 是「分」。
微前端解决的问题是,将多个以不同技术完成,但是拥有相同业务逻辑的 app 聚合到一起。而 Monorepo 却相反,是将一个 app 中相对比较独立的逻辑,拆分成独立的 package,减少相互的耦合和制约,并且使用同一个 Repo。
Monorepo 和普通 Repo 的关系
通常情况下,我们会在一个 Repo 下使用一个 package。类似如下的项目结构。
src
-feature1
-feature2
-feature3
-feature4
package.json
这样的项目结构在项目还比较小的情况下还好。但是随着项目的不断扩大,就会出现一些弊端:
- 项目越来越大,每一次很小的改动都会影响整个项目。(build 时间长,或者业务逻辑的影响)
- 业务逻辑直接的耦合,无法拆分清楚。
- 可能会多个 team 维护一个 Repo,team 直接也不好合作。
- 不同的业务逻辑可能希望分开发布来避免互相影响。
解决方法当然是 Monorepo,我们看一下 Monorepo 的项目结构:
projects
-feature1
-src
-package.json
-feature2
-src
-package.json
-feature3
-src
-package.json
package.json
对应相面的问题,我们来看一看 Monorepo 的优势:
- 项目上功能的增加,转换为子 package 的增加,每一个独立的功能也只需要关心自己当前的 package 即可。(包括编译)
- 因为 package 之间的隔绝,需要我们将 package 直接的依赖关系理清楚。业务逻辑的依赖关系转换为了 package 直接的依赖关系,更为清晰。
- 每个 team 维护自己的 package 就可以的。甚至可以指定属于自己的编码规范,lint,build,都可以自己做自己的配置。
- 按照 build 分开 deploy 即可。
Monorepo 需要解决的问题
- 每个 package 的依赖如何避免重复安装。比如一个 Angular 项目,每个 package 可能都需要安装 angular,如何避免不同 package 重复安装。
- 相同 Repo 下不同 package 直接依赖如何处理。
- 单 package 下我们可以简单的使用,npm script 处理各种问题,在多 package 下我们该如何使用?
下面我们来看看常用的几种解决方案:
方案一,Angular 默认的解决方案:
- 利用 node 的向上查找策略,将所有依赖收敛到根 package。package 内只安装自己独有的依赖。
- 使用 npm link 或者 tsconfig 中的 path,将编译后的目录连接到每个项目。
- Angular 通过 angular cli 提供。
方案二,Lerna + yarn workspaces
- Lerna 提供的 lerna add xxx --scope=xxxxx 来安装依赖。
- lerna add 同时支持,安装当前项目的其他 package,如果本地存在当前版本,优先加载本地(类似 npm link),如果本地不存在则从远端加载。
- lerna 可以在任意 folder 执行,加上,--scope 即是某个 package。Lerna 还集成了一系列其他常用的命令。
- lerna clean 清除所有的 node_modules
- lerna bootstrap install all packages
- lerna version
以上两种方案其实并不算完美:
- 如果每一个 package 的依赖都互相独立,则可能出现同一个 repo 下对于相同依赖的多次安装,不仅会增加项目的体积,也会因为相同项目对于同一个依赖的不同引用出现使用上的问题。Node 中的 node_module hell 也会因为 Monorepo 而成倍的放大。
- 如果项目中的多数依赖都放在 package 中,则会导致每个 package 的依赖不够清晰(Angular 推荐使用,peerdependency 的折中方案)。假设某一天,我希望将某一个 package 独立成一个 repo,发现,很难写清楚,这个 package 真实的依赖有哪些。
- 内部项目的依赖关系仍然没有办法指定清楚。
方案三:pnpm
归根结底,以上问题还是因为包管理工具不够强大。pnpm 的出现,是为了解决,node_modules 打平的问题而出现的。
- pnpm 的出现,使得,子 package 可以像一个独立的 app 一样去指定自己的依赖。
pnpm install xxx --filter=xxxxx
,因为 pnpm 中 node_module 会 link 到 pnpm 的根目录,所以,完全不用担心,同一个 package 在不同子 package 的重复安装。 - pnpm 提供了
workspace*
来制定当前项目的 package,*
会在pnpm publish
前替换成真实的版本号。 - pnpm 提供了一系列支持 monorepo 的命令可以让我们减少使用 lerna.