已经更新,移动到 https://rsdoctor.dev/zh/blog/topic/duplicate-pkg-problem
Updated, moved to https://rsdoctor.dev/blog/topic/duplicate-pkg-problem
pnpm 里锁依赖:某一依赖使用某一确定的版本,分为 "子workspace的直接依赖" 和 "第三方包的直接依赖"
直接依赖直接通过 package.json 声明,确定某一版本,无需使用任何 hack
如 某个子 workspace 确定 react 的版本为
{
"name": "a",
"dependencies": {
"react": "18.2.0"
}
}
每个独立的包都有 确定直接依赖
的能力,直接依赖再确定直接依赖的直接依赖,以此递归来组成整个 node_modules
的版本
这里第三方包的直接依赖,相对于 子workspace 来说就是间接依赖
理解了 覆写 package.json 来确定直接依赖的版本
,也可以更改第三方包的直接依赖
以 overrides 为例,pnpm 提供了以 "a>b":"版本号"
的方式来覆写 package.json
{
"pnpm": {
"overrides": {
"react@18.2.0>loose-envify": "1.0.0", // 1
"react>loose-envify": "1.0.0", // 2
"loose-envify": "1.0.0" // 3
}
}
}
1. "react@18.2.0>loose-envify": "1.0.0"
在 "react@18.2.0"
的 package.json
,改写 loose-envify
的版本为 1.0.0,无论 "dependencies" 还是 "devDependencies"
2. "react>loose-envify": "1.0.0"
react@*>loose-envify
在 "react@*"
(所有react版本,react@16 react@17 react@18等) 的 package.json
覆写 loose-envify
的版本为 1.0.0
3. "loose-envify": "1.0.0"
*>loose-envify
在 所有包的 package.json
覆写loose-envify
的版本为 1.0.0,包括所有的子 workspace
(常用于整个 monorepo 某个包确定唯一版本)
但需要注意的是不存在 a>b>c
这种形式
由于每个包只能确定自己的直接依赖,一个包的一个具体版本在 lockfile 中独一份,所以不存在锁间接依赖,和基于某个子 workspace 维度锁依赖
(否则就会造成多分身的问题,不过 pnpm 中 peer 存在多分身,利用此 Hack 来确定间接依赖版本,可见我这个仓库 SoonIter/pnpm-peer-trick)
// package.json
"overrides": {
"react>loose-envify>js-tokens": "8.0.0", // ❌
"react>loose-envify": "1.1.0", // ✅
"loose-envify@1.1.0>js-tokens": "8.0.0",
}
.pnpmfile.cjs 同理, 不过它比 overrides 更加灵活
hooks.readPackage(pkg, context): pkg | Promise<pkg> Allows you to mutate a dependency's package.json after parsing and prior to resolution.
function readPackage(pkg, context) {
if (pkg.name === 'react') {
pkg.dependencies["loose-envify"] = "1.0.0";
}
return pkg;
}
module.exports = {
hooks: {
readPackage,
},
};
依赖层面无法解决子 workspace 维度的锁依赖问题,但是子项目里一个任意规定 resolve 逻辑的 bundler 可以为所欲为
一个包只有一个版本
常用于 子项目维度
因此常被用于解决 react 单例,合并版本减小包体
{
"name": "c",
"dependencies": {
"react": "18.2.0",
"@babel/runtime": "7.21.0"
},
"license": "MIT"
}
const path = require('path');
module.exports = {
//...
resolve: {
alias: {
"@babel/runtime": path.resolve(__dirname, 'node_modules', '@babel/runtime'),
"react": path.resolve(__dirname, 'node_modules', 'react'),
},
},
};