JSDC 2020 回顾 - Lerna

png
(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各自维护自己的版本,并不干涉到其他的package
  • dependency: 在Mono-Repo的架构下,除了从npm安装下来的依赖以外,内部的package也能透过指令的链结,成为依赖之一

Lerna 基本指令

  • npm i -g lerna - 全域安装lerna
  • lerna init - 专案初始化,建立package.json、lerna.json和packages目录
  • lerna create <pkg-name> - 建立package目录,并产生package.json、README.md和预设目录
  • lerna ls: 列出所有packages。
    • -a - 可列出所有设为private的packages
  • 安装依赖
    • --dev - 将依赖安装到devDendency
    • --peer - 将依赖安装到peerDendency
    • lerna 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->* - 将内部依赖安装到所有适配的packages
  • lerna bootstrap - 安装package所需要的依赖,如果内部package互相依赖则自动安装。
    • --hoist - 将同版本的同套件,安装到根目录下的node_modules下
  • lerna clean - 移除所有package底下的node_modules
  • lerna run <command> - 在所有packages下执行npm指令
  • lerna exec 'command' - 在所有packages下执行shell指令
  • lerna version [major | minor | patch | premajor | preminor | prepatch | prerelease] - 跳版本
  • lerna publish - 发布packages
  • lerna changed - 找出跟上次发布有差异的packages

lerna.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.org

Lerna 与 CICD workflow

以执行测试job为例,在CI上希望只针对有修改的packages执行测试。相关的lerna指令可以这样写 -

# 列出所有从release_tag之後有修改过的packages
lerna ls --all --since <release_tag>

# 在这些packages安装依赖,并把相同套件安装在根目录的node_modules
lerna 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 --hoist
lerna clean

过不久後我改用了yarn的workspaces,以上面的安装依赖为例,我只要输入 yarn add -W <pkg-name>,就能达到那3个lerna指令才能做到的事,相当的简洁快速。

不过听完这个议程之後,我发现这两个工具是可以相辅相成的。因为在某些状况下,使用Lerna还是比较理想的。

简单、单一范围的事情交给yarn workspaces;而复杂、大范围的事情就给Lerna。个人觉得这样的搭配是最适合管理mono repo,在google相关文章时会发现有些大大也是这样建议使用。


<<:  【图解演算法教学】二元树的一生,听说有些凄凉!?

>>:  [DayN]上传档案绕过

DAY 18:Singleton Pattern,致独一无二的你

什麽是 Singleton Pattern? 整个程序运作只会有此一个物件,不会创建第二个重复的物件...

第27天:this(2)

接续昨天的这个例子 function sayHello(){ console.log(`你好,我是$...

Day 31 - 迟来的铁人赛心得

某人可能会迟到,但从不缺席 (没x 失踪很久了好吗== 故事原点 在正式参加铁人赛之前,我从不知道...

[Day18] CH10:排序大家族——合并排序法

今天要介绍的是我们学的最後一个排序法——合并排序法(Merge Sort)。 合并排序法 分成切割与...

Day 38 - 在 AWS Lambda 中使用 YOLO 推估 (Inference)

Day 38 - 在 AWS Lambda 中使用 YOLO 推估 (Inference) 在 Da...