NPM 包引用踩坑
问题
已知在项目开发过程中, 项目 P 和依赖的模块 M 都用到了另一个模块 M2.
有以下几种情况:
-
M2 只安装在项目 P(模块提升): 在项目 P 和模块 M 中引用的模块 M2 是同一个文件, 获得的是同一个对象(单例)
-
M2 安装在项目 P 中和模块 M 中: 这种情况下在项目 P 中和模块 M 其实引用到了不同的文件, 获得的是不同的对象(非单例)
在文章后提供了相关源代码, 两个项目中的 package.json 都是一样的, 不一样的只是 package-lock.json
复现
已知 @fe/framework 依赖最新的 @fe/config 项目
-
开一个新的项目, 安装
@fe/framework, 此时项目的node_modules里 @fe 下面应该有两个包, 分别是config和framework -
安装
@fe/config的降级版本, 此时会在项目的 node_module 下安装@fe/config的降级包; 因为@fe/framework的包仍旧依赖最新版本, 所以会在它的里面也会生成node_modules, 并且会安装@fe/config的最新包; 此时在项目和@fe/framework中引用到的config包是不一样的, 所以不是同一个单例, 配置会出错 -
就算现在更新项目中的
@fe/config到最新版本(即和@fe/framework中保持一致), 仍旧是引用的不同的单例, 也会出现错误 -
此时删除项目中的
@fe/config依赖并重新安装, 只是删除项目中的@fe/config而已, 所以会导致项目中找不到config包
会出错的代码如下:
export default () => { // 项目引用 config const config = require('@fe/config'); config.load(); console.warn('项目获取配置:', config.get('api')); // @fe/framework 引用 config // 在使用 config.get() 之前必须调用 config.load(), @fe/framework 是不会调用 config.load() // 所以可以根据以下代码是否报错来判断 @fe/config 引用是否为单例 require('@fe/framework/lib/logger'); console.warn('@fe/framework获取配置:', config.get('api')); return true;};
解决方法
解决方法 1
-
确保项目中依赖的
@fe/config版本要满足@fe/framework中依赖的@fe/config版本, 如果可以的话, 删除项目中对@fe/config的依赖 -
删除
@fe/framework, 并且重装@fe/framework即可 -
此时如果执行
npm ls @fe/config命令, 仍应该会显示有两个 @fe/config 包, 但是@fe/framework下的@fe/config应该会显示deduped, 代表已经被去重删除了
结果如下:
➜ test npm ls @fe/config test@1.0.0 /Users/jiyu/Code/personal/test ├── @fe/config@2.5.3 └─┬ @fe/framework@5.3.0-rc.0 └── @fe/config@2.5.3 deduped
解决方法 2
-
确保项目中依赖并安装的
@fe/config版本要满足@fe/framework中依赖的@fe/config版本 -
执行
npm ddp, 来进行去重并上提通用包
结果如下:
➜ test npm ls @fe/config test@1.0.0 /Users/jiyu/Code/personal/test ├── @fe/config@2.5.3 └─┬ @fe/framework@5.3.0-rc.0 └── @fe/config@2.5.3 deduped
建议
锁定 package.json 版本, 然后部署的时候使用 npm ci 来进行安装, 这样的话比较容易统一开发环境和构建环境的 node 环境
结论
-
在第一次安装的情况下,
npm会提升并合并packages的dependency -
但是之后手动安装或者更新了
package.json之后再安装的情况下, 如果同一个版本的dependency不能满足多个引用到它的包的情况下, 还是会分开安装 -
导致不同的包可能引用到不同的
dependency, 会导致版本不一致的问题, 并且会生成不同的单例 -
就算此时撤销更改, 虽然
package.json里看起来和一开始一样, 但是已经有多个dependency分散在不同的包下 -
此时就算版本一致, 但是还是引用的不同的包, 所以会产生多个单例
-
建议使用
package-lock.json来保留安装信息
Discussion
留言与讨论
想法、补充和不同意见都欢迎。