npm install 原理
npm 介绍
npm 是一个包管理器也可以说是最大的软件仓库,它让 JavaScript 开发者分享、复用代码更方便
在程序开发中我们常常需要依赖别人提供的框架。这些可以重复的框架代码被称作包(package)或者模块(module)
一个包可以是一个文件夹里放着几个文件,同时有一个叫做 package.json 的文件。
一个网站里通常有几十甚至上百个 package,分散在各处,通常会将这些包按照各自的功能进行划分.
但是如果需要重复造轮子,每个开发者都会疲惫不堪.而 npm 的作用就是提供了开源的库让我们发布、下载 JS 轮子更加方便。
npm install 原理
检查配置: .npmrc
- 手动执行附带参数
npm install --registry=https://registry.npm.taobao.org
- 项目级的 .npmrc 文件
./.npmrc
文件 - 用户级的 .npmrc 文件
路径:npm config get userconfig
设置:config set registry https://registry.npm.taobao.org
- 全局级的 .npmrc 文件
路径:npm config get prefix
$PREFIX/etc/npmrc
设置:config set registry https://registry.npm.taobao.org -g
- npm 内置
npm 内置配置文件 基本用不到
lock 文件有无的区别
无 lock 文件:
有 lock 文件
可以明显看到使用 lock 文件并且和 package 无冲突的时候安装依赖非常的快捷
原因是因为 package-lock.json 中已经缓存了每个包的具体版本和下载链接,不需要再去远程仓库进行查询,然后直接进入文件完整性校验环节,减少了大量网络请求。
npm install/update
如果想更新已安装模块,就要用到 npm update 命令。
1 | npm update <packageName> |
它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。
- 先到远程仓库查询最新版本
- 然后对比本地版本,如果本地版本不存在,或者远程版本较新就会比较版本规则, 否则不更新
- 查看 package.json 中对应的语义版本规则
- 如果当前新版本符合语义规则,就更新,否则不更新
远程版本是否较新是由 npm 模块仓库提供的信息https://registry.npmjs.org/
这个网址后面跟上模块名,就会得到一个 JSON 对象,里面是该模块所有版本的信息。
比如,访问 https://registry.npmjs.org/react
,就会看到 react 模块所有版本的信息。
registry 网址的模块名后面,还可以跟上版本号或者标签,用来查询某个更具体的版本信息
在本地项目时我们可以通过以下命令对某个具体的包进行查询
1 | npm view react |
返回的 JSON 对象里面,有一个 dist.tarball
属性,是该版本压缩包的网址。
到这个网址下载压缩包,在本地解压,就得到了模块的源码。
而 npm install 和 npm update 命令,都是通过这种方式安装模块的。
校验完整性
在下载依赖包之前,我们一般就能通过执行 npm v
命令拿到 npm 对该依赖包计算的 hash
值,紧跟 tarball
(下载链接) 的就是 shasum(hash)
:
1 | npm v react |
用户下载依赖包到本地后,需要确定在下载过程中没有出现错误或被人篡改的情况,
因此需要在本地计算一次整个文件的 hash 值,再与给出shasum
的进行比较.
如果两个 hash 值是相同的,则确保下载的依赖是完整的,反之,则进行重新下载。
缓存
npm install
或 npm update
命令,从 registry 下载压缩包之后,除了将依赖包安装在 node_modules 目录下外, 本地的缓存目录也会储存一份。
通过配置命令,可以查看这个目录的具体位置npm config get cache
;
在 Linux 或 Mac 默认是在用户主目录下的.npm/_cacache
目录, Windows 默认是在%AppData%/npm-cache
。
在这个目录下又存在我们主要关注是两个目录:
- content-v2
用于存储 tar 包的缓存 - index-v5
用于存储 tar 包的 hash。 npm 在执行安装时,可以根据 package-lock.json 中存储的 integrity、version、name 生成一个唯一的 key 然后对应到 index-v5 目录下的缓存记录,从而找到 tar 包的 hash,最后根据 hash 找到缓存的 tar 包直接使用。
我们可以找一个包在缓存目录下搜索测试一下,在 index-v5 搜索一下包路径:
1 | grep https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz -r index-v5 |
1 | { |
上面的 _shasum
属性 1b1b440160a5bf7ad40b650f095963481903930a
即为 tar 包的 hash, hash 的前几位 1b1b 即为缓存的前两层目录,我们进入到 content-v2 进行查看;
果然440160a5bf7ad40b650f095963481903930a
这个压缩后的依赖包确实存在
npm 在执行安装时,可以根据 package-lock.json 中存储的 integrity、version、name 生成一个唯一的 key
对应到 index-v5 目录下的缓存记录,从而找到 tar 包的 hash,然后根据 hash 再去找缓存的 tar 包直接使用。
而这样的缓存策略是从 npm v5 版本开始的,在 npm v5 版本之前,
每个缓存的模块在 ~/.npm 文件夹中以模块名的形式直接存储,储存结构是{cache}/{name}/{version}。
npm 管理缓存数据的命令
npm cache add
: 官方解释说这个命令主要是 npm 内部使用,但是也可以用来手动给一个指定的 package 添加缓存。npm cache clean
:删除缓存目录下的所有数据,为了保证缓存数据的完整性,需要加上 –force 参数。npm cache verify
:验证缓存数据的有效性和完整性,清理垃圾数据。
基于缓存数据,npm 提供了离线安装模式,分别有以下几种:--prefer-offline
:优先使用缓存数据,如果没有匹配的缓存数据,则从远程仓库下载。--prefer-online
:优先使用网络数据,如果网络数据请求失败,再去请求缓存数据,这种模式可以及时获取最新的模块。--offline
:不请求网络,直接使用缓存数据,一旦缓存数据不存在,则安装失败。
npm install 总结
- 检查 .npmrc 文件, 即配置文件
- 无 lock 文件:
- 从 npm 远程仓库获取包信息
- 根据 package.json 构建依赖树,构建过程:
- 构建依赖树时,不管其是直接依赖还是子依赖的依赖,优先将其放置在 node_modules 根目录。
- 当遇到相同模块时,判断已放置在依赖树的模块版本是否符合新模块的版本范围,如果符合则跳过,不符合则在当前模块的 node_modules 下放置该模块。
- 注意这一步只是确定逻辑上的依赖树,并非真正的安装,后面会根据这个依赖结构去下载或拿到缓存中的依赖包
- 在缓存中依次查找依赖树中的每个包
- 不存在缓存:
- 从 npm 远程仓库下载包
- 校验包的完整性
- 校验不通过:
- 重新下载
- 校验通过:
- 将下载的包复制到 npm 缓存目录
- 将下载的包按照依赖结构解压到 node_modules
- 存在缓存:将缓存按照依赖结构解压到 node_modules
- 不存在缓存:
- 将包解压到 node_modules
- 生成 lock 文件
- 有 lock 文件:
- 检查 package.json 中的依赖版本是否和 package-lock.json 中的依赖有冲突。
- 如果没有冲突,直接跳过获取包信息、构建依赖树过程,开始在缓存中查找包信息,后续过程相同
- 有冲突时会按照无 lock 文件的情况进行安装
- 检查 package.json 中的依赖版本是否和 package-lock.json 中的依赖有冲突。
上面的过程简要描述了 npm install
的大概过程,这个过程还包含了一些其他的操作,例如执行定义的一些生命周期函数,我们可以执行 npm install package --timing=true --loglevel=verbose
来查看某个包具体的安装流程和细节
查看流程
npm2
npm 2 在安装依赖包时,采用简单粗暴的递归形式进行安装,
严格按照 package.json 结构以及子依赖包的 package.json 结构将依赖安装到他们各自的 node_modules 中。
直到有子依赖包不在依赖其他模块。
我们可以来实际测试一下
1 | { |
我们的模块 package-analysis 现在依赖了:buffer、ignore 连个模块:
ignore 是一个纯 JS 模块,不依赖任何其他模块,而 buffer 又依赖了下面两个模块:base64-js 、 ieee754。
1 | { |
如此这般 npm install 后,得到的 node_modules 中模块目录结构就是下面这样的:
这样的方式优点很明显:
- node_modules 的结构和 package.json 结构一一对应
- 层级结构明显,并且保证了每次安装目录结构都是相同的
- 在已知所需包名和版本号时,甚至可以从别的文件夹手动拷贝需要的包到 node_modules 文件夹中,再手动修改 package.json 中的依赖配置
- 要删除这个包,也可以简单地手动删除这个包的子目录,并删除 package.json 文件中相应的一行即可
但是,试想一下,如果随着开发的进行依赖的模块愈来愈多,node_modules 将成为一个庞然大物,嵌套层级非常之深和地狱式回调如出一辙:
- 在不同层级的依赖中,可能引用了同一个模块,导致大量冗余。
- 在 Windows 系统中,文件路径最大长度为 260 个字符,嵌套层级过深可能导致不可预知的问题。
npm3
为了解决以上问题,NPM 在 3.x 版本做了一次较大更新。其将 2.x 的嵌套结构改为扁平结构:
安装模块时,不管其是直接依赖还是子依赖的依赖,优先将其安装在 node_modules 根目录。
所以我们执行npm install
将会得到扁平的结构(例子中是将 buffer 的依赖 base64-js,ieee754 进行扁平化处理);
注意此时的 base-js64 的依赖是^1.0.2 而且实际上是 buffer 的依赖被扁平化之后提升到了 node_modules 中的
此时我们如果在模块中又依赖了 base64-js@1.0.1
即依赖了和 buffer (子依赖)中的 base64-js 一样只是版本和 Semver 语法不同;
1 | { |
npm 的安装会有预处理,在预处理时遇到相同模块时(base64-js),会判断已安装的模块版本是否符合新模块的版本范围,如果符合则跳过,不符合则在当前模块的 node_modules 下安装该模块。
此时再执行 npm install 将会得到以下的结构
此时我们模块 package-analysis 的依赖base64-js@1.0.1由于在 buffer 中也被依赖了,而且二者之间没有交集;
因此 base64-js@1.0.1会被安装在根目录,而 buffer 中依赖的base64-js@1.0.2则被安装到了 buffer 的 node_module 中了;
所以如果我们在项目代码中引用了一个模块,模块查找流程如下:
- 在当前模块路径下搜索
- 在当前模块 node_modules 路径下搜素
- 在上级模块的 node_modules 路径下搜索
- …
- 直到搜索到全局路径中的 node_modules
假设我们又依赖了一个包 buffer2@^5.4.3,而它依赖了包 base64-js@1.0.3,则此时的安装结构是下面这样的:
因此 npm 3.x 版本并未完全解决老版本的模块冗余问题,甚至还会带来新的问题。
假设没有 package-analysis 依赖 base64-js@1.0.1 版本,而你同时依赖了依赖不同 base64-js 版本的 buffer 和 buffer2。
由于在执行 npm install 的时候,按照 package.json 里依赖的顺序依次解析,则将会由 buffer 和 buffer2 在 package.json 的放置顺序
决定 node_modules 的依赖结构:
- 先依赖 buffer:
- 先依赖 buffer2:
另外,为了让开发者在安全的前提下使用最新的依赖包,我们在 package.json 通常只会锁定大版本,这意味着在某些依赖包小版本更新后,
同样可能造成依赖结构的改动,依赖结构的不确定性可能会给程序带来不可预知的问题。
package-lock.json 的作用
为了解决 npm install 的不确定性问题(package.json 文件中的语义版本锁定,安装源也不固定),
导致我们在协同开发和线上构建时,不同开发者 npm i
得到的依赖版本可能有一定差异的现象。
所以在 npm 5.x 版本新增了 package-lock.json 文件,而安装方式还沿用了 npm 3.x 的扁平化的方式。
package-lock 是 package.json 中列出的每个依赖项的大型列表,
包含应安装的特定版本,模块的位置(URI),验证模块完整性的哈希,以及依赖项列表等信息。
我们看一个具体的例子
- name: lock 的包名和 package.json 中的 name 保持一致。
- lockfileVersion:
lock 文件的版本在 npm7 中有了显著的改变- 没有值: 来自 NPM v5 之前版本的一个历史深远的 shrinkwrap 版本。
- lockfileVersion@1: npm v5 and v6 执行得到的
- lockfileVersion@2: npm v7 使用的 lockfile 版本,它向后兼容 v1 的 lockfiles。
- lockfileVersion@3: npm v7 创建而成, 不再向后兼容.不维护 v6 的版本后就会在未来使用, 该文件将会被隐藏路径成为
node_modules/.package-lock.json
- version: 指模块的版本 遵循 semver 原则;
- resolved: 模块的下载地址
- integrity: 模块的 hash 值用于校验完整性
- requires: 依赖
如何处理 package-lock.json
对于是否将 package-lock.json 文件上传到远程仓库,也有许多有趣的讨论,
例如 package-lock.json 需要写进 .gitignore 吗?
lock 文件上传到远程仓库时,可能遇到的一些问题和解决方法。
关于旧的 npm 版本的表现:
查阅资料得知,自 npm 5.0 版本发布以来,npm i 的规则发生了三次变化。
- npm 5.0.x 版本,不管 package.json 怎么变,npm i 时都会根据 lock 文件下载
package-lock.json file not updated after package.json file is changed · Issue #16866 · npm/npm
这个 issue 控诉了这个问题,明明手动改了 package.json,为啥不给我升级包!然后就导致了 5.1.0 的问题… - 5.1.0 版本后 npm install 会无视 lock 文件 去下载最新的 npm 然后有人提了这个
issue why is package-lock being ignored? · Issue #17979 · npm/npm
控诉这个问题,最后演变成 5.4.2 版本后的规则。 - 5.4.2 版本后 why is package-lock being ignored? · Issue #17979 · npm/npm
最终才有了 在npm@5.4.2版本后的表现
在npm@5.4.2版本后的表现:
无 package-lock.json
npm i 根据 package.json 进行安装,并生成 package-lock.jsonpackage.json 和 package-lock.json 的版本不兼容
npm i 会以 package.json 为准进行安装,并更新 package-lock.jsonpackage.json 和 package-lock.json 的版本兼容
npm i 会以 package-lock.json 为准进行安装。
npm ci
由于以上 5.4.2 版本的第 1、2 点的存在,即使有 package-lock.json 文件,配合 npm i,
我们也不能保证线上构建时的依赖版本与本地开发时的一致。
npm ci 是类似于 npm i 的命令。npm ci 与 npm i 主要的差异有:
- 使用 npm ci 的项目必须存在 package-lock.json 或 npm-shrinkwrap.json 文件,否则无法执行
- 如果 package-lock.json 或 npm-shrinkwrap.json 中的依赖与 package.json 中不一致(即以上 2 的情况),
npm ci 会报错并退出,而不是更新 lock 文件 - npm ci 只会安装整个项目,无法单独安装某个依赖项目
- 如果项目中已有 node_modules,该文件夹会在 npm ci 执行安装前自动被移除
- 该命令不会写入 package.json 或 lock 文件,安装的行为是完全被固定的。
基于以上几种特性,使用 npm ci 能够有效防止线上构建的依赖与开发者本地不一致的问题。
npm7
近期,组内的小伙伴有遇到如上的问题乍一看不知所云,但是提示却写的非常的明确;
提取到 root 里面的 eslint 依赖是eslint@7.29.0
而内置的 eslint-config-airbnb@18.2.1 却希望宿主的 eslint 版本是 ^5.16.0 || ^6.8.0 || ^7.2.0;
eslint-plugin-react-hooks@1.7.0 希望宿主的 eslint 版本是 ^3.0.0-^6.0.0;
二者产生了冲突从而无法建立依赖数报错;
解决方式也写的比较明确 npm install --force
或者 npm install --legacy-peer-deps
;
经过查证 我们发现
除了一些新特性和不兼容更改之外。与 npm 6 相比,我们对 npm 7 的性能方面产生了一些重要的影响,其中包括:
- 依赖包数量上减少了 54%(npm 7 67 个,npm 6 123 个)
- 代码测试覆盖率增加了 54%(npm 7 94% vs npm 6 77%)
- 因此性能得到了比较大的提升
增加 lockfileVersion@2+ 支持 yarn.lock
Our new package-lock format will unlock the ability to do deterministically reproducible builds and includes
everything npm will need to fully build the package tree. Prior to npm 7 yarn.lock files were ignored, the npm cli
can now use yarn.lock as source of package metadata and resolution guidance.
新的 package 格式会解锁 lock 文件能够进行定性且可执行重复构建的能力, 包含构建时所需要的一切;
该格式也会向后兼容 npm 6 用户
在以前的版本中,yarn.lock 文件被忽略,npm CLI 现在可以使用 yarn.lock 作为 package 元数据和依赖的来源。
如果存在 yarn.lock,则 npm 还将使它与 package 的内容保持最新。
使用 npm 7 并且在有 v1 的 lockfile 的项目中执行 npm install,则会把 lock file 文件的内容取代成 v2 的格式。
如果想避免这种行为,可以通过执行 npm install –no-save
自动安装 peerDependencies
但和上面提到过的问题息息相关的也就只有这个特性了;
npm 7 中引入的一项新功能是自动安装 peerDependencies。在 npm 的之前版本(4-6)中,peerDependencies 冲突会有版本不兼容的警告,
开发人员需要自己管理和安装 peerDependencies, 有冲突仍会安装依赖并不会抛出错误。
但在 npm 7 中,新的 peerDependencies 可确保在 node_modules 树中 peerDependencies 的位置处或之上
找到有效匹配的 peerDependencies。如果存在无法自动解决的依赖冲突,将会阻止安装。
可以通过使用 --force
选项重新安装来绕过冲突,或者选择 --legacy-peer-deps
选项处理 peerDependencies 的依赖关系(类似于 npm 版本 4-6)。
由于许多包都依赖宽松的 peerDependencies 解析,npm 7 将打印警告并解决包依赖树中存在的大多数同级冲突,
因此这些冲突不能手动处理。要在所有层级强制执行严格正确的 peerDependencies 依赖关系,使用--strict-peer-deps
选项。
完全支持 node_modules 内的符号链接
此前,npm 将有向有环的包依赖展开成树结构,导致冗余,也容易出现版本冲突
此版本里,npm 将会把有向有环的包尽可能展平,并使用符号链接形成原有依赖
使用了 @npmcli/arborist 包实现了符号链接和相关算法,性能更好、更可控、更少 bug
使用了 npm link 替代之前 npm install –link
环境变量修改
追加 npmpackage_resolved、npm_package_integrity、npm_command
移除 npm_package、npmconfig
PATH 环境变量将包含全部的 node_modules/.bin 目录
npx 使用 npm exec 进行重构,并内置在新版本中
支持了 acceptDependencies 声明,允许手工声明覆盖一些包的依赖版本
如何解决 package-lock.json 的冲突
package-lock.json 文件不由开发者自行写入,在协同开发时,某一方若更新了依赖,很容易产生大量冲突,且难以逐个解决。
- npm 文档中推荐的两种方式:
从 5.7.0 版本的 npm 开始,解决 package-lock.json 的冲突可以通过解决 package.json 的冲突后,运行 npm install [–package-lock-only] 来完成。npm 会自动解析 package-lock.json 中的冲突,并生成一个有着合理的 tree 结构,且包含了两个分支的所有依赖的 lock 文件。
文档中还提到了一个 npm-merge-driver 工具,可以帮助开发者解决 package-lock.json 的冲突。
- 至于旧版本的 npm,可以尝试对于 package-lock.json 文件,忽略掉冲突,并重新 npm i。
最好将文件重置到非自己开发分支的状态,便于合并之后的验证。
例如,对于将 master 合进自己开发的特性分支的情况(如 git pull origin master),对于冲突,选择 Accept All Incoming。
pnpm
目前 GitHub 已经有近 12k 的 star ,并且已经相对成熟且稳定了。它由 npm/yarn 衍生而来,但却解决了 npm/yarn 内部潜在的 bug,并且极大了地优化了性能,扩展了使用场景
Fast, disk space efficient package manager
因此,pnpm 本质上就是一个包管理器,这一点跟 npm/yarn 没有区别,但它作为杀手锏的两个优势在于:
- 包安装速度极快;
- 磁盘空间利用非常高效。
1 | # node 版本v12.17.0+ |
安装速度快
pnpm 安装包的速度究竟有多快?先以 React 包为例来对比一下:
可以看到,作为黄色部分的 pnpm,在绝多大数场景下,包安装的速度都是明显优于 npm/yarn,速度会比 npm/yarn 快 2-3 倍。
对 yarn 比较熟悉的同学可能会说,yarn 不是有 PnP 安装模式吗?
直接去掉 node_modules,将依赖包内容写在磁盘,节省了 node 文件 I/O 的开销,这样也能提升安装速度。(具体原理见这篇文章(https://loveky.github.io/2019/02/11/yarn-pnp/))
接下来,我们以这样一个仓库为例,我们来看一看 benchmark 数据,主要对比一下 pnpm 和 yarn PnP:
从中可以看到,总体而言,pnpm 的包安装速度还是明显优于 yarn PnP 的。
磁盘使用高效
pnpm 内部使用基于内容寻址的文件系统来存储磁盘上所有的文件,这个文件系统出色的地方在于:
不会重复安装同一个包。用 npm/yarn 的时候,如果 100 个项目都依赖 lodash,那么 lodash 很可能就被安装了 100 次,磁盘中就有 100 个地方写入了这部分代
码。但在使用 pnpm 只会安装一次,磁盘中只有一个地方写入,后面再次使用都会直接使用 hardlink;
即使一个包的不同版本,pnpm 也会极大程度地复用之前版本的代码。举个例子,比如 lodash 有 100 个文件,更新版本之后多了一个文件,那么磁盘当中并不会重新写入 101 个文件,而是保留原来的 100 个文件的 hardlink,仅仅写入那一个新增的文件。
依赖管理
从之前对 npm 发展和遇到的问题 (buffer,buffer2 依赖) 我们可以知道
一些共同依赖的不同版本在 package.json 中的位置会影响最终的构建结果,
这是为什么会产生依赖结构的不确定的问题所在,也是 lock 文件诞生的原因;
但无论是 package-lock.json(npm 5.x 才出现)还是 yarn.lock,都是为了保证 install 之后都产生确定的 node_modules 结构。
尽管如此,npm/yarn 本身还是存在扁平化算法复杂和 package 非法访问的问题,影响性能和安全。
pnpm 的作者Zoltan Kochan
发现 yarn 并没有打算去解决上述的这些问题,于是另起炉灶,写了全新的包管理器,开创了一套新的依赖管理机制.
真正的文件都在.pnpm 中
目录结构都是
.pnpm 目录下虽然呈现的是扁平的目录结构,但仔细想想,顺着软链接慢慢展开,其实就是嵌套的结构!
将包本身和依赖放在同一个 node_module 下面,与原生 Node 完全兼容,又能将 package 与相关的依赖很好地组织到一起,设计十分精妙。
现在来看根目录下的 node_modules 下面不再是眼花缭乱的依赖,而是跟 package.json 声明的依赖基本保持一致。即使 pnpm 内部有一些包会设置依赖提升,被提升到根目录 node_modules 当中,但整体上,根目录的 node_modules 比以前还是清晰和规范了许多。
安全
pnpm 这种依赖管理的方式也很巧妙地规避了非法访问依赖的问题,也就是只要一个包未在 package.json 中声明依赖,那么在项目中是无法访问的
但在 npm/yarn 当中是做不到的,那你可能会问了,如果 A 依赖 B, B 依赖 C,那么 A 就算没有声明 C 的依赖,由于有依赖提升的存在,C 被装到了 A 的 node_modules 里面,那我在 A 里面用 C,跑起来没有问题呀,我上线了之后,也能正常运行啊。不是挺安全的吗?
还真不是。
第一,你要知道 B 的版本是可能随时变化的,假如之前依赖的是C@1.0.1,现在发了新版,新版本的 B 依赖 C@2.0.1,那么在项目 A 当中 npm/yarn install 之后,装上的是 2.0.1 版本的 C,而 A 当中用的还是 C 当中旧版的 API,可能就直接报错了。
第二,如果 B 更新之后,可能不需要 C 了,那么安装依赖的时候,C 都不会装到 node_modules 里面,A 当中引用 C 的代码直接报错。
还有一种情况,在 monorepo 项目中,如果 A 依赖 X,B 依赖 X,还有一个 C,它不依赖 X,但它代码里面用到了 X。由于依赖提升的存在,npm/yarn 会把 X 放到根目录的 node_modules 中,这样 C 在本地是能够跑起来的,因为根据 node 的包加载机制,它能够加载到 monorepo 项目根目录下的 node_modules 中的 X。但试想一下,一旦 C 单独发包出去,用户单独安装 C,那么就找不到 X 了,执行到引用 X 的代码时就直接报错了。
这些,都是依赖提升潜在的 bug。如果是自己的业务代码还好,试想一下如果是给很多开发者用的工具包,那危害就非常严重了。
npm 也有想过去解决这个问题,指定–global-style 参数即可禁止变量提升,但这样做相当于回到了当年嵌套依赖的时代,一夜回到解放前,前面提到的嵌套依赖的缺点仍然暴露无遗。
npm/yarn 本身去解决依赖提升的问题貌似很难完成,不过社区针对这个问题也已经有特定的解决方案: dependency-check,地址: https://github.com/dependency-check-team/dependency-check
但不可否认的是,pnpm 做的更加彻底,独创的一套依赖管理方式不仅解决了依赖提升的安全问题,还大大优化了时间和空间上的性能。
参考文献
链接:一些 package-lock.json 的小知识
链接:https://www.zhihu.com/question/62331583/answer/275248129
https://myan.im/2018/03/24/you-dont-know-npm/index.html
http://www.ruanyifeng.com/blog/2016/01/npm-install.html
https://cloud.tencent.com/developer/article/1555982
https://segmentfault.com/a/1190000013962514
https://www.zhihu.com/question/264560841
npm install xxxx –legacy-peer-deps 到底做了些什么? v7
https://stackoverflow.com/questions/66020820/npm-when-to-use-force-and-legacy-peer-deps
https://stackoverflow.com/questions/66239691/what-does-npm-install-legacy-peer-deps-do-exactly-when-is-it-recommended-wh
https://www.cnblogs.com/zhaohui-116/p/14285015.html
https://segmentfault.com/q/1010000011571000
https://zhuanlan.zhihu.com/p/237532427
https://github.com/rogeriochaves/npm-force-resolutions/issues
https://github.blog/2020-10-13-presenting-v7-0-0-of-the-npm-cli/
npm docs
https://jishuin.proginn.com/p/763bfbd3bcff
http://www.conardli.top/blog/article/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96%EF%BC%88%E4%BA%8C%EF%BC%89package.json%E7%9F%A5%E5%A4%9A%E5%B0%91%EF%BC%9F.html