前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >果断放弃npm切换到pnpm--节约磁盘空间(256G硬盘救星)

果断放弃npm切换到pnpm--节约磁盘空间(256G硬盘救星)

作者头像
奋飛
发布2021-09-10 10:11:23
3.2K1
发布2021-09-10 10:11:23
举报
文章被收录于专栏:Super 前端Super 前端

团队成立初期我们采用 npm3 来管理项目依赖,后续我们研发了自己组件库、图表库、工具库,采用了 monorepo 管理,依赖管理也由 npm3 切换成了 yarn(yarn workspace)。不管是 npm3 还是 yarn 都采用扁平化的 node_modules 文件夹方式,以此避免引入层级过深、相同依赖版本重复等问题。

随着公司业务不断壮大,团队支撑的项目越来越多。由于依赖是跟随项目的,导致磁盘空间占用严重。

由于上述原因,开始尝试使用 pnpm 来进行管理。

节约磁盘空间

pnpm 依赖项将存储在一个全局内容可寻址的仓库中(${os.homedir}/.pnpm-store),具体项目中使用依赖采用硬链接方式,而不是进行复制。对于每个模块的每个版本只保留一个副本。如:本地有10个项目依赖相同 vue 版本,如果使用 npm 或 yarn 时本地磁盘需要有 10 个 vue 的副本;而 pnpm 只有1个。

  1. 如果你用到了某依赖项的不同版本,那么只会将有差异的文件添加到仓库(公共仓库)。
  2. 所有文件都会存储在硬盘上的同一位置。 当多个包(package)被安装时,所有文件都会从同一位置创建硬链接,不会占用额外的磁盘空间。 这允许跨项目共享同一版本的依赖。
代码语言:javascript
复制
$ pnpm install

Packages: +1585
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Packages are hard linked from the content-addressable store to the virtual store.
  Content-addressable store is at: /Users/ligang/.pnpm-store/v3
  Virtual store is at:             node_modules/.pnpm
Progress: resolved 1585, reused 1585, downloaded 0, added 1585, done

可以发现:

  • 内容可寻址存储在 /Users/ligang/.pnpm-store/v3
  • 虚拟存储目录 node_modules/.pnpm
  • downloaded 0,这样极大的提升了 install 速度
代码语言:javascript
复制
ll node_modules

lrwxr-xr-x   1 ligang  staff    44B  9  1 17:59 deepmerge -> .pnpm/deepmerge@3.3.0/node_modules/deepmerge
lrwxr-xr-x   1 ligang  staff    72B  9  1 17:59 element-resize-detector -> .pnpm/element-resize-detector@1.2.2/node_modules/element-resize-detector
lrwxr-xr-x   1 ligang  staff    58B  9  1 17:59 element-ui -> .pnpm/element-ui@2.13.1_vue@2.6.12/node_modules/element-ui
lrwxr-xr-x   1 ligang  staff    39B  9  1 17:59 eslint -> .pnpm/eslint@5.16.0/node_modules/eslint

node_modules 目录下的文件全部被软链到了虚拟存储路径下 .pnpm.pnpm/ 以平铺的形式储存着所有的包(格式:.pnpm/@/node_modules/)。.pnpm 目录下的包会硬链到全局仓库中(/Users/ligang/.pnpm-store/v3)。

关于「硬链」、「软链」可以查看上篇博文。

以项目中依赖 element-ui 为例:

代码语言:javascript
复制
cd node_modules

ls -li element-ui
8643474522 lrwxr-xr-x  1 ligang  staff  58  9  1 17:59 element-ui -> .pnpm/element-ui@2.13.1_vue@2.6.12/node_modules/element-ui

ls -li .pnpm/element-ui@2.13.1_vue@2.6.12/node_modules/
8643424956 drwxr-xr-x  13 ligang  staff  416  9  1 17:59 element-ui

node_modules 目录下,element-ui软链到了 .pnpm 对应的目录下 element-ui;.pnpm目录下,element-ui硬链接( link count 13)。

非扁平化的 node_modules 文件夹

回归一下 node_modules 结构历史:

