NPM 包引用踩坑

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
              会出错的代码如下:

              解决方法

              解决方法 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 来保留安装信息

                                    参考

                                    • Nodejs 模块加载
                                      • Are Node.js modules singletons?

                                        © Jiyu Shao 2018 - 2025