方案的起源
单仓库管理多个包
在进行业务开发的时候,往往能抽离出很多相似的模块跟组件。自己负责的项目一般也不止一个,如果只是将其手动复制到不同的项目当中,组件跟模块往后的更新与迭代将会很困难。
大多数人都会这么干,那就是将公共的组件、模块、工具方法、发布到 npm私有仓库上进行统一的管理跟迭代...
遇到的问题
- 由于大家都是高自我要求的前端开发者,踊跃的抽离项目中的公共模块,发布到
npm 仓库当中,所以就造成了 npm 公共组件仓库体积的急剧增加。
- 不仅如此,由于公共方法也被放到这个仓库当中,造成了一个很尴尬的情况,自己只是修改了公共方法当中的导出操作,不得不发布了整个仓库。
提出的解决方案(多仓库管理多个包)
最开始的解决方案,将公共操作方法,公共组件,公共模块弄成了三个仓库单独发布
自从这样实行以来,上面的问题得到了初步的解决,本以为满心欢喜,结果却遇到了更棘手的情况。
遇到的问题
- 在使用公共操作方法时,细心的小明发现了公共操作方法当中的某个缺陷,提出了改进的意见,于是提了一个
pull request ,负责管理公共仓库的小华,review 以后觉得相当的不错,于是打上了 tag 更新了版本。
- 某日,小明在使用公共模块时,发现自己改进的操作方法,在这个模块当中没有更改,于是在查看源码之后,发现公共模块依赖的公共操作方法的版本没有升级...
简单的总结
- 使用单独的仓库进行管理多个包,更改一个,必须发布整个仓库,文件目录过多,阅读体验差。
- 使用多仓库管理多个包,难以梳理其中的依赖情况,不便于管理,维护多个仓库浪费时间。
那能不能一个仓库管理多个包,但是这些包相对于这个仓库,又是独立的呢?
答案是可以的,在调研之后,发现业界早已存在解决方案,那就是 monorepo 管理方案,本文基于monorepo 方案的开源框架 lerna 进行简单的讲解。
lerna 的简单使用
Lerna 是一个管理多个 npm 模块的工具,是 Babel 自己用来维护自己的 Monorepo 并开源出的一个项目。优化维护多包的工作流,解决多个包互相依赖,且发布需要手动维护多个包的问题。
简单的安装
npm i -g lerna
git init lernaExample
cd lernaExample
lerna init
然后根据提示,项目当中生成的 package.json 跟 lerna.json 如下
// package.json
{
"name": "root",
"private": true, // 私有的,不会被发布,是管理整个项目,与要发布到npm的解耦
"devDependencies": {
"lerna": "^3.15.0"
}
}
// lerna.json
{
"packages": [
"packages/*"
],
"version": "0.0.0" // 仓库的版本
}
创建 npm 包
这里有个注意点是,lerna 中的仓库名 @lernaExample 必须一致
lerna create @lernaExample/utils
lerna create @lernaExample/components
lerna create @lernaExample/decorator
文件目录

增加依赖
lerna add react // 为所有 package 增加 react 模块 (像这种公共的包可以使用 lerna bootstrap --hoist 提升到项目的工程目录)
lerna add classnames--scope @lernaExample/components // 为 @lernaExample/components 增加 classnames 模块
lerna add @lernaExample/utils--scope @lernaExample/components // 增加内部模块之间的依赖
// 想删除某个依赖项
lerna clean
更多的命令可以查看 lerna

发布
lerna publish
// 按照提示发布就行
// 如果发布报错,可能是没有相对于仓库的权限
lerna 完整示例
在上文中,只是简单的了解 lerna 的基本使用,在项目当中使用的话还存在下面的几个问题。
- 包中的文件没有经过编译,如果是直接书写的
es6 文件,则很有可能浏览器中不能兼容
- 没有对文件进行基本的
eslint 格式校验
- 没有对发布进行约束,比如
commit 校验,changlog 文件生成等
环境搭建
babel 环境
需要下载的 babel 包
"dependencies": {
"@babel/runtime": "^7.6.2",
"@lernaExample/utils": "^0.0.0",
"classnames": "^2.2.5",
"react": "^16.8.0"
},
"devDependencies": {
"@babel/cli": "^7.6.0",
"@babel/core": "^7.6.0",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-decorators": "^7.6.0",
"@babel/plugin-transform-regenerator": "^7.4.5",
"@babel/plugin-transform-runtime": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"@babel/preset-react": "^7.0.0",
"babel-plugin-import": "^1.12.2"
},
.babelrc 文件配置
{
"presets": [
[
"@babel/preset-env",
{
"modules": false // 模块使用 es modules ,不使用 commonJS 规范
}
],
"@babel/preset-react"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
["@babel/plugin-transform-regenerator"],
["@babel/plugin-proposal-class-properties"],
[
"@babel/plugin-transform-runtime",
{
"corejs": false, // 默认值,可以不写
"helpers": true, // 默认,可以不写
"regenerator": true, // 通过 preset-env 已经使用了全局的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不污染全局的 regeneratorRuntime
"useESModules": true // 使用 es modules helpers, 减少 commonJS 语法代码
}
],
[
"import",
{
"libraryName": "antd"
}
] // 通过手动引入antd样式
]
}

