前言
关于node包管理
的问题总结,之前都是看很多文章但是没有系统的总结要点,导致很多知识点都忘记了。这篇文章用于总结node包管理的一些问题点。 还有一些关于pnpm的常见问题集也值得看一下
- npm@3为什么需要扁平化
- 扁平化带来的弊端
- pnpm比起npm、yarn的优势是什么
- pnpm/npm下载依赖包结构对比,以及不兼容包结构对比
- pnpm如何清除掉全局无用的包
- 如何将npm迁移到pnpm项目
- 在项目里指定pnpm包管理
- CI环境下的优化
- 幽灵依赖问题
npm dedupe
解决的问题npm pack
打包测试版本的压缩包
npm@3为什么需要扁平化
首先来看看npm@2
有什么问题。该版本采用了嵌套的形式
来解决版本冲突问题。这也导致了新的问题即依赖层级过深
以及依赖项无法复用问题
。如下A\C两个依赖包
都有依赖项B
无法复用
├─ A
├─ node_modules
| ├─ B@1.0.0
└─ C
├─ node_modules
| ├─ B@1.0.0
为了解决npm@2
存在的问题,npm@3
实现了依赖扁平化(也没彻底解决可复用
以及层级深
的问题)。如下A\C两个依赖包
都有依赖项B@1.0.0
而D依赖包
依赖于B@2.0.0
最终构建的结构如下(B依赖那个版本在平层,那个版本在Node_modules里,取决于解析的先后顺序
):
├─ A
├─ B@1.0.0
├─ D@1.0.0
└─ C
├─ node_modules
| ├─ B@2.0.0
扁平化带来的弊端
- 模块可以访问他们不依赖的软件包,幽灵依赖问题
- 扁平化依赖树的算法非常复杂
- 一些依赖包必须复制到项目的node_modules目录里(不兼容时)
- 依赖树的结构不确定(
安装顺序对依赖树影响特别大
)
pnpm的优势是什么
1.解决了幽灵依赖问题
2.速度: pnpm执行的速度更快、3倍的速度,如下图
3.体积: 将包存储在本地磁盘上,在我们创建的项目里使用硬链接的方式,从global store直接链接依赖。(这也是速度快的原因、对于npm和yarn如果有100个项目使用lodash就会有100份lodash拷贝项目目录里)
实际的依赖存储路径可以通过命令pnpm store path
或者查看.modules.yaml
文件。 也可以通过pnpm store
来修改你需要存储依赖的目录地址
执行pnpm install,因为之前装过对应的模块,直接从磁盘里面获取
pnpm/npm下载依赖包结构对比,以及不兼容包结构对比
对于npm@3
之前的包结构存在依赖层级过深
以及包无法被复用 (A、B依赖包,同时依赖C包,无法被复用)
.来看看pnpm
和npm
改进之后的包结构
- pnpm包结构(非展平依赖树,而是依赖包与其依赖项组合在一起,避免了层级过深问题。依赖包与依赖项是通过
符号链接
的形式将他们链接在一起。而node_modules的依赖包的文件是来自内容存储的硬链接
) pnpm
所有依赖的软连接
都放置在node_modules/.pnpm/
中的对应目录. 把依赖包与依赖包
都处于在同一级别避免了循环的软链- 硬链接:
- 符号链接:
下面以express的accepts包结构为例:
.pnpm
└─ node_modules
├─ accepts -> registry.npmmirror.com+accepts@1.3.8
├─ registry.npmmirror.com+accepts@1.3.8
├─ node_modules
| ├─ accepts // 相关内容
| | // 依赖包与其依赖项组合在一起 (以符号链接的形式链接到外层对应的文件,解决了扁平化带来问题.不展开依赖树)
| ├─ negotiator -> registry.npmmirror.com+negotiator@0.6.3
| └─ mime-types -> registry.npmmirror.com+mime-types@2.1.35
└─ registry.npmmirror.com+mime-types@2.1.35
├─ node_modules
| ├─ mime-types // 相关内容
| | // 依赖包与其依赖项组合在一起 (以符号链接的形式链接到外层对应的文件,解决了扁平化带来问题)
| ├─ mime-db -> registry.npmmirror.com+mime-db@1.52.0
...
// 不兼容包 (我在项目里安装accepts@1.0.0版本与上面的1.3.0 不兼容)
.pnpm
├─ registry.npmmirror.com+accepts@1.0.0
├─ registry.npmmirror.com+accepts@1.3.8
...
- npm包结构(扁平化之后)
node_modules
├─ accepts
├─ mime-types
├─ express
...
// 不兼容 (安装accepts1.0.0 和 express => accepts在该包内部)
├─ accepts@1.0.0
├─ express
├─ node_modules
| ├─ accepts@1.3.8
pnpm如何清除掉全局无用的包
使用方法pnpm store prune
。它提供了一种用于删除一些不被全局项目
所引用到的 packages 的功能。假如之前有一个项目引用了lodash@1.0.0,此时将该项目里的lodash更新为1.1.0。那么全局store目录存储的lodash@1.0.0就不再被引用,应该将它移除掉。(节省本地磁盘开销
)
pnpm store prune
如何将npm迁移到pnpm项目
既然pnpm优点这么多,那么如何将已有的npm迁移成pnpm项目也很简单。在项目里执行pnpm import
将npm的lock文件生成pnpm-lock.yaml
文件。重新pnpm install
即可
pnpm import
在项目里指定pnpm包管理
为了规范团队,为了防止开发人员使用其他的包管理器npm/yarn install
。通过配置npx only-allow ~
npx only-allow 包管理器名称
"scripts": {
"preinstall": "npx only-allow pnpm"
}
CI环境下的优化
通过执行pnpm ci
,相对于npm install
命令,他有如下几个不同点:
pnpm ci
要求项目必须存在package-lock.json
或npm-shrinkwrap.json
文件pnpm ci
完全依据package-lock.json
文件安装依赖。保证团队之间使用一致性的依赖版本pnpm ci
完全依据package-lock.json
。因此在安装过程中就不需要解决依赖满足问题
以及构造依赖树
的问题pnpm ci
会先删除项目中的node_module
再安装pnpm ci
无法安装单个依赖包
pnpm ci
如果lock文件与package.json
冲突,则报错pnpm ci
不会更新lock文件
幽灵依赖问题
假设A包依赖于B包,此时我在项目里只安装了A包,但是我可以在模块里B的语法。 具体可以查看该文章
npm install A
├─ A
└─ B
index.ts
import fn from 'B' // 合法的
npm dedupe解决的问题
假设A@1.0.0
版本依赖B@1.0.0
版本、c@1.0.0
版本依赖于B@2.0.0
。在npm中安装顺序对于构建依赖树的影响特别大. 如下A包先更新
├─ A
├─ B@1.0.0
└─ C
├─ node_modules
| ├─ B@2.0.0
紧接着我更新了A@2.0.0
此时依赖于@B2.0.0
.但是由于顶层存在@B1.0.0
那么更新A时它的依赖项B会在A的node_module里,且B@1.0.0并没有被销毁。如下结构:
├─ A
├─ node_modules
| ├─ B@2.0.0
├─ B@1.0.0
└─ C
├─ node_modules
| ├─ B@2.0.0
上面的依赖结构很明显不是我们想要的,我们想要的应该是如下的结构,如何实现呢,执行npm dedupe
即可
├─ A
├─ B@2.0.0
└─ C
npm dedupe减少重复依赖
npm pack打包测试版本的压缩包
有时候我们在开发包的时候,暂时还不想要发布到线上,但是第三方业务有想要先用着,此时就可以通过npm pack
打包一个本地版本的压缩包丢给他们即可。 如果自己本地开发就通过npm link
来实现
pnpm pack
// 在项目中使用
pnpm install ~(压缩包路径)
总结
看了很多文章,到最后都忘记,哈哈哈。
这里强烈推荐看一下国外一个博主出的视频讲解。可以使用YouTube中英文字母谷歌插件
来看视频。
这里推荐一款快速删除node_modules的脚手架支持多包模式下的删除npkill
。