NPM 包引用踩坑

Last Edited Time
Feb 12, 2022 09:58 AM
date
Dec 31, 2019
slug
npm-package-dependency
status
Published
tags
NodeJS
NPM
Blog
必读系列
个人笔记
summary
NPM 包引用踩坑总结
type
Post

NPM 包引用踩坑

问题

已知在项目开发过程中, 项目 P 和依赖的模块 M 都用到了另一个模块 M2.
有以下几种情况:
  • M2 只安装在项目 P(模块提升): 在项目 P 和模块 M 中引用的模块 M2 是同一个文件, 获得的是同一个对象(单例)
  • M2 安装在项目 P 中和模块 M 中: 这种情况下在项目 P 中和模块 M 其实引用到了不同的文件, 获得的是不同的对象(非单例)
在文章后提供了相关源代码, 两个项目中的 package.json 都是一样的, 不一样的只是 package-lock.json

复现

已知 @fe/framework 依赖最新的 @fe/config 项目
  1. 开一个新的项目, 安装 @fe/framework, 此时项目的 node_modules 里 @fe 下面应该有两个包, 分别是 configframework
  1. 安装 @fe/config 的降级版本, 此时会在项目的 node_module 下安装 @fe/config 的降级包; 因为 @fe/framework 的包仍旧依赖最新版本, 所以会在它的里面也会生成 node_modules, 并且会安装 @fe/config 的最新包; 此时在项目和 @fe/framework 中引用到的 config 包是不一样的, 所以不是同一个单例, 配置会出错
  1. 就算现在更新项目中的 @fe/config 到最新版本(即和 @fe/framework 中保持一致), 仍旧是引用的不同的单例, 也会出现错误
  1. 此时删除项目中的 @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

  1. 确保项目中依赖的 @fe/config 版本要满足 @fe/framework 中依赖的 @fe/config 版本, 如果可以的话, 删除项目中对 @fe/config 的依赖
  1. 删除 @fe/framework, 并且重装 @fe/framework 即可
  1. 此时如果执行 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

  1. 确保项目中依赖并安装的 @fe/config 版本要满足 @fe/framework 中依赖的 @fe/config 版本
  1. 执行 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 会提升并合并 packagesdependency
  • 但是之后手动安装或者更新了 package.json 之后再安装的情况下, 如果同一个版本的 dependency 不能满足多个引用到它的包的情况下, 还是会分开安装
  • 导致不同的包可能引用到不同的 dependency, 会导致版本不一致的问题, 并且会生成不同的单例
  • 就算此时撤销更改, 虽然 package.json 里看起来和一开始一样, 但是已经有多个 dependency 分散在不同的包下
  • 此时就算版本一致, 但是还是引用的不同的包, 所以会产生多个单例
  • 建议使用 package-lock.json 来保留安装信息

参考