背景
一个新技术的出现必然是为了解决之前的一些痛点和难点的。
当一个Git仓库随着项目的不断迭代发展,这个仓库的体积也会不断的变大变复杂,就会导致一系列的问题,比如构建速度变慢、依赖管理混乱等,如果不加以干预,最终就会变成一个难以维护的巨石应用。
出现巨石应用后,会将项目拆分出来,放到多个子仓库中管理,这种代码管理方式称为Multirepo。但是业务代码越来越多,子仓库也会越来越多,这就会导致多个仓库之间的依赖管理变得非常复杂,而且隔离出去之后代码风格也很难控制、代码的复用也成了一个问题。
所以就有了Monorepo,在同一个仓库中管理多个项目,这样就可以解决多仓库带来的问题。另外可以通过workspace以及changeset来解决单个仓库依赖管理的问题。
可以看到Monorepo实际是在单仓库管理方式的一种改进,所以优点和缺点都很明显:
优点:
- 单个仓库中,代码风格统一,代码复用性高。
- 所有项目的依赖都在顶层,磁盘占用较少(使用pnpm 都在全局)。
- 通过软链接可以方便的本地调试
缺点:
- 单个仓库下,代码权限可能有些问题,因为每个人都能修改。
- 虽然不是构建全部的代码,但是代码量就在那里,初始化时还是很慢
- 多个项目之间依赖管理复杂:可以通过
workspace以及changeset解决
workspace + changeset + Monorepo解决了工程管理的这三个问题:
- 手动
npm link时,是先link到全局,然后再引入到本地的。如果子项目很多,就很麻烦。
workspace帮我们解决了这个麻烦,安装依赖时会自动软链接。
NOTE
yarn workspace是将所有子项目都软链接到根目录的node_modules中。
pnpm workspace是将子项目依赖的本地包软链接到当前node_modules中。
- 子项目的命令执行问题:如果需要同时打包多个子项目,没有统一的命令就会很麻烦,需要一个一个去执行。
workspace提供了统一的执行命令。 另外如果这些子项目之间有强关联的依赖关系,比如A项目需要访问到B项目,这种情况,如果先启动了A项目,就会报错,因为B项目还没有启动。 pnpm workspace可以解决这种问题,按照顺序来执行。
- 版本更新时,对应项目的依赖管理问题:比如A依赖的B,只修改了B,对应的A也应该更新,但是这种依赖关系单纯靠人去判断可能会有遗漏。
changeset可以很好的解决这个问题。通过git diff找到修改的项目,并且会自动找到所有依赖了该项目的项目,自动更新版本号。
方案
现在一般都是workspace+changeset配合monorepo使用的
yarn workspace
使用yarn workspace:
NOTE
需要有项目文件以及package.json文件
假设有以下两个子项目:
-- packages
---- page
---- utils
生成workspace配置:
# 进入子目录中 执行命令 会在根目录的package.json文件中自动生成workspace字段。
npm init -w有了workspace配置,在根目录执行yarn安装依赖时就会将子项目软链接到根目录的node_modules中。
如果需要安装本地依赖,比如在page中安装utils:
yarn workspace page add utils@1.0.0IMPORTANT
特别注意 这里需要指定版本号,否则会从npm仓库中查找下载
如果安装非本地依赖,比如安装typescript:
yarn workspace page add typescriptNOTE
删除依赖只需要将add改为remove即可。
子项目的依赖都是放在根目录的node_modules中,当然package.json中也有会记录。
NOTE
以上命令和直接进入子项目目录执行yarn add命令是一样,也会把依赖安装到根目录
提供这么一个在根目录使用的命令 是为了方便管理,不用频繁的切换目录。
在根目录执行:
yarn workspace page run build
yarn workspaces run build这两个命令是用来执行子项目的命令的,也是为了方便管理,不用频繁的切换目录。
如果想要查看依赖之间的关系,可以使用yarn workspaces info命令。
changeset
如果使用changeset来解决依赖升级后的更新问题:
# 安装changeset
yarn workspace add @changesets/cli -D接着就是使用changeset:
- 初始化:
npx changeset init会在根目录生成一个.changeset文件夹,注意里面有一个config.json文件,这个文件是用来配置changeset的。
- 依赖更新后
npx changeset add当修改完成准备提交的时候,执行这个命令,该命令会要求用户选择更新的包、版本号以及本次更新描述。 输入完成之后会在.changeset中生成一个临时文件,记录了本次更新的相关信息。
NOTE
是根据git记录来找到修改的文件的,所以要有一次commit记录。 并且changeset默认是main分支,如果分支不对 是找不到变化的文件的,只能手动选择。
- 更新项目以及依赖的版本号
npx changeset version输入了相关更新信息之后,执行该命令: 比如A依赖了B,只修改了B,B的版本号会修改,即使没有选中A,也会修改A的版本号(patch版本号1.0.0 -> 1.0.1)。并且都会生成CHANGELOG.md文件。
- 根据用户给的信息来修改相关包以及依赖的版本号,并生成
CHANGELOG.md文件。 - 会自动判断其他项目有没有使用更新的包,如果有的话也会更新版本号。
- 提交发布
git add .
git commit -m "xxx"
npx changeset publish会自动执行git push以及npm publish,而且还会打上tag。
pnpm workspace
使用pnpm workspace时和上述没有什么太大区别,主要是提供的命令使用上有些区别,changeset的使用方式完全相同。
workspace的配置是手动的,只需要指定文件夹即可。 手动创建一个pnpm-workspace.yaml文件,配置如下:
packages:
- 'packages/*'创建好workspace之后,执行pnpm install是不会将子项目软链接到根目录的,只有安装了相应子项目作为依赖时才会软链接到对应项目中。 比如有两个子项目,page和utils。 执行pnpm -F page add utils --workspace,会将utils软链接到page的node_modules中,并且package.json添加以下依赖:
"dependencies": {
"utils": "workspace:^"
}可以看到,utils的版本号是workspace:^,表示依赖的是workspace中的版本号,等发布时会自动替换成对应的实际版本。
如果需要安装通用的依赖,即在根目录安装依赖:
pnpm add typescript -w需要加上-w参数才能安装,否则会有一个警告提示。
也提供了在根目录执行子项目的命令:
pnpm -F cli exec xxx
pnpm -F cli exec npx tsc --init
pnpm -F cli exec pnpm run build
pnpm -r run build区别
可以看到,这两种方式并没有太大的区别,最大的区别就是pnpm workspace可以按照顺序来执行命令。
另外还有一些细微的区别,比如子项目的软链接位置不同。