You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A tool for managing JavaScript projects with multiple packages.
Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.
lerna run <script> -- [..args] # 在所有包下运行指定
# 例如
lerna run test # 运行所有包的 test 命令
lerna run build # 运行所有包的 build 命令
lerna run --parallel watch # 观看所有包并在更改时发报,流式处理前缀输出
lerna run --scope package-a test # 运行 package-a 模块下的 test
通过命令行参数--conventional-commits or 在lerna.json中配置"conventionalCommits": true,如上所示,则会在每个package中生成一份changlog;需要注意的是fixed模式下,会在根目录也生成一份changeLog,而independent模式则不会在根目录下生成一份changeLog
{"type":"fix","scope":"title","subject":"a title is fixed","header":"fix(title): a title is fixed","body":null,"footer":null,"notes":[],"references":[],"revert":null}
commit message type的作用用于推导version是patch minor major 版本,判断依据是默认是patch版本,当commit message type是feat or feature 则认为是minor版本,如果scope后面有!,比如test(system)!: 'xxx',那么则认为是major版本
Monorepo vs Multirepo
Monorepo 的全称是 monolithic repository,即单体式仓库,与之对应的是 Multirepo(multiple repository),这里的“单”和“多”是指每个仓库中所管理的模块数量。
Multirepo 是比较传统的做法,即每一个 package 都单独用一个仓库来进行管理。例如:Rollup, ...
Monorep 是把所有相关的 package 都放在一个仓库里进行管理,每个 package 独立发布。 例如:React, Angular, Babel, Jest, Umijs, Vue ...
具体看下图
当然到底哪一种管理方式更好,仁者见仁,智者见智。前者允许多元化发展(各项目可以有自己的构建工具、依赖管理策略、单元测试方法),后者希望集中管理,减少项目间的差异带来的沟通成本。
虽然拆分子仓库、拆分子 npm 包是进行项目隔离的天然方案,但当仓库内容出现关联时,没有任何一种调试方式比源码放在一起更高效。
结合我们项目的实际场景和业务需要,天然的 MonoRepo ! 因为工程化的最终目的是让业务开发可以 100% 聚焦在业务逻辑上,那么这不仅仅是脚手架、框架需要从自动化、设计上解决的问题,这涉及到仓库管理的设计。
一个理想的开发环境可以抽象成这样:
“只关心业务代码,可以直接跨业务复用而不关心复用方式,调试时所有代码都在源码中。”
在前端开发环境中,多 Git Repo,多 npm 则是这个理想的阻力,它们导致复用要关心版本号,调试需要 npm link。而这些是 MonoRepo 最大的优势。
上图中提到的利用相关工具就是今天的主角 Lerna ! Lerna是业界知名度最高的 Monorepo 管理工具,功能完整。
lerna
lerna是什么
Lerna 是一个管理多个 npm 模块的工具,是 Babel 自己用来维护自己的 Monorepo 并开源出的一个项目。优化维护多包的工作流,解决多个包互相依赖,且发布需要手动维护多个包的问题。
一个基本的 Lerna 管理的仓库结构如下
安装
初始化项目
independent与fixed区别
Fixed/Locked mode,在这种模式下,实际上lerna是把工程当作一个整体来对待。每次发布packges,都是全量发布,无论是否修改。
在Independent mode下,lerna会配合Git,检查文件变动,只发布有改动的packge及依赖了该package的包。
创建包
方法1-手动创建:
方法2-使用lerna create方法创建:
lerna常用命令
lerna提供了很多的命令,我们可以通过
lerna --help
查看,但根据2/8法则我们更应该关注下面这几个命令lerna bootstrap
等同于lerna link + yarn install
,用于创建软链包与安装依赖包lerna run
:会像执行一个 for 循环一样,在所有子项目中执行npm script
脚本,并且,它会非常智能的识别依赖关系,并从根依赖开始执行命令;lerna add <package>[@version] [--dev]
向packages内的包安装本地或者线上包,该命令让 Lerna 可以识别并追踪包之间的依赖关系,因此非常重要lerna exec -- <command> [..args]
像lerna run
一样,会按照依赖顺序执行命令,不同的是,它可以执行任何命令,例如shell
脚本;lerna version
根据有变动的包,生成新的包版本,并更新其它包的依赖关系,最终打上tag并提交到远程git仓库,是lerna publish
命令中的默认前置命令lerna publish
发布代码有变动的package
,因此首先您需要在使用Lerna
前使用git commit
命令提交代码,好让Lerna
有一个baseline
;详细整理了lerna version 及 lerna publish内部流程脑图(lernav4.0.0),如下所示
项目添加依赖
1、手动在package-a的
dependencies
ordevDependencies
内添加依赖2、命令行添加
项目卸载依赖
安装依赖
执行lerna bootstrap用于创建软链包与安装依赖包
执行该命令式做了以下四件事:
package
安装依赖npm run prepublish
npm run prepare
显示packages下的各个package的version及依赖关系
清理packages中每个package的node_modules
执行packages中每个pacakge内的scripts
获取本地发包的涉及到的包的新版本号及changeLog
lerna version 生成新的唯一版本号
自动计算包的新版本号的规则,即conventionalCommits:true的场景
fixed模式
根据commit信息计算,当前包的版本是major | minor | patch | premajor | preminor | prepatch | prerelease等,注意这里每个包的comit信息会包含指定scope的commit及没有指定scope的commit msg;这个自动判断是在conventional-changelog-xxx预设内
计算完成之后,会在做一层统一更新,先从包的版本内,获取最高的版本号,然后将其它包的版本号都更改成最高的这个版本号
independent模式下
所以自动推算版本号可以做如下总结
independent模式下,通过git来判断改动了哪些文件,从而判断哪些包做了变动,变了的包会将本次commit msg添加到commits数组内,用于版本推导,推导版本的规则是 type=feat|feture => minor , commit msg footer内BREAK CHANGE,或者scope后面有!,比如fix(cli-utils)!: xxxx; => major(0.x.x开始的版本会被做一次修正 major => minor); 其它都是patch版本
fixed模式下,上一步推导出每个包的版本号之后,在做一次版本号修正,获取每个包推导的版本号,用最大的版本号,去覆盖其它包的版本号
我们推送commit msg的时候,一定要注意改动了哪些包内的文件,然后正确的使用feat|!等推导 minor | major的关键type或者标识
如果使用了bump关键字,不论independent模式还是fixed模式,都是按照bump关键字生成版本号
bump: patch => lerna version patch
bump: prepatch => lerna version prepatch
bump: minor => lerna version minor
bump: preminor => lerna version preminor
0.x.x升级主版本的时候,不会成功,会变成小版本,只有包的主版本本身大于1的时候才会直接升主版本
非自动计算包的新版本号的规则,即conventionalCommits:false的场景
版本号都是通过交互工具,让用户确定包的新版本号,具体如下图所示
lerna version内部流程可以参考总结的脑图
发布npm包
lerna publish --conventional-commits false 成功发布的一个例子
lerna publish内部流程可以参考总结的脑图
lerna.json字段解析
lerna.json解析
version:当前库的版本,如果是具体数字则是fixed模式,如果是independent则是independent模式
npmClient: 允许指定命令使用的client, 默认是 npm, 可以设置成 yarn
command.publish 控制发布的参数,所有命令行的参数都可以在这里定义,避免在命令行上输入参数,其它的命令参数都可以同样的方式书写
command.publish.ignoreChanges:可以指定那些目录或者文件的变更不会被publish
command.bootstrap.ignore:指定不受 bootstrap 命令影响的包
command.bootstrap.npmClientArgs:指定默认传给 lerna bootstrap 命令的参数
command.bootstrap.scope:指定那些包会受 lerna bootstrap 命令影响
packages:指定包所在的目录
command.version.changelogPreset:修改生成changelog文件的预设
生成changeLog
通过命令行参数--conventional-commits or 在lerna.json中配置"conventionalCommits": true,如上所示,则会在每个package中生成一份changlog;需要注意的是fixed模式下,会在根目录也生成一份changeLog,而independent模式则不会在根目录下生成一份changeLog
然后为了保成生成changelog内容的格式,我们需要规范我们的commit-msg
规范comit-msg的方式有很多中,我们选择@commitlint/cli + husky的方式,在提交的时候做校验,然后这里有不同的规范,
这里我们选择
注意点:
当我们执行version or publish命令的时候,如果conventionalCommits: true 或者命令行添加了该参数,则会直接跳过选择包升级版本的步骤,直接到确认版本是否是需要的版本步骤,如果命令行在加上--yes,会跳过所有命令行确认步骤
publish中的message字段是,lerna在在计算版本的时候,会修改package.json且会进行一次commit,所以这里需要我们添加message,且要符合commit-msg校验的,之前本来是想使用包名称的"chore(包名称): publish",但是包名称这里不能使用变量,或者变量不生效,这里到时可以看下源码
lerna-changelog 作用结合pr来生成changelog,具体可以参考下面三个例子
在仓库改造成monorepo之前的commit怎么生成对应的changLog
注意通过命令行conventional-changelog脚本,生成的changeLog指定的预设只包含下面这几种angular, atom, codemirror, ember, eslint, express, jquery, jscs or jshint
conventional-changelog -p angular -i CHANGELOG.md -s
conventional-changelog-angular vs conventional-changelog-conventionalcommits
相同点:
conventional-changelog-angular、conventional-changelog-conventionalcommits 一个类型的库,都是生成changeLog的预设
不同点:
conventional-recommended-bump 自动计算得出包的新版本,而计算得到包的新版本,是由不同的preset内的whatBump函数来计算,而对commit messgae的解析是由conventional-commits-parser解析得出,然后conventional-commits-parser解析的出来的commit.notes来判断是否要升级主版本,而notes是否有值,是通过commit的footer内是否有BREAKING CHANGE关键字来判断;而conventional-changelog-conventionalcommits preset加了一个取巧的方式,通过!,比如test(system)!:xxx来给notes赋值,从而判断是否是主版本升级;
另外conventional-changelog-conventionalcommits预设允许自定义types,而conventional-changelog-angular不可以自定义types
conventional-changelog-conventionalcommits whatBump源码
怎么调试conventional-commits-parser,获取commit-message解析之后的值
可以在控制台直接输入 conventional-commits-parser,然后进入交互模式
fix(title): a title is fixed 回车三次则会输出解析结果
commit message type的作用用于推导version是patch minor major 版本,判断依据是默认是patch版本,当commit message type是feat or feature 则认为是minor版本,如果scope后面有!,比如test(system)!: 'xxx',那么则认为是major版本
更多commit message 解析详情参考conventional-commits-parser
yarn workspace
作用
Workspace 能更好的统一管理有多个项目的仓库,既可在每个项目下使用独立的 package.json 管理依赖,又可便利的享受一条 yarn 命令安装或者升级所有依赖等。更重要的是可以使多个项目共享同一个 node_modules 目录,提升开发效率和降低磁盘空间占用。
Yarn Workspace 共享 node_modules 依赖
project1/package.json:
没有使用 Yarn Workspace 前,需要分别在 project1 和 project2 目录下分别执行 yarn|npm install 来安装依赖包到各自的 node_modules 目录下。或者使用 yarn|npm upgrade 来升级依赖的包。
这会产生很多不良的问题:
如果 project1 和 project2 有相同的依赖项目 a,a 都会各自下载一次,这不仅耗时降低开发效率,还额外占用重复的磁盘空间;当 project 项目比较多的时候,此类问题就会显得十分严重。
如果 project2 依赖 project1,而 project1 并没有发布到 npm 仓库,只是一个本地项目,有两种方式配置依赖:
使用相对路径(如 file: 协议)在 project2 中指定 project1 的依赖。
使用 yarn|npm link 来配置依赖。
没有一个统一的地方对全部项目进行统一构建等,需要到各个项目内执行 yarn|npm build 来构架项目。
使用 Yarn Workspace 之后,上述问题都能得到很好的解决。而且这是 Yarn 内置的功能,并不需要安装什么其他的包,只需要简单的在 projects 目录(Yarn 称之为 workspace-root)下增加如下内容的 package.json 文件即可。
projects/package.json:
在 workspace-root 目录下执行 yarn install:
此时查看目录结构如下:
说明:
如果需要某个特殊的 workspace 不受 Yarn Workspace 管理,只需在此 workspace 目录下添加 .yarnrc 文件,并添加如下内容禁用即可:
Yarn Workspace 命令
yarn workspaces命令
projects/package.json:
project1|project2/package.json:
lerna中开启workspace
lerna默认事没有开启workspace的,也就是packages/xxx目录下的每个包会存在一份node_modules,也就是同一份依赖会存在每个node_modules下
开启workspace
同一个依赖不同版本
Yarn使用放置在工作空间根目录中的一个yarn.lock文件。
此外,它尝试将所有项目的依赖项移至工作区根目录的node_modules,以尽可能避免重复。
只有当当前目录的某个依赖包有不同的版本时才会被放到对应目录的node_modues下
比如
package-a "react-router": "^5.2.0"
package-b "react-router": "4",
这时候根目录会存在一个版本,对应的包下面会存在一个版本
注意点
workspace不能嵌套(只能有一个根workspace)
workspace采用的是向上遍历,所以workspace并不能识别根workspace之外的依赖。
lerna最佳实践
开源项目,采用fixed模式,原因是开源项目涉及到的包比较多,且发布版本之间的时间间隔会比较长
公司内部项目可以根据具体场景决定采用fixed模式还是independent模式
The text was updated successfully, but these errors were encountered: