前言
由于公司项目大多采用react-native实现,在APP内部运行,但也要有H5的使用场景,以前很多项目都开发多套代码, 造成人力成本大幅浪费,基于此, 我主导开发了一套RN转H5的一套开发框架以及一整套的代码规范、推送CND等一系列的前端工程化体系, 然而有较多的私有库发布到公司内部NPM镜像上,私有库多了,每个库之间又相互依赖,一段时间下来,版本号就很难管理,大多数时候需要手动更新版本依赖;
其实babel的重要贡献者Jamie Kyle1,在为 Babel 6 工作的过程中发现所有东西都拆分成漂亮的小插件包,但同时也就需要管理数十个软件包, 也遇到同样的问题。因此,多包存储库管理工具 Lerna 应运而生。为让项目更好用,他对项目进行了多次重写,试图让架构更完善。
什么是Lerna
Lerna官网对此给出了官方的解释:Lerna是一个管理包含多个软件包的JavaScript项目的工具。它可以:
1、解决包之间的依赖关系。
2、通过git仓库检测改动,自动同步。
3、根据相关的git提交的commit,生成CHANGELOG。
Lerna是一个命令行工具,可以将其安装在系统全局。简单的命令说明,可以使用:lerna -h查看命令帮助。
两种包管理模式
默认的为固定模式(Fixed mode),当使用lerna init命令初始化项目时,就默认为固定模式,也可以使用 lerna init --independent 命令初始化项目,这个时候就为独立模式(Independent mode)。
固定模式中,packages下的所有包共用一个版本号(version),会自动将所有的包绑定到一个版本号上(该版本号也就是lerna.json中的version字段),所以任意一个包发生了更新,这个共用的版本号就会发生改变。
独立模式允许每一个包有一个独立的版本号,在使用lerna publish命令时,可以为每个包单独制定具体的操作,同时可以只更新某一个包的版本号。此种模式时,lerna.json中的version字段指定为independent即可。
添加lerna.json配置
{
"version": "0.7.30",
"packages": [
"packages/package-1",
"packages/package-2",
"packages/package-3",
"packages/package-4"
],
"command": {
"publish": {
"message": "chore(release): publish %s"
},
"bootstrap": {
"npmClientArgs": [
"--no-package-lock"
]
}
},
"npmClient": "npm"
}
备注: 上面的配置文件中,部分字段做下如下说明:
version指定的是所有包的统一版本号;对于independent模式,这个字段请指定为independent;
npmClient指定的是npm的客户端。默认的,lerna将使用npm。读者也可依所需将程序设置为yarn,甚至cnpm等等。
command字段,可以对publish和bootstrap命令进行参数传递和命令定制。如:command.publish.ignoreChanges,用来设置一些忽略的文件,以避免无关文件的提交对于版本号的变更,如README.md等等。command.bootstrap.npmClientArgs指定在bootstrap命令时,传递的默认参数,比如我们会常常使用--no-package-lock来禁止package-lock.json或yarn.lock等等。
packages字段指定包所在的目录。
常用命令
Lerna命令
初始一个多包的工程
lerna init
上述命令会初始化一个多包工程。初始化之后会在根目录生成packages目录、lerna.json,如果使用independent模式,请使用命令:lerna init --independent
创建子包
lerna create [-y]
在packages所指目录下创建package包。
添加包
lerna add [@version] [--dev] [--exact] [--scope=module名]
上述命令会添加一个包package指明的软件包。
指定--dev是添加在devDependencies中。
指定--exact,则将用精确匹配的版本添加包。
指定--scope将只在此指明的模块中安装这个软件包,否则将在所有packages目录中的包中安装。
对于packages目录下的子包,将通过设立systemlink来解决依赖。
对于npm镜像中存在的包,将安装镜像中的包。
运行命令
运行命令分为两种:任意命令和npm scripts定义的命令。
对于任意命令使用,lerna exec;对于npm scripts定义的命令使用lerna run
以lerna exec为例:
lerna exec [--concurrency number] [--stream] [--parallel] --
特别地,lerna exec -- rm -rf ./node_modules将删除所有包中的依赖。lerna exec -- npm uninstall
lerna exec 和 lerna run 如需要每个子模块相继的执行并按顺序输出,可以指定--concurrency 1。
对于指定了--stream的命令,将把所有子进程的输出立即回显此举可能造成子进程显示顺序交叉,为了分辨输出来源,每个输出,会带上包名;指定了--parallel的命令,则会在scope指定的范围内,并行地执行相关地命令。
lerna run与上述命令不一样的情况在于,lerna run build将在每一个包中scripts字段中执行定义的build命令。
安装所有依赖
lerna bootstrap
上述命令安装所有的依赖、将所有的相关链接做好,同时在所有的包中运行npm run prepublish。随后,在所有包中运行npm run prepare。此时,所有的依赖均已完备。
发布
lerna publish // 发布所有的包。
清理
lerna clean // 删除所有的node_modules
一些优化
合并公共依赖
我们在开发过程中,经常发现包依赖类似。这样,我们发现运行lerna bootstrap之后,会重复安装依赖包,这样会造成空间的浪费和效率的降低。为此,我们可以把同样的依赖包在根目录安装一次即可。此时,可以使用lerna bootstrap --hoist命令,则公用的依赖,只会在顶层目录安装一次。
发布带有scope公有包
带有scope的包,需要发布时候,如果是公有的包,需要在npm publish时候使用npm publish --access public。为了能够成功publish,并使用lerna流程,请在每个子包的lerna.json中加入:
"publishConfig": {
"access": "public"
}
检测循环依赖
lerna本身内置了检测循环依赖的功能,如果出现循环依赖。会在bootstrap时候给出提示:
此时,请依照提示去掉循环依赖,以保证软件包的正常运行。
配置lerna后目录结构
.
├── CHANGELOG.md
├── README.md
├── docs
├── lerna.json
├── package.json
├── packages
│ ├── package-1
│ ├── ── package.json
│ ├── package-2
│ ├── ── package.json
│ ├── package-3
│ ├── ── package.json
│ ├── package-4
│ ├── ── package.json
│ └── package-5
│ ├── ── package.json
├── tslint.json
└── website
运行脚本
"scripts": {
"bootstrap:ci": "lerna bootstrap --npm-client=npm",
"bootstrap:lerna": "lerna bootstrap -- --ignore-engines",
"release:lerna": "lerna publish --exact --skip-temp-tag --registry http://127.0.0.1:4873",
"release:beta": "lerna publish --exact --skip-temp-tag --preid=beta --npm-tag=beta --registry http://127.0.0.1:4873"
}