pnpm: 扁平化的 node_modules 不是唯一的方式

原文链接:Flat node_modules is not the only way[1],2020.05.27,by Zoltan Kochan

pnpm: 扁平化的 node_modules 不是唯一的方式

新用户经常问我关于 pnpm 创建的 node_modules 结构为什么如此奇怪。为什么它不是扁平化的?所有子依赖(sub-dependencies)在哪里?

我假设读者已经熟悉 npm 和 Yarn 创建的扁平化 node_modules。如果你不明白为什么 npm 3 必须从 v3 开始使用扁平化 node_modules,可以在《为什么我们应该使用pnpm?》[2]中找到一些前史。

那么,为什么 pnpm 的 node_modules 与众不同呢?让我们创建两个目录,在其中一个目录中运行 npm add express,在另一个目录中运行 pnpm add express。下面是第一个目录的 node_modules 顶层内容:

.bin
accepts
array-flatten
body-parser
bytes
content-disposition
cookie-signature
cookie
debug
depd
destroy
ee-first
encodeurl
escape-html
etag
express

你可以在这里[3]看到整个目录。

而下面就是通过 pnpm 创建的 node_modules 中所得到的内容:

.pnpm
.modules.yaml
express

你可以在这里[4]查看。

那么所有的依赖项都在哪里呢?node_modules 文件夹中只有一个名为 .pnpm 的文件夹和一个名为 express 的符号链接。我们只安装了 express,所以这也我们在应用程序唯一能够访问的包。

在这里阅读更多关于 pnpm 严格性好处[5]的信息。

让我们来看看 express 里面有什么:

▾ node_modules
  ▸ .pnpm
  ▾ express
    ▸ lib
      History.md
      index.js
      LICENSE
      package.json
      Readme.md
  .modules.yaml

express 没有 node_modulesexpress 的所有依赖在哪里?

「诀窍在于 express 只是一个符号链接」。当 Node.js 解析依赖项时,它使用它们的真实位置,会忽略符号链接。但你可能会问,express 的真实位置在哪里呢?

这里: node_modules/.pnpm/express@4.17.1/node_modules/express[6]

好的,现在我们知道了 .pnpm/ 文件夹的目的。.pnpm/ 将所有包存储在一个扁平化的文件夹结构中,因此每个包都可以在以这种模式命名的文件夹中找到:

.pnpm/<name>@<version>/node_modules/<name>

我们称之为虚拟存储目录(virtual store directory)。

译注:pnpm 的虚拟存储目录最初是 .registry.npmjs.org/<name>/<version>/node_modules/<name> 方式,后来才改成上述结构。因为 .pnpm 具有标识意义,类似于项目中的 .vscode、.github 的作用

这种扁平结构避免了由 npm v2 创建的嵌套 node_modules 引起的长路径问题,但与 npm v3、4、5、6 或 Yarn v1 创建的扁平 node_modules 相比,它又保持了包的隔离性。

现在让我们来看一下 express 的真实位置:

  ▾ express
    ▸ lib
      History.md
      index.js
      LICENSE
      package.json
      Readme.md

这是一个骗局吗?它还缺少 node_modules!pnpm 的 node_modules 结构的第二个技巧是,包的依赖关系与其真实位置所在的目录层级相同。因此,express 的依赖关系不在 .pnpm/express@4.17.1/node_modules/express/node_modules/ 中,而是在 .pnpm/express@4.17.1/node_modules/[7] 中:

▾ node_modules
  ▾ .pnpm
    ▸ accepts@1.3.5
    ▸ array-flatten@1.1.1
    ...
    ▾ express@4.16.3
      ▾ node_modules
        ▸ accepts
        ▸ array-flatten
        ▸ body-parser
        ▸ content-disposition
        ...
        ▸ etag
        ▾ express
          ▸ lib
            History.md
            index.js
            LICENSE
            package.json
            Readme.md

所有 express 的依赖都是指向 node_modules/.pnpm/ 中适当目录的符号链接。将 express 的依赖放在上一级可以避免循环符号链接。

所以你可以看到,尽管 pnpmnode_modules 结构一开始看起来不寻常:

  • 它完全兼容 Node.js
  • 包和它们的依赖关系又能被很好地分组

译注:这就是 pnpm 非常巧思的地方,利用了 Node.js 查找依赖时的寻址策略(查找当前目录或上层目录中的 node_modules 目录,以此类推),配合符号链接:既解决了 npm 扁平化带来的间接依赖暴露问题又做到了依赖分组,同时借助全局存储(global store)大大节省了硬盘空间。

对于具有 peer dependencies 的包,结构稍微复杂些[8],但思路是相同的:使用符号链接创建一个嵌套并具有扁平化的目录结构。

参考资料

[1]

Flat node_modules is not the only way: https://pnpm.io/blog/2020/05/27/flat-node-modules-is-not-the-only-way

[2]

《为什么我们应该使用pnpm?》: https://www.yuque.com/zhangbao/blog/why-should-we-use-pnpm

[3]

在这里: https://github.com/zkochan/comparing-node-modules/tree/master/npm-example/node_modules

[4]

在这里: https://github.com/zkochan/comparing-node-modules/tree/master/pnpm5-example/node_modules

[5]

pnpm 严格性好处: https://www.yuque.com/zhangbao/blog/pnpms-strictness-helps-to-avoid-silly-bugs

[6]

node_modules/.pnpm/express@4.17.1/node_modules/express: https://github.com/zkochan/comparing-node-modules/tree/master/pnpm5-example/node_modules/.pnpm/express@4.17.1/node_modules/express

[7]

.pnpm/express@4.17.1/node_modules/: https://github.com/zkochan/comparing-node-modules/tree/master/pnpm5-example/node_modules/.pnpm/express@4.17.1/node_modules

[8]

结构稍微复杂些: https://pnpm.io/how-peers-are-resolved

原文始发于微信公众号(写代码的宝哥):pnpm: 扁平化的 node_modules 不是唯一的方式

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容