随着 JS 生态的持续发展,NPM 上的包数量已经突破 100W 大关。当前整个前端应用生态已经离不开这些功能各异的 NPM 包了。现在,我们仅仅想运行一个带框架的 HelloWorld Demo,整个的磁盘占用量都在几百M上下,甚至有些会超过 1G(主要是来自 NPM 包),再加上包的下载时间,想想还是有点可怕~

NPM 包依赖的问题,在之前有个有个很严重的问题是层级依赖(嵌套依赖,加上版本兼容性规则叠加的复杂性),在 npm 4.x(大概是 4.x) 的时候通过扁平化 node_modules 进行了缓解(现在不怎么痛了),那么现如今整个依赖臃肿的问题,有哪些方式可以去解呢?

分析 NPM 包依赖太大的问题,核心痛点是:

  1. 依赖太大,导致磁盘占用高(这个还算好,毕竟磁盘不那么值钱)
  2. 依赖太大,初始化时网络开销大,耗时长(时间可值钱了)

从单个仓库视角来看,依赖臃肿,那么很直白的方式就是进行瘦身。从这点思考,比较容易想到如下:

  1. NPM 包优化,保障发布到 NPM 仓库的都是必须内容 - 减小包自身体积
  2. 仓库内依赖包控制,严格控制对依赖包的引用,避免不必要的包依赖 - 精细化控制依赖

从以上两点来看,第一点可以从根源上优化包的体积,所有的依赖方都会收益,但这依赖来个各个地方的开发者去协力做这个事情,难度可见一斑,从实现层面,基本不可行。第二点的话,需要在构建项目的时候,去思考的严控包依赖,会极大的增加研发人员的负担,也难以执行(ROI 不高)。

BTW:从单仓库来说,为了缓解网络开销,还可以直接把 node_modules 目录推送到仓库,更进一步,压缩之后推送到仓库。当然,同样治标不治本,也会增加其他方面的复杂度。

从整个机器视角来看(我们一般会在一台机器上运行 N 个依赖,其中又会存在大量重复的依赖),我们是否可以考虑把依赖项进行中心化,然后我们在运行项目的时候再进行 resolve?
其实这个思路在很多后端语言的包管理上已经用上了,如 Nuget(.Net 的包管理)。在缓解 NPM 的依赖臃肿和下载慢的问题上,多年间也有一些探索:

  1. yarn 的 依赖缓存
  2. npm 6.x(应该是吧)依赖缓存(被 yarn 倒逼)
  3. cnpm 的软链接

这些方案在实际运用层面,都这两个痛点有所缓解,但还不够彻底。接下来来看一种比较彻底的解决办法

yarn PnP

方案易想,但只有落地才能产生价值。PnP 方案思路新么?一点也不,核心来看就是对后端常见的依赖管理机制的复刻,甚至我在几年前也都有想到过这个解决办法(but,不落地都是空谈)

PnP 是什么?

想了解更多,可以查阅:https://classic.yarnpkg.com/en/docs/pnp/

全称 Plug'n'P ,直译就是“即插即用”(BTW:这个直译和实际功能我觉得是有点偏差的,暂且不管直译,先叫 PnP 吧)。核心思路就是在每台机器上,单独创建一个包目录,来管理所有被依赖的 NPM 包,既然是中心化的管理思路,那么一台机器上的包就不再需要重复安装了,项目运行的时候,先对依赖包从这个中心化目录进行 resolve,然后再进行启动:

此处缺一张图

怎么用?

想必看到这个特性,势必会想着赶紧用起来,那么接下来就看看怎么来使用这个:

  1. 首先,我们得先安装 yarn ,执行 npm i -g yarn 即可(至于 node 和 npm 我想不用多说)
  2. 在项目目录下执行 yarn --pnp 来启用 PnP,想了解更多命令,可以执行 yarn -h 查看
  3. pacakge.json 中确保如下配置项存在,则可认为启用了 PnP

    1. // package.json
    2. {
    3. ...
    4. "installConfig": {
    5. "pnp": true
    6. }
    7. }

    注意:

  4. 当前 PnP 特性还不支持 Windows,截至目前(2020年9月5日),在 Windows 上执行,会出现 warning Plug'n'Play on Windows doesn't support the cache and project to be kept on separate drives 异常

  5. PnP 依赖 yarn 支持,所以需要 Yarn 得版本至少 1.12+

运行原理

待补充