www.lishuaishuai.com/engineering…html
对于维护过多个package的同窗来讲,都会遇到一个选择:这些package是放在一个仓库里维护仍是放在多个仓库里单独维护,数量较少的时候,多个仓库维护不会有太大问题,可是当package数量逐渐增多时,一些问题逐渐暴露出来:前端
Monorepo
的全称是 monolithic repository,即单体式仓库,与之对应的是 Multirepo
(multiple repository),这里的“单”和“多”是指每一个仓库中所管理的模块数量。node
Multirepo
是比较传统的作法,即每个模块都单独用一个仓库来进行管理,典型案例有 webpack
,优缺点总结以下:webpack
优势:git
缺点:github
Monorep
是把全部相关的 module 都放在一个仓库里进行管理,每一个 module 独立发布,典型案例有 babel,优缺点总结以下:web
优势:npm
缺点:json
A tool for managing JavaScript projects with multiple packages.bootstrap
Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.
Lerna 是一个管理多个 npm 模块的工具,是 Babel 本身用来维护本身的 Monorepo 并开源出的一个项目。优化维护多包的工做流,解决多个包互相依赖,且发布须要手动维护多个包的问题。Lerna 如今已经被不少著名的项目组织使用,如:Babel, React, Vue, Angular, Ember, Meteor, Jest 。
一个基本的 Lerna 仓库结构以下:
my-lerna-repo/
┣━ packages/
┃ ┣━ package-1/
┃ ┃ ┣━ ...
┃ ┃ ┗━ package.json
┃ ┗━ package-2/
┃ ┣━ ...
┃ ┗━ package.json
┣━ ...
┣━ lerna.json
┗━ package.json
复制代码
全局安装 lerna,再执行相关命令
$ npm i -g lerna
$ mkdir lerna-repo && cd $_
$ lerna init
复制代码
lerna init
命令会建立一个用来配置的 lerna.json
,文件以及用于存放全部 module
的 packages
文件夹,以下:
lerna-repo/
┣━ packages/
┣━ lerna.json
┗━ package.json
复制代码
Lerna 提供两种不一样的方式来管理你的项目:Fixed
或 Independent
,默认采用 Fixed
模式,若是你想采用 Independent
模式,只需在执行 init
命令的时候加上 --independent
或 -i
参数便可。
Fixed/Locked 模式(默认) 固定模式下 Lerna 项目在单一版本线上运行。版本号保存在项目根目录下 lerna.json
文件中的 version
下。当你运行 lerna publish
时,若是一个模块自上次发布版本之后有更新,则它将更新到你将要发布的新版本。这意味着你在须要发布新版本时只需发布一个统一的版本便可。
Independent 模式(--independent) 独立模式下 Lerna 容许维护人员独立地的迭代各个包版本。每次发布时,你都会收到每一个发生更改的包的提示,同时来指定它是 patch
,minor
,major
仍是自定义类型的迭代。
在独立模式下,lerna.json 文件中 version 属性的值将被忽略。
npm i
$ lerna bootstrap
复制代码
当执行完上面的命令后,会发生如下的行为:
npm install
安装全部依赖npm run prepublish
npm run prepare
$ lerna add <package>[@version] [--dev] [--exact] # 命令签名
当咱们执行此命令后,将会执行下面那2个动做:
- 在每个符合要求的模块里安装指明的依赖包,相似于在指定模块文件夹中执行 `npm install <package>`。
- 更新每一个安装了该依赖包的模块中的 `package.json` 中的依赖包信息
# 例如
$ lerna add module-1 --scope=module-2 # 将 module-1 安装到 module-2
$ lerna add module-1 --scope=module-2 --dev # 将 module-1 安装到 module-2 的 devDependencies 下
$ lerna add module-1 # 将 module-1 安装到除 module-1 之外的全部模块
$ lerna add babel-core # 将 babel-core 安装到全部模块
复制代码
$ lerna exec -- <command> [..args] # 在全部包中运行该命令
# 例如
$ lerna exec --scope=npm-list yarn remove listr # 将 npm-list 包下的 listr 卸载
$ lerna exec -- yarn remove listr # 将全部包下的 listr 卸载
复制代码
能够经过 clean 命令来快速删除全部模块中的 node_modules 文件夹。基本命令以下:
$ lerna clean
复制代码
$ lerna updated
# 或
$ lerna diff
复制代码
Lerna 提供了两种建立或导入模块的方式,分别是 create
,import
。
建立一个 lerna 管理的模块。基本命令格式以下:
$ lerna create <name> [loc]
复制代码
name
是模块的名称(必填项,可包含做用域,如 @uedlinker/module-a
),必须惟一且能够发布(npm 仓库中无重名已发布包)
loc
是自定义的包路径(选填), 会根据你在 lerna.json
文件中的 packages
的值去匹配,默认采用该数组的第一个路径,指定其余路径时只要写明路径中的惟一值便可,例如想选择 /user/lerna-repo/modules
这个路径,只须要执行以下命令便可
命令执行完后,lerna 会帮咱们在指定位置建立模块的文件夹,同时会默认在该文件夹下执行 npm init
的命令,在终端上根据根据提示填写全部信息后会帮咱们建立对应的 package.json
文件,大体的结构以下
lerna-repo/
┣━ packages/
┃ ┗━ package-a/
┃ ┣━ ...
┃ ┗━ package.json
┣━ lerna.json
┗━ package.json
复制代码
导入一个已存在的模块,同时保留以前的提交记录,方便将其余正在维护的项目合并到一块儿。基本命令格式以下:
$ lerna import <dir>
复制代码
dir
是本项目外的包含 npm 包的 git 仓库路径(相对于本项目根路径的相对路径)
执行后会将该模块总体复制到指定的依赖包存放路径下,同时会把该模块以前全部提交记录合并到当前项目提交记录中
建立完毕以后,咱们能够经过 list
命令来查看和确认如今管理的包是否符合咱们的预期,执行以下命令:
$ lerna list
复制代码
lerna run
运行 npm script,能够指定具体的 package。
$ lerna run <script> -- [..args] # 在全部包下运行指定
# 例如
$ lerna run test # 运行全部包的 test 命令
$ lerna run build # 运行全部包的 build 命令
$ lerna run --parallel watch # 观看全部包并在更改时发报,流式处理前缀输出
$ lerna run --scope my-component test # 运行 my-component 模块下的 test
复制代码
lerna 经过 version
命令来为各个模块进行版本迭代。基本命令以下:
$ lerna version [major | minor | patch | premajor | preminor | prepatch | prerelease]
复制代码
若是不选择这次迭代类型,则会进入交互式的提示流程来肯定这次迭代类型
例如:
$ lerna version 1.0.1 # 按照指定版本进行迭代
$ lerna version patch # 根据 semver 迭代版本号最后一位
$ lerna version # 进入交互流程选择迭代类型
复制代码
注意: 若是你的 lerna 项目中各个模块版本不是按照同一个版本号维护(即建立时选择 independent 模式),那么会分别对各个包进行版本迭代
当执行此命令时,会发生以下行为:
package.json
中的 version
值来反映这次更新小技巧: 你能够在执行此命令的时候加上 ——no-push 来阻止默认的推送行为,在你检查确认没有错误后再执行 git push 推送
--conventional-changelog
$ lerna version --conventional-commits
复制代码
version
支持根据符合规范的提交记录在每一个模块中自动建立和更新 CHANGELOG.md
文件,同时还会根据提交记录来肯定这次迭代的类型。只须要在执行命令的时候带上 --conventional-changelog
参数便可
--changelog-preset
$ lerna version --conventional-commits --changelog-preset angular-bitbucket
复制代码
changelog 默认的预设是 angular
,你能够经过这个参数来选择你想要的预设建立和更新 CHANGELOG.md
预设的名字在解析的时候会被增添 conventional-changelog-
前缀,若是你设置的是 angular
,那么实际加载预设的时候会去找 conventional-changelog-angular
这个包,若是是带域的包,则须要按照 @scope/name
的规则去指明,最后会被解析成 @scope/conventional-changelog-name
。
小技巧: 上述 2 个参数也能够直接写在 lerna.json 文件中,这样每次执行 lerna version 命令的时候就会默认采用上面的 2 个参数
"command": {
"version": {
"conventionalCommits": true,
"changelogPreset": "angular"
}
}
复制代码
在一切准备就绪后,咱们能够经过 publish 命令实现一键发布多个模块。基本命令以下:
$ lerna publish
复制代码
当执行此命令时,会发生以下行为:
lerna version
,2.x 版本遗留的行为)注意: Lerna 不会发布在 package.json
中将 private
属性设置为 true
的模块,若是要发布带域的包,你还须要在 'package.json' 中设置以下内容:
"publishConfig": {
"access": "public"
}
复制代码
若是以前已执行过 lerna version
命令,这里若是直接执行 lerna publish
会提示没有发现有更新的包须要更新,咱们能够经过从远端的 git 仓库来发布:
lerna publish from-git
复制代码
{
"version": "1.1.3",
"npmClient": "npm",
"command": {
"publish": {
"ignoreChanges": [
"ignored-file",
"*.md"
]
},
"bootstrap": {
"ignore": "component-*",
"npmClientArgs": ["--no-package-lock"]
}
},
"packages": ["packages/*"]
}
复制代码
version:当前库的版本 npmClient: 容许指定命令使用的client, 默认是 npm, 能够设置成 yarn command.publish.ignoreChanges:能够指定那些目录或者文件的变动不会被publish command.bootstrap.ignore:指定不受 bootstrap 命令影响的包 command.bootstrap.npmClientArgs:指定默认传给 lerna bootstrap 命令的参数 command.bootstrap.scope:指定那些包会受 lerna bootstrap 命令影响 packages:指定包所在的目录
最后咱们来讲说 Monorepo 的适用场景
另外,还须要:
基础建设是指强大的构建工具,能知足全部模块的 build 需求(纯前端项目的话,build 压力不大)
Monorepo 环境下,而且鼓励改别人的代码,一方面须要持续集成机制(例如 React – CircleCI)确认修改带来的影响,另外一方面还须要不一样团队之间互相信任。
sosout.github.io/2018/07/21/… www.uedlinker.com/2018/08/17/… juejin.im/post/5d4aa8…