项目中使用了一个npm包a。前几天一直用得好好的,忽然某次拉了别的分支代码后,就出Bug了。javascript
第一反应是别人把这个包的版本变了。查看了下项目的package.json
、package-lock.json
文件,该模块和依赖模块的信息并无改变,node_modules/a
中的版本信息也和package.json
中的对应。java
一会儿没了头绪,只好到node_modules中去调试一下。node
拉到最后看总结 XDwebpack
项目中node_modules
目录以下:web
node_modules │ └───a │ │ index.js | | ... │ │ │ └───node_modules │ │ ... │ └───c | | index.js | | ... │ └───c │ index.js │ ...
从该目录结构中能够发现,模块a的目录下还有一个node_modules
目录,这个目录里放的是模块a的依赖。眼尖的同窗可能发现了,项目自己的node_modules
目录和a模块的node_modules
目录中都有安装了模块c。这是为何呢?npm
缘由有2个:json
咱们的项目中并无直接引用模块c,因此是第二种状况。优化
本节主要解释为何项目没有直接依赖模块c,却会把c安装在项目的node_modules目录下。不感兴趣的同窗能够直接跳过。ui
假设项目依赖了模块a和模块b,模块a依赖模块c的1.0.0版本,模块b依赖模块c的2.0.0版本。spa
在npm2的时候,使用嵌套的方式来安装模块,c模块分别被安装到a和b模块的node_modules目录中。
这种方式虽然简单,可是却会致使node_modules中存在大量相同的模块。想象一下,若是模块a和模块依赖的模块c都是1.0.0版本,使用这种方式就会产生冗余的模块。
到npm3的时候,npm2中产生冗余模块的状况获得改善。npm安装模块时会尽可能把模块安装到最外层的node_modules目录中,让模块可以尽可能被复用。
上述状况的安装模块如图
到node_modules/a/node_modules/c/index.js
中加了一些log,发现竟然没执行!?
到这一步,要么是log的位置没写对,要么是没有引入这个模块。确认了webpack配置中的resolve.mainFields
属性和模块c的package.json
文件信息后,排除了第一种可能。
Tips: resolve.mainFields
属性用来告诉webpack,引入一个npm模块时,如何找到这个模块的入口。
这时候已经有点懵了,引用模块时不是先从当前目录下的node_modules目录中开始一级一级向上查到吗?说到向上查找,那便到上一级的node_modules目录中去试一试。
果真!引用的是最外层node_modules中的模块c!
难道webpack查找模块的方式和Node.js不同吗?仍是由于webpack的某些配置致使的?
使用以下webpack配置来构建,发现并无存在上述问题。
const path = require('path') module.exports = { entry: './src/index.js', output: { filename: 'main.js', path: path.resolve(__dirname, './dist/js') }, };
那接下来只要排查究竟是哪些webpack配置影响到模块检索。查看项目中的webpack配置,和模块检索相关的只有resolve
属性。
const config = { resolve: { modules: [ path.resolve(projectDir, 'src'), path.resolve(projectDir, 'node_modules'), path.resolve(imtPath, 'node_modules'), ], // es tree-shaking mainFields: ['jsnext:main', 'browser', 'main'], alias: {}, extensions: ['.jsx', '.js'], } }
所幸配置很少,对着webpack文档查一下,很快便找到了问题:resolve.modules
中使用了绝对路径。如下为webpack文档原文:
告诉 webpack 解析模块时应该搜索的目录。绝对路径和相对路径都能使用,可是要知道它们之间有一点差别。
经过查看当前目录以及祖先路径(即 ./node_modules, ../node_modules 等等),相对路径将相似于 Node 查找 'node_modules' 的方式进行查找。
使用绝对路径,将只在给定目录中搜索。
上述webpack配置中,path.resolve(projectDir, 'node_modules')
为项目的node_modules目录。这样配置的缘由,是由于想要优化模块检索的速度,结果却致使了这么严重的Bug。
根据webpack文档,就是由于这个绝对路径致使了Bug。那么只要把这个绝对路径换成node_modules
,Bug便解决了。
npm在安装模块时,会优先将包安装在node_modules目录的最外层,除非有冲突才会安装到父模块下的node_modules中。而webpack配置中的resolve.modules
设置成项目node_modules目录的绝对路径时,会致使webpack在查找node_modules目录时,只在最外层目录查找,忽略掉更深层次的同名模块。这与默认的查找策略“优先使用深层模块”相反,致使构建时使用了错误的npm包。