第一阶段:npm@3 之前版本

代码语言:javascript
复制
node_modules
└─ foo
   ├─ index.js
   ├─ package.json
   └─ node_modules
      └─ bar
         ├─ index.js
         └─ package.json
  • 依赖树层级太深,会导致 Windows 上的目录路径过长问题
  • 相同包在不同的依赖项中需要时,会存在多个相同副本

第二阶段:npm@3 版本,扁平化处理

主要是解决上述两个问题

代码语言:javascript
复制
node_modules
├─ foo
|  ├─ index.js
|  └─ package.json
└─ bar
   ├─ index.js
   └─ package.json

第三阶段:pnpm

由于扁平化算法的极其复杂,以及会存在多项目间相同依赖副本的情况。pnpm 在尝试解决这些问题时,放弃了扁平化处理 node_modules 的方式。而是采用 硬链+软链 方式。

代码语言:javascript
复制
node_modules
├─ .pnpm
|  ├─ foo@1.0.0/node_modules/foo
|  |  └─ index.js
|  └─ bar@2.0.0/node_modules/bar
├─ foo -> .pnpm/foo@1.0.0/node_modules/foo
└─ bar -> .pnpm/bar@2.0.0/node_modules/bar

node_modules 根目录中的包只是一个符号链接。require('foo') 将执行 node_modules/.pnpm/foo@1.0.0/node_modules/foo/indexjs 中的文件(这里是硬链接),而不是 node_modules/foo/index.js 中的文件。

好处

这种布局结构的一大好处是只有真正在依赖项中(package.json dependences)的包才能访问。使用扁平化的 node_modules 结构,所有提升的包都可以访问。

npm@3/yarn 采用扁平化的方式管理 node_modules

示例:以 chokidar 为例

代码语言:javascript
复制
"dependencies": {
  "chokidar": "^3.5.2"
}

项目中依赖了 chokidar 用于监听文件夹内容变化,通过 npm 安装后结构

在这里插入图片描述
在这里插入图片描述

依赖包如此之多,正是由于扁平化处理而来。chokidar 依赖包以及其依赖的依赖包都被提取到了一级目录下。这种方式会导致没有明确被依赖的包也可以被引用。

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
const isNumber = require('is-number')
console.log(isNumber(123), isNumber('abc'))

上述可以正常引用到!

采用 pnpm 重新安装

在这里插入图片描述
在这里插入图片描述

执行上面代码,会报错:Error: Cannot find module ‘is-number’

问题

扁平化 node_modules 导致了上述错误。如果存在这种情况,需要切换成 pnpm 我们应该如何处理?

方案1:

通过 pnpm add 添加依赖

方案2:

通过相关 hooks 添加相关的依赖

.pnpmfile.cjs

代码语言:javascript
复制
module.exports = {
  hooks: {
    readPackage: (pkg) => {
      if (pkg.name === "inspectpack") {
        pkg.dependencies['babel-traverse'] = '^6.26.0'
      }
      return pkg
    }
  }
}

方案3:

如果缺少依赖太多,可以使用提升选项。此选项官方不推荐。

代码语言:javascript
复制
pnpm install --shamefully-hoist

由于 cli3 对于 pnpm 支持不够完善(在 cli4 中已完全支持),我们采用了这种方式。 相关 Issue

总结

pnpm 方式的实现精髓

  1. 通过软链的形式,使得 require 可以正常引用;同时对非真正依赖的项目做隔离(避免引用依赖的混乱)
  2. .pnpm 的存在避免了循环引用和层级过深的问题(都在其第一层)
  3. 硬链使得不同项目相同依赖只存在一个副本,减少磁盘空间

参考链接

  • https://www.kochan.io/nodejs/pnpms-strictness-helps-to-avoid-silly-bugs.html
  • https://www.kochan.io/nodejs/why-should-we-use-pnpm.html
  • https://github.com/vuejs/vue-cli/issues/2703
  • https://pnpm.io/zh/faq
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-09-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 节约磁盘空间
  • 非扁平化的 node_modules 文件夹
    • 好处
    • 问题
    • 总结
    • 参考链接
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档