基于 Lerna 管理 packages 的 Monorepo 项目组织

痛点

假设我们要把一些通用能力拆成npm 包,目前有ted-clited-util两个包,其中ted-cli依赖ted-util。那么ted-util发布新版本之后,是不是ted-cli应该安装一下,测试看看有没有bug?其实npm link 命令就是来解决本地开发依赖包的问题的。但如果你的项目里有几十个包互相依赖,这种手工link 的方式就比较捉襟见肘了。

更重要的一点是,ted-clited-util 都是ted 这个项目下的一些包,更好的组织方式应该是把他们放在一个git 仓库里,这样做的好处就是代码管理收敛,也能减少各种配置文件的泛滥和不一致,包括lint、tsconfig 等。

下面要介绍的两个东西,就是来解决这个问题的。Monorepo 是一种代码组织方式,Lerna是用来更好的管理这种组织的工具。

Monorepo

Monorepo 的全称是 monolithic repository,即单体式仓库,与之对应的是 Multirepo(multiple repository),这里的“单”和“多”是指每个仓库中所管理的模块数量。

Multirepo 是比较传统的做法,即每一个 package 都单独用一个仓库来进行管理。例如:Rollup, …

Monorep 是把所有相关的 package 都放在一个仓库里进行管理,每个 package 独立发布。例如:React, Angular, Babel, Jest, Umijs, Vue …

img

Lerna

Lerna 是什么

Lerna 是一个管理多个 npm 模块的工具,是 Babel 自己用来维护自己的 Monorepo 并开源出的一个项目。优化维护多包的工作流,解决多个包互相依赖,且发布需要手动维护多个包的问题。

使用教程

1.安装

npm i -g lerna

2.初始化项目

mkdir lerna-demo
cd lerna-demo
lerna init

初始化之后的目录结构如下,其中lerna.json是用来存放lerna 配置的文件。

.
|-lerna-demo
 |-lerna.json
 |-package.json
 |-packages

3.创建模块文件夹

#lerna create 
lerna create ted-cli
lerna create ted-utils

我们可以看到,lerna 帮助我们在packages 文件夹下初始化了两个包,包含了一些基础的文件和测试文件,我们只用集中注意力开发即可。

.
|-lerna-demo
 |-lerna.json
 |-package.json
 |-packages
 |  |-ted-cli
 |  |  |-README.md
 |  |  |-__tests__
 |  |  |  |-ted-cli.test.js
 |  |  |-lib
 |  |  |  |-ted-cli.js
 |  |  |-package.json
 |  |-ted-utils
 |  |  |-README.md
 |  |  |-__tests__
 |  |  |  |-ted-utils.test.js
 |  |  |-lib
 |  |  |  |-ted-utils.js
 |  |  |-package.json

4.安装依赖

使用 bootstrap 命令,可以一键安装所有module 的依赖,更重要的是,bootstrap命令会默认自动执行npm link,把ted-utils等包link 到node_modules 里,相互依赖的包,相当于是直接使用的的package 里的包,调试和开发十分方便。

另外,有的时候,我们可能会在每个子项目中都依赖相同的第三方包,例如lodash 之类,这时候node_modules 里会有很多重复的包,造成浪费存储等问题,可以使用–hoist 参数,将重复的node_modules提升到根目录的node_modules里,因为node 包是会向上查找的,所有不影响使用。

注意,如果在package.json 的scripts 中用到一些开发时依赖的工具,如webpack 等,因为只会在当前目录下查找,最好还是安装一下,或使用bin文件路径执行

lerna bootstrap
lerna bootstrap --hoist

5.发布npm 包

Lerna 管理包其实有两种模式,Independent mode和Fixed/Locked mode,默认是Fixed。

Fixed 模式:

仓库下所有包的版本都是一致的,每次发布,会更新所有包的版本号。这个版本号存在了lerna.json

的version 字段。这种方式我觉得适合一些高内聚的仓库,就是子包的依赖关系非常强烈那种。

Independent 模式:

这种方式,每次在发布的时候,lerna会检测有变更的包,只发布有变更的包,包的版本号根据package.json 的version 递增。

因为默认是Fixed模式,是在init 的时候确定的,如果init 后想变更也很简单,更改erna.json

的version 字段为independent即可。

//初始化的时候带上参数可以使用不同模式
lerna init
lerna init --independent

说回正题,发布其实很简单,执行publish 就可以,会有交互式的询问版本增加方式。当然,先登录你的npm 账号。

当然,如果是内网发布,需要增加几个参数。

lerna publish // 内网发布
lerna publish --no-verify-access --no-verify-registry

值得注意的是,npm 其实是有prepublish 之类的一些钩子的,我们可以好好利用下,因为有的包需要build 忘记build 可能会发布一个坏包出去。所以在prepublish 这里其实可以执行一下构建和单元测试等命令。

相关命令

#创建工程
lerna init
lerna init --independent
#安装依赖
#2 所有的包都装上
lerna add eslint
#2 只有某个包装上
lerna add eslint --scope=package1
#2 只有某些包装上
lerna add eslint packages/prefix-*
#创建类库软链
#下拉远程依赖
lerna bootstrap
#导入本地某包
lerna import 
#发布某包
lerna publish
lerna publish --npm-tag latest
lerna publish --npm-tag v1.0.0
lerna publish --canary
lerna publish --skip-git
#检查变化
lerna changed
lerna diff [package?]
#运行命令
lerna run [script]
#列出类库
lerna list

参考文档

  • https://lerna.js.org/
  • https://github.com/pigcan/blog/issues/3
  • https://www.jianshu.com/p/d21cd4717542
  • https://juejin.im/post/5a989fb451882555731b88c2
  • https://juejin.im/post/5d4aa8905188250e4258249e#heading-0
  • https://juejin.im/post/5c7491cae51d457fc564d6ee#heading-17
  • https://github.com/chinanf-boy/lerna-zh
  • https://blog.logrocket.com/setting-up-a-monorepo-with-lerna-for-a-typescript-project-b6a81fe8e4f8/
  • https://www.javazhiyin.com/52734.html
  • https://github.com/babel/babel/blob/master/lerna.json

Leave a Comment

邮箱地址不会被公开。 必填项已用*标注