(https://www.pinterest.com/pin/492722015457646968/)
两种管理程式码的方式
在 git、npm 成为主流后,为了能独立各自的开发环境,我们会为每个专案建立一个 repository 进行版控和套件管理,这种方式就称为 Multi-Repo。
不过产品线的範围变大时,专案 B、C 都是基于专案 A 的底层程式去做新服务的开发,或甚至专案之间都有一些相依的程式码时,除非能将这些共用的程式另外做成套件,并且保证每次的更动在多个专案下都能运作,不然通常会很难维护在同一支产品线下的各个专案。
Mono-Repo
跟 Multi-Repo 相反,Mono-Repo是将相关的产品专案都放在同一个 repository。
Mono-Repo 的好处主要有:
Better developer testing - 可以立即知道修改程式码后,所有相关的专案是否还能正常运作Sharing of common components - 只要开发一次元件,就能给所有专案使用Effective code reviews - 要在专案之间检视程式码时,可以省下切换的时间不过有几个问题是:
程式码日益庞大,目录结构就会愈複杂。因此在建立初期,最后可以做好规划,并保有弹性建置测试的时间可能会愈花愈长。所以在开发工具与环境设定时,尽量能区分专案各自的範围,只针对影响的範围进行测试目前对于Mono-Repo的管理功能最完整的工具是Lerna,在一些着名的大型专案,像是Babel、React、Storybook等都能看到它被大量使用,可见这也成为程式码管理的主流方式之一。
Lerna 基本观念
packages: 一个独立专案或套件的单位,可互相依赖workspaces: 集中与分类 package 的单位versioning: 在版本控管上,主要分为两种。fixed mode,一旦执行发布,各package版本都会维持一致的更新。independent mode,每个package各自维护自己的版本,并不干涉到其他的packagedependency: 在Mono-Repo的架构下,除了从npm安装下来的依赖以外,内部的package也能透过指令的链结,成为依赖之一Lerna 基本指令
npm i -g lerna
- 全域安装lernalerna init
- 专案初始化,建立package.json、lerna.json和packages目录lerna create <pkg-name>
- 建立package目录,并产生package.json、README.md和预设目录lerna ls
: 列出所有packages。-a
- 可列出所有设为private的packages安装依赖--dev
- 将依赖安装到devDendency--peer
- 将依赖安装到peerDendencylerna add <external-pkg-name>
- 为所有packages安装外部依赖lerna add <internal-pkg-name>
- 为所有packages(除依赖本身以外)安装内部依赖lerna add <internal-pkg-name> --scope=<target-pkg-name>
- 在目标package安装内部依赖lerna add <internal-pkg-name> <worksapce-name>/<prefix->*
- 将内部依赖安装到所有适配的packageslerna bootstrap
- 安装package所需要的依赖,如果内部package互相依赖则自动安装。--hoist
- 将同版本的同套件,安装到根目录下的node_modules下lerna clean
- 移除所有package底下的node_moduleslerna run <command>
- 在所有packages下执行npm指令lerna exec 'command'
- 在所有packages下执行shell指令lerna version [major | minor | patch | premajor | preminor | prepatch | prerelease]
- 跳版本lerna publish
- 发布packageslerna changed
- 找出跟上次发布有差异的packageslerna.json设定
packages
: workspaces的列表version
: 预设fixed mode。如果要设成independent mode,则改为 "independent"
npmClient
: 套件管理的执行指令,预设为npm。如果专案是以yarn执行,则改为"yarn"
command
publish
ignoreChanges
: 忽略变更的档案列表,例如.gitignore、README.md等。避免不必要的更新message
: 执行lerna version
提交commit时的内容文字registry
: npm server的位置。预计为 npmjs.orgLerna 与 CICD workflow
以执行测试job为例,在CI上希望只针对有修改的packages执行测试。相关的lerna指令可以这样写 -
# 列出所有从release_tag之后有修改过的packageslerna ls --all --since <release_tag># 在这些packages安装依赖,并把相同套件安装在根目录的node_moduleslerna bootstrap --hoist --since <release_tag> # 在这些packages执行测试的npm指令lerna run test --since <release_tag> --parallel
Lerna and Yarn Workspaces
在一开始尝试以lerna管理mono repo时,发现到有些麻烦的地方,像是安装全域依赖时,会希望依赖可以直接安装在根目录的node_modules下,让所有的packages都能以link方式引用依赖。在lerna中你可能要这样做以下三件事情 -
lerna add <pkg-name>lerna bootstrap --hoistlerna clean
过不久后我改用了yarn的workspaces,以上面的安装依赖为例,我只要输入 yarn add -W <pkg-name>
,就能达到那3个lerna指令才能做到的事,相当的简洁快速。
不过听完这个议程之后,我发现这两个工具是可以相辅相成的。因为在某些状况下,使用Lerna还是比较理想的。
简单、单一範围的事情交给yarn workspaces;而複杂、大範围的事情就给Lerna。个人觉得这样的搭配是最适合管理mono repo,在google相关文章时会发现有些大大也是这样建议使用。