工作中我们会遇到同时支撑多个vue项目,而且是频繁修改多个项目,我们本地会有clone多个项目并且每个项目有自己的node_modules,导致我们的电脑磁盘爆满,偶尔删一下空出来磁盘空间,过两天又要修改刚删过的文件不得不安装过来;苦–!
找问题的话 就发现我们每个项目去掉node_modules文件以及.git 或者一些其他的依赖后 项目本身源文件大小也就20M,但是加上这些依赖后我们的项目会有500M-600M
再接着找问题的话发现和使用npm或者yarn时的安装的依赖有很大关系,如果你有100个项目使用了某个依赖,就会有100份依赖的副本保存在磁盘上,即使我们删除了部分项目的依赖,只要我们重新安装,我们清除的磁盘就又爆满了;(对于pnpm,依赖项将存储在一个内容可寻址的仓库中上,实现了节约磁盘空间并提升安装速度)
除此之外, Node 的模块机制还有以下缺点:
Node 本身并没有模块的概念, 它在运行时进行查找和加载。你可能在开发环境运行得好好的, 可能到了线上就运行不了了, 原因是一个模块没有添加到 package.json
Node 模块的查找策略非常浪费.(递归遍历所有的项目依赖关系) 这个缺点在大部分前端项目中可以进行优化,比如 webpack 就可以限定只在项目根目录下的 node_modules 中查找, 但是对于嵌套的依赖, 依然需要 2 次以上的查找
node_modules 不能有效地处理重复的包. 两个名称相同但是不同版本的包是不能在一个目录下共存的.
# ① 假设项目依赖a,b,c三个模块, 依赖树为:
├─── a
│ ├── vue@3
├─── b
│ ├──vue@2
├─── c
├──vue@2
# yarn安装时会按照项目被依赖的次数作为权重, 将依赖提升(hoisting),
# 安装后的node_modules结构为:
.
└── node_modules
├── a
│ ├── index.js
│ ├── node_modules
│ │ └── vue # @3
│ └── package.json
├── b
│ ├── index.js
│ └── package.json
├── c
│ ├── index.js
│ └── package.json
└── vue # @2 被依赖了两次, 所以进行提升
# ② 现在假设在①的基础上, 根项目依赖了react@15, 对于项目自己的依赖肯定是要放在node_modules根目录的,
# 由于一个目录下不能存在同名目录, 所以react@16没有的提升机会.
# 安装后node_moduels结构为
└── node_modules
├── a
│ ├── index.js
│ └── package.json # vue@3 提升
├── b
│ ├── index.js
│ ├── node_modules
│ │ └── vue # @2
│ └── package.json
├── c
│ ├── index.js
│ ├── node_modules
│ │ └── vue # @2
│ └── package.json
└── vue # @3
# 上面的结果可以看出, vue@2出现了重复
那么,有没有什么好的方式解决吗?
这里会主要推出以下两位包管理器:
Pnpm 是node.js的包管理工具,然而并不是npm或者yarn的优化兼容模式,通过对node_modules的处理,让模块的管理更加快速和高效。
下面会讲一下pnpm解决npm和yarn的现有存在问题,以及是如何节省磁盘空间和提升安装速度的。
Vue cli已经默认添加了对pnpm的支持,所以项目切换pnpm的方式可以直接切换,支持vue2 vue3:
npm i -g pnpm
rm -rf node_modules
rm -rf yarn.lock
# 安装依赖 第一次安装会耗费一点时间
pnpm install
# 运行服务
pnpm run serve
可以看到pnpm方式安装的依赖,可以看到如下变化:
node_modules
文件,避免项目破坏性升级,向下兼容友好,node_modules
下面不再是眼花缭乱的依赖,而是跟 package.json 声明的依赖基本保持一致node_modules/.pnpm
文件夹下则扁平放着所有项目所有依赖软连,这样的好处可能对于pnpm来说查找更容易pnpm install xxx # 安装包
pnpm update xxx # 更新包
pnpm uninstall xxx # 卸载包
查看更多
Yarn 2 最大的特点是它的 Plug’n’Play 系统(以下简称 PnP)。虽然 Yarn 1 也可以使用 PnP,但是 Yarn 2 默认开启了它。
Pnp的产生也是因为node_modules,它是社区尝试替代node_modules
的一个方案。在第一次安装依赖后,除了yarn.lock
,Yarn 2 还会创建一个.pnp.js
文件。通过这个文件,Yarn 可以主动地告诉 Node.js 应当在哪个具体的位置找到某一个包,而不是让 Node.js 自己尝试在 node_modules 目录中寻找依赖。通过这种方式,Yarn 可以把所有的包以 zip 的格式储存起来,甚至让不同的工程共用同一份依赖文件,从而减少依赖的体积和加载时间。
然而,目前的 Node.js 社区工具几乎都是基于 node_modules 进行开发的。这导致 Yarn PnP 和目前的很多工具都不兼容,即使webpack、rollup等的支持,仍然没有让yarn pnp普及起来。
初始化之前,项目下吧。yarnrc.yml .yarnrc删除
> output
Downloading https://github.com/yarnpkg/yarn/releases/download/v1.22.10/yarn-1.22.10.js...
Saving it into /Users/xiaoshao/Downloads/ROOT/pnpm/vue3-test-pnp/.yarn/releases/yarn-1.22.10.cjs...
Updating /Users/xiaoshao/Downloads/ROOT/pnpm/vue3-test-pnp/.yarnrc...
Done!
> output
Resolving berry to a url...
Downloading https://github.com/yarnpkg/berry/raw/master/packages/berry-cli/bin/berry.js...
Saving it into /Users/xiaoshao/Downloads/ROOT/pnpm/vue3-test-pnp/.yarn/releases/yarn-berry.cjs...
Updating /Users/xiaoshao/Downloads/ROOT/pnpm/vue3-test-pnp/.yarnrc.yml...
Done!
在yarnrc.yml 添加如下配置:
npmAlwaysAuth: true
npmAuthToken: 7BPLCVtdNJLsH7vJT5Cm9g==
npmRegistryServer: "http://192.168.x.x:9999/"
unsafeHttpWhitelist:
- 192.168.1.1
yarnPath: .yarn/releases/yarn-berry.cjs
enableGlobalCache: true
yarn install 开始安装依赖
yarn config
.yarn 目录结构
├── releases
│ └── yarn-berry.js
│—————— .yarnrc.yml
工程根目录下会新增一个.yarn目录及一个.yarnrc.yml的配置文件:
.yarn和.yarnrc.yml都应该提交到仓库
默认情况下完成上述操作后,webpack构建相关项目无法正常运行,需按照下面配置
yarn install pnp-webpack-plugin -D
// vue.config.js
const PnpWebpackPlugin = require('pnp-webpack-plugin')
module.exports = {
configureWebpack: {
resolve: {
plugins: [PnpWebpackPlugin],
},
resolveLoader: {
plugins: [PnpWebpackPlugin.moduleLoader(module)]
}
}
}
# .yarnrc.yml
yarnPath: ".yarn/releases/yarn-berry.js"
# 如果工程跑不起来可以先尝试增加下面的配置:
nodeLinker: "pnp"
pnpMode: "loose"
# 如果仍然跑不起来,可以用下面的配置完全按照以前的依赖按照方式
nodeLinker: "node-modules"
因为Yarn 2通过将所有npm包以zip包的形式存在store
中,以zip包的方式管理依赖,大大减少了文件数量和文件体积,故而使得工程的依赖能够进行版本管理,进而能够完美地实现依赖锁定。
基于上述场景,部分人提出将工程依赖都提交到仓库中了,协作者拉取代码后甚至都不再需要执行依赖安装的步骤。
上面的论题,只能说有一定的好处,但是个人观点更觉得弊大于利。
Yarn 1 的一个很大的问题是yarn.lock
中会出现依赖版本的重复和落后的问题。这个问题曾经造成了一些令人非常疑惑的现象。Yarn 2 原生支持了yarn deque
命令,可以说从根本上解决了这个问题。
同一份代码分别在yarn/yarn2 pnp/pnpm下安装依赖后的大小查看,结果应该很明显
整体使用感受下来,
总结来说:确实能感受到Yarn pnp在处理的精妙之处,目前仍是觉得稍微不稳定,但是感觉仍需要几个小版本升级可能才会真的普及起来;而pnpm已经发展了很多年,也确实在实用性、稳定性上有一定的作用。