Monorepo
Monorepo 是管理项目代码的一个方式,指在一个项目仓库 (repo) 中管理多个模块/包 (package),不同于常见的每个模块建一个 repo。
monorepo 好处是统一的工作流和Code Sharing。搭建一套脚手架,就能管理(构建、测试、发布)多个 package,统一测试、统一发版。
坏处也很明显,就是repo的体积会比较大,由于每个 package 都有自己的package.json,会安装自己的node_modules,但是大概率会有很多包是重复的,这就使本来就很大的 node_modues 变得更大。
目前常见的monorepo解决方案是 Lerna 和 yarn
的 workspaces
特性
对于node_modules包重复安装的问题,lerna提供了--hoist选项
,相同的依赖,会「提升」到 repo 根目录下安装,但……太鸡肋了,lerna 直接以字符串对比 dependency 的版本号,完全相同才提升,semver 约定在这并不起作用。
yarn作为包管理器很好的解决了这个问题,只需要在根package.json
中以 workspaces
字段声明 packages目录和"private": true,
yarn 就会以 monorepo 的方式管理 packages。yarn 会以 semver 约定来分析 dependencies 的版本,安装依赖时更快、占用体积更小;
启用了yarn workspace的项目,使用yarn安装依赖时,yarn会为工作区的所有包创建符号链接,在根目录的node_modules可以看到
我们可以结合lerna yarn来用
谁在用 lerna
lerna 仓库
lerna参考教程
https://segmentfault.com/a/1190000019350611
//www.greatytc.com/p/35787ebecf2e
yarn workspace 相关命令
-
yarn workspace <workspace_name> <command>
在指定工作区执行命令,如:
注意: workspace_name取包名(package.json的name属性值),add或remove内部包时带上版本号;
下面命令会将react 、react-dmo
添加到packages/awesome-package/package.json
的devDependencies
yarn workspace awesome-package add react react-dom --dev
-
yarn workspaces run <command>
为所有工作区运行命令(lerna run 命令有同样功能),如:
将会在每个工作区运行 test 脚本
yarn workspaces run test
-
yarn workspaces info [--json]
显示当前项目的工作区依赖关系
yarn workspaces info
vue-next项目下打印结果: 显示了工作区的相互依赖关系
lerna常用 commands
-
lerna init
初始化lerna管理项目,生成如下目录:
packages/
package.json
lerna.json
-
lerna bootstrap --hoist
为所有项目安装依赖,并链接所有依赖包,类似于npm i
使用--hoist
选项后,所有公共的依赖都只会安装在根目录的node_modules目录中去,而不会在每个包目录下的node_modules中都保留各自的依赖包。 -
lerna clean
删除所有项目的node_modules目录 -
lerna run [script]
默认为所有的项目运行npm run [script]脚本,可以指定项目; -
lerna changed
列出下次发版lerna publish
要更新的包。 -
lerna publish
版本发布,按提示选择版本号(递增,或自定义),将会执行以下步骤:
- 运行lerna updated来决定哪一个包需要被publish
- 如果有必要,将会更新lerna.json中的version
- 将所有更新过的的包中的package.json的version字段更新
- 将所有更新过的包中的依赖更新
- 为新版本创建一个git commit或tag
- 将包publish到npm上;注意要先用npm adduser登录npm源,否则会失败;
-
lerna add <package>[@version] [--dev] [--exact] [--peer]
:可以指定为某一个或所有的包安装依赖,依赖可以是外部(npm i 安装的)也可以是内部依赖(packages/下的包,会创建符号链接),example:
- lerna add babel , 该命令会在package-1和package-2下安装babel
- lerna add react --scope=package-1 ,该命令会在package-1下安装react
- lerna add package-2 --scope=package-1,该命令会在package-1下安装package-2
-
lerna create <name> [loc]
创建一个lerna管理的包 -
lerna ls
控制台打印 packages下的包名 -
lerna link
类似npm link
,创建软连接 , 将相互依赖的所有包符号链接在一起
lerna工作的两种模式
- Fixed/Locked mode (default)
vue,babel都是用这种,在publish的时候,会在lerna.json文件里面"version": "0.1.5",,依据这个号,进行增加,只选择一次,其他有改动的包自动更新版本号。
- Independent mode
lerna init --independen
t初始化项目,lerna.json
文件里面"version": "independent"
,
每次publish时,都将得到一个提示符,提示每个已更改的包,以指定是补丁、次要更改、主要更改还是自定义更改。
启用yarn的workspaces模式
默认是npm, 而且每个子package都有自己的node_modules,通过这样设置后,只有顶层有一个node_modules
- 修改顶层 package.json and lerna.json
# package.json 文件加入
"private": true,
"workspaces": [
"packages/*"
],
# lerna.json 文件加入
"useWorkspaces": true,
"npmClient": "yarn"
说了那么多,接下来实战演示一把:
1. 初始化项目
npm intall lerna -g
mkdir lernaProject && cd $_
git init
lerna init
git add .
git commit -m "Initial Commit"
git remote add origin http://github.com/renbuzhudek/lernaProject .git
git push -u origin master
上述命令执行完成后,生成如下目录:
packages/
package.json
lerna.json
2. 新建两个模块
为了演示方便,我们新建两个模块, moduleA和moduleB, 并让moduleA依赖moduleB:
lerna create module-a
lerna create module-b
# 将本地包链接起来,可以直接引用
lerna add module-b --scope=module-a
修改module-b 的入口文件:
module.exports = moduleB;
function moduleB() {
return "hello world";
}
修改module-a 的入口文件:
const moduleB = require('module-b');
const moduleA = function() {
console.log(moduleB());
}
module.exports = moduleA;
moduleA()
node调用模块a:
3. 发布新模块
完成修改后,git提交完代码,我们就可以直接发布新的模块,记得要先登录npm源
然后运行下面命令,根据提示输入版本号等,lerna会自动帮我们给包加上tag,并上传到对应的仓库中去。
lerna publish
依赖包的值可以提供一个url
会下载到node_modules里面
作用:内网部署npm镜像时,可用于下载内部包