package.json 文件中增加编译命令
"main": "lib/index.js", // 文件入口
"scripts": {
"compile": "babel src --out-dir lib",
...
},
使用方式 npm run compile
eslint 环境
我这边直接使用的 vscode 扩展的 eslint 工具,如果团队内部对 eslint 有自己的定义,可以单独下载 eslint 包,对其文件进行配置。
commit 以及 changelog 环境
自动校验commit规范
使用 husky 包,相关依赖项
"devDependencies": {
"@commitlint/cli": "^7.0.0",
"@commitlint/config-conventional": "^7.0.1",
"husky": "^0.14.3",
}
创建 commitlint.config.js 文件
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'bug',
'docs',
'style',
'refactor',
'test',
'chore',
'version',
'build',
'ci',
'perf',
'revert',
]
],
'scope-case': [2, 'always', ['lower-case', 'snake-case', 'upper-case', 'kebab-case']],
'subject-case': [0]
}
};
自动生成changelog记录
npm i cz-lerna-changelog 下载 lerna 专用的 changelog 包

使用
- 更改了文件之后,执行
npm run compile
- 写上
commit 内容,如果内容不符合,会自动打回
- 如果不用发布版本,则直接执行
lerna version,打上tag 生成 changlog 记录
- 需要发布版本
- 只发布一个,可以先执行
lerna updated,然后执行 lerna publish
- 全部发布, 执行
lerna publish
- 如果不想生成版本记录,以及不发布版本,则直接执行
git push
总结
- 以往的仓库管理方式的不足之处
- 使用
lerna 之后,package 之间存在互相的依赖项,lerna 会自动对其管理,不需要手动更新 package.json 中依赖项的版本号。
- 一个基本完整的
lerna 使用介绍
参考文章:lerna+yarn workspace+monorepo项目的最佳实践(掘金)
方案的起源
单仓库管理多个包
在进行业务开发的时候,往往能抽离出很多相似的模块跟组件。自己负责的项目一般也不止一个,如果只是将其手动复制到不同的项目当中,组件跟模块往后的更新与迭代将会很困难。
大多数人都会这么干,那就是将公共的组件、模块、工具方法、发布到 npm私有仓库上进行统一的管理跟迭代...
遇到的问题
npm仓库当中,所以就造成了npm公共组件仓库体积的急剧增加。提出的解决方案(多仓库管理多个包)
最开始的解决方案,将公共操作方法,公共组件,公共模块弄成了三个仓库单独发布
自从这样实行以来,上面的问题得到了初步的解决,本以为满心欢喜,结果却遇到了更棘手的情况。
遇到的问题
pull request,负责管理公共仓库的小华,review以后觉得相当的不错,于是打上了tag更新了版本。简单的总结
那能不能一个仓库管理多个包,但是这些包相对于这个仓库,又是独立的呢?
答案是可以的,在调研之后,发现业界早已存在解决方案,那就是
monorepo管理方案,本文基于monorepo方案的开源框架lerna进行简单的讲解。lerna的简单使用Lerna是一个管理多个 npm 模块的工具,是 Babel 自己用来维护自己的 Monorepo 并开源出的一个项目。优化维护多包的工作流,解决多个包互相依赖,且发布需要手动维护多个包的问题。简单的安装
然后根据提示,项目当中生成的
package.json跟lerna.json如下创建
npm包这里有个注意点是,
lerna中的仓库名@lernaExample必须一致增加依赖
更多的命令可以查看 lerna
发布
lerna完整示例在上文中,只是简单的了解
lerna的基本使用,在项目当中使用的话还存在下面的几个问题。es6文件,则很有可能浏览器中不能兼容eslint格式校验commit校验,changlog文件生成等环境搭建
babel环境需要下载的
babel包.babelrc文件配置package.json文件中增加编译命令eslint环境我这边直接使用的
vscode扩展的eslint工具,如果团队内部对eslint有自己的定义,可以单独下载eslint包,对其文件进行配置。commit以及changelog环境自动校验commit规范
使用
husky包,相关依赖项创建
commitlint.config.js文件自动生成
changelog记录npm i cz-lerna-changelog下载lerna专用的changelog包使用
npm run compilecommit内容,如果内容不符合,会自动打回lerna version,打上tag生成changlog记录lerna updated,然后执行lerna publishlerna publishgit push总结
lerna之后,package之间存在互相的依赖项,lerna会自动对其管理,不需要手动更新package.json中依赖项的版本号。lerna使用介绍参考文章:lerna+yarn workspace+monorepo项目的最佳实践(掘金)