小明同学在做项目的时候遇到了一个问题🤔🤔,用pnpm搭建的monorepo下有三个项目,分别是a、b和sdk,项目a和项目b都依赖sdk,按照monorepo的文档说法,sdk应该提升到根目录的node_modules,但是小明发现自己在根目录的node_modules下根本没找到sdk的影子😮😮这是怎么回事,那到底什么才是monorepo,这个问题显然冲击了小明对monorepo浅薄的理解🤢🤢
于是,小明同学开始狠狠恶补相关概念,势必成为monorepo糕手🕶
什么是monorepo
monorepo
是一种项目代码管理方式 ,单个仓库可包含许多相关但独立的项目,可以由同一个团队或多个团队管理。
monorepo(单仓库)和multirepo(多仓库)的区别
multirepo
又称为polyrepo
,就是每一个package
单独用一个仓库进行管理,而monorepo
将所有package都放在一个仓库进行管理。
好处
一致性:由于所有项目都存储在同一个存储库中,执行代码质量标准和统一风格会更容易。
轻松的补丁管理:由于所有共享依赖项都集中在一个地方,我们可以快速升级所有项目版本。这为我们节省了大量时间,使我们能够进行更令人兴奋的工作。
缺点
冗长的CI/CD流程:所有项目都在同一个存储库中,有的时候需要构建和测试不必要的没有发生任何改变的项目,会导致代码到生产的时间延长。
小明同学:🤔这我也知道monorepo
的思想啊,可是一提到软链接和workspace
就阴暗爬行了🤢
那这就不得不提到pnpm
了
什么是pnpm
performance npm(高性能的npm),官网对它的描述是高速的,节省磁盘空间的包管理工具
好处
节省磁盘空间: 统一安装包到磁盘的某个位置,项目中的node_modules
通过hard-link
的方式链接到实际的安装地址
**提高安装速度:**官网提供的不同包管理在不同场景下的耗时对比如下图所示,原因看链接
创建了一个非扁平的node_modules目录:
扁平结构是安装一个包,这个包所依赖的包将一起被安装到同一级目录下。小明同学运行了一下,仅仅<font style="color:rgb(28, 30, 33);">npm install axios</font>
,展示在node_modules里显得非常多,这是因为axios需要的依赖例如<font style="color:rgb(28, 30, 33);">form-data</font>
也被塞到和axios同级目录了。
而换了pnpm,axios
需要用到的依赖被塞进了.pnpm
文件夹,所以就显得格外简洁了。
当然这些只是表面的东西,除了外观上不同,扁平化和非扁平化还是存在着本质的差异。
扁平结构存在的问题
①扁平化算法复杂,耗时
②依赖本身结构具有不确定性
.pnpm
.pnpm文件夹被称为虚拟存储目录,以平铺的形式储存着所有的包,像上文中有白色特殊标记的axios👉只是一个符号链接,当node.js
解析依赖的时候,它使用的依赖的真实位置在node_modules/.pnpm/
目录下。pnpm
结合了软链和平铺目录去实现构建一个嵌套的结构。
Workspace
pnpm 内置了对单一存储库(也称为多包存储库、多项目存储库或单体存储库)的支持, 你可以创建一个 workspace
以将多个项目合并到一个仓库中。
要想启动pnpm
的workspace
功能,需要工程根目录下存在 配置文件,并且在pnpm-workspace.yaml中指定工作空间的目录。
jsx
packages:
# specify a package in a direct subdir of the root
- 'my-app'
# all packages in direct subdirs of packages/
- 'packages/*'
# all packages in subdirs of components/
- 'components/**'
# exclude packages that are inside test directories
- '!**/test/**'
在设置依赖版本的时候推荐用workspace:*
,这样就可以保持依赖的版本是工作空间里最新版本,不需要每次手动更新依赖版本。当pnpm publish
的时候,会自动将package.json
中的workspace
修正为对应的版本号。
常用monorepo pnpm命令
列出sdk
的源码位置,被monorepo
内部哪些项目引用了
json
pnpm why sdk
取消某个依赖的安装
json
//整体移除
pnpm remove axios
//局部移除
pnpm remove axios --filter @monorepo/a
常用配置
小明同学渐入佳境,感觉自己离问题的答案越来越近了,于是他在官网找到了如下依赖提升的配置
hoist
- 默认值为
true
- 当值为
true
的时候,所有的依赖会被提升到node_modules/.pnpm/node_modules
,使得node_modules
内的所有包都可以访问未列出的依赖项
shamefully-hosit
- 译为丢脸的提升(不是),默认值为false
- 会造成破坏隔离性 的问题:使用
shamefully-hosit
后,pnpm的隔离优势会被部分削弱,可能引入与npm扁平化依赖相同的问题
hoist-pattern
- 默认值:['*']
- 告诉pnpm哪些包应该被提升到
node_modules/.pnpm/node_modules
默认情况下,所有包都被提升
public-hoist-pattern
- 默认值:['eslint ','prettier']
- 不同于hoist-pattern一样提升到虚拟存储目录,
public-hoist-pattern
将匹配的依赖提升至跟模块目录中。
学到这里,小明同学虽然还不知道自己做项目出现问题的原因在哪里,但他已经学会解决了!既然没有提升到根目录,那我就想方设法将共有依赖sdk提升到根目录不就对了?
于是小明同学写下了这段"羞耻"的代码:
yaml
packages:
- "packages/*"
shamefully-hoist: true
这下好了,sdk是顺利找到了,根目录node\_modules
里面瞬间"活跃"起来了😱😱
小明同学:自己小钱包里的钱要是也能这样翻倍就好了😋(不是
这样的处理方式显然不太符合预期和小明对项目设计的要求以及pnpm
本身的隔离特点,如果只是提升sdk这个特殊的依赖该多好呀🤔
问题是解决了,但小明同学心中有一个小小的问题:hoist
默认值为true
了,为什么提升sdk
还需要这么费劲?按理说,monorepo
不应该会将所有的公用依赖提升到根目录node\_modules
嘛,难道是自己的小电脑坏了😰😰
按照小明同学的思路,我们在项目a和项目b都安装了lodash
依赖,可以看出monorepo
确实是提升到了根目录
小明同学:坏了,我的sdk被孤立了,一定是这样😭😭
这里就不得不再说一说workspace
了,当一个依赖是子项目的时候,pnpm会优先将它作为软链接分发给其他子项目。在a和b的package.json
中分别加入如下代码:
json
{
"dependencies": {
"sdk": "workspace:*"
}
}
运行pnm install时,并不会将sdk提升到根目录的node_modules
中,而是为每个依赖它的项目创建一个指向packages/sdk
的符号链接。
还有像这样不会被提升的特殊情况嘛?有的。
- 公共依赖版本必须兼容
- 如果多个子项目依赖同一个包,但版本号不一致,包管理工具不会将该包提升到根目录,而是保留在各自子项目的
node_modules
下。
- 如果某些依赖被声明为
peerDependencies
(而非dependencies
或devDependencies
),它们不会被提升。
学习了上面的内容,已经顺利解决了问题,但出于对monorepo
的好奇,于是小明同学接着了解了一个和monorepo
同样密切相关的工具------lerna
什么是lerna
Lerna是一个用来优化托管在上的多代码库的工作流的一个管理工具,可以让你在主项目下管理多个子项目,从而解决了多个包互相依赖,且发布时需要手动维护多个包的问题。像babel使用的就是lerna进行管理。
lerna的使用
- lerna使用之前需要全局安装lerna工具
json
npm install lerna -g
- 初始化,即便已经有一个仓库了,仍然可以通过
lerna init
去添加lerna
,如果使用workspace,lerna默认会用workspace里的模式,不需要添加其他参数。
json
lerna init
(lerna初始化伴随着git init)
- 创建一个新的由lerna管理的包
json
lerna create @c-demo/cli
- 安装依赖项
- 在最外层公共node_modules增加lodash模块
json
lerna add lodash
- 在指定项目中增加axios模块
json
lerna add axios --scope @c-demo/cli
- 增加内部模块之间的依赖
json
lerna add @c-demo/cli-utils --scope @c-demo/cli
注意,lerna在7.0.0版本中移除了lerna bootstap``lerna add
和lerna link
的命令,因为比起lerna,具备workspace特性的包管理更适合做这些任务。
文档参考:
Legacy Package Management | Lerna
ps: 有关软硬链接我们下次再见~~~