本篇研究:使用 pnpm 安装依赖时,node_modules 下是什么结构
回顾
npm@3 之前:依赖树
缺点:
- frequently packages were creating too deep dependency trees, which caused long directory paths issue on Windows
- packages were copy pasted several times when they were required in different dependencies
npm@3 和 yarn:扁平化
缺点:
- modules can access packages they don’t depend on
- the algorithm of flattening a dependency tree is pretty complex
测试 pnpm 效果
yalc
为了方便看效果,我打算自己建几个 npm 包测试。
我并不想真的发布 npm 包,所以使用 yalc 模拟。折腾了一番,成功用yalc 发布、安装 lib 后,发现自己的行为很愚蠢。。。因为使用这个 lib 时,要用 yalc add xxx
来安装,安装的逻辑是 yalc 的逻辑,不是 pnpm 的。所以这种方法不能测试出 pnpm 安装包的效果!STUPID!!!
还是老实发布 lib 吧
publish library
注册
去 npm 官网注册账号
登录
在 cmd 中登录 npm login
。登录失败,报403。
google 后修改 npm registry:npm config set registry http://registry.npmjs.org/
。再次登录仍然失败,报426。
google 后说是 node 版本过老,但是我升到最新版也没用。折腾一番发现是 registry 不能用 http
,要用 https
。修改 npm config set registry https://registry.npmjs.org/
。再次登录,成功。
发布 lib
新建文件夹 lib-c,初始化 npm init -y
新建 index.js:
const str_c = "c";
module.exports = { str_c };
发布时 lib 的名称是由 package.json 中的 name 字段规定的。加个前缀防止重名:
"name": "lilytest-lib-c",
发布 npm publish
按照这个方法创建三个包 a b c并发布,依赖关系为:a依赖b,b依赖c,c无依赖
test-project
新建文件夹 test-project,npm init -y
安装 a pnpm i lilytest-lib-a
:
以 node_modules/lilytest-lib-a
为例说明。它是一个链接,真实路径为 .pnpm/lilytest-lib-a@1.1.0/node_modules/lilytest-lib-a
。使用 dir 可以看到:
安装 a 和 b:
和之前相比,只多了 node_modules/lilytest-lib-b,链接到 .pnpm/lilytest-lib-b@1.1.0/node_modules/lilytest-lib-b
总结
结合官网说明,总结一下 pnpm 的安装流程:
第一步:在 .pnpm 下安装真实依赖
在 .pnpm 下安装所有 package,无论直接、间接、间间接、间间间接、间间间…接的。放在.pnpm/<name>@<version>/node_modules/<name>
中。这些是 node_modules 下唯一的“真实”文件
解释一下:为什么最后还要多套一层 node_modules?
- allow packages to import themselves.
foo
should be able torequire('foo/package.json')
orimport * as package from "foo/package.json".
- avoid circular symlinks. Dependencies of packages are placed in the same folder in which the dependent packages are. For Node.js it doesn’t make a difference whether dependencies are inside the package’s node_modules or in any other node_modules in the parent directories.
第二步:在 .pnpm 下链接依赖
比如,将 .pnpm/a/node_modules 下的 b 链接到 .pnpm/b/node_modules/b:
将 .pnpm/b/node_modules 下的 c 链接到 .pnpm/c/node_modules/c:
第三步:在 node_modules 下链接直接依赖
将 node_modules 下的 a 和 b 链接到 .pnpm 下对应位置
参考阅读:
- Symlinked node_modules structure
old-school webpack
本篇文章的起因就是 webpack 不认 pnpm 的路径,终于说回这个问题了。
webpack Problems with pnpm and the way loaders are resolved
We have issues in lots of applications because webpack can’t resolve packages in the node_modules created by pnpm. I think it is because webpack doesn’t resolve dependencies the way Node does. Node resolves requires from the real path of a module. That is why the symlink approach pnpm uses works with all Node apps. However, it seems to confuse webpack and some other tools like browserify and eslint.