关于webpack热更新出现`Nothing hot updated`的解决方案
Note
本文档针对webpack3以下, webpack4不适用
前言
今天同事反馈为什么我们的项目突然之间不能热更新了?于是我试了一下,咦,果然不能热更新了。然后试了好几个版本,发现原来热更新已经好久都不能使用了。现在是不能回滚到很老的版本的了(之前webpack做过一个很大的配置优化),那么只能在这个新版本下找到问题的根源所在。好在所有的代码都是可以调试的,这也是我喜欢js的一个最大的原因。于是我就撸起袖子开始找问题。
寻找问题根源
从控制台的打印来看:
有一句话引起我的注意:[HMR] Nothing hot updated.
。
webpack HRM竟然判断我没有热更新的内容,明明我改了其中的一个模块的。
于是我们开始追溯process-update.js文件,找到这个调用:
var result = module.hot.check(false, cb);
发现这里会去校验热更新,当然这个触发是因为__webpack_hmr
请求,这是一个SSE协议.
然后我们继续追踪下去,发现这个check会去执行一个请求:
http://127.0.0.1:9093/dist/66262610103662a21e81.hot-update.json
然后我查看了这个请求也是ok的,并且把对应被修改的trunk以及hash值带回来了:
按理说应该没问题呀,都知道哪个chunk更新了呀,于是我们继续跟踪代码,JSON文件请求回来之后调用回调函数,这个时候断点突然打到了这个文件: boostrap xxxxxxxx
很奇怪的一个文件!
但是断点确确实实在这里执行的,诡异的事情发生了:
大家发现没有明明代码上写的是:
var chunkId = 1;
但是chrome却将其变为chunkId = 4,我滴个娘啊,难道是chrome有bug!
但是细细想一下,不可能的呀,别人明明可以用的,肯定是我有什么地方疏忽了!
就因为这个chunkId的值和hot-update.json传回来的chunkId不一样导致代码不会去请求另外一个文件:hot-update.js
,进而导致不会重新渲染组件。
貌似找到问题的根源了,但是为什么这个chunkId是一个错误的值呢?
百思不得其解呀。无奈之下,我就把项目的初始版本拿出来跑,对比一下有什么区别。
这是一种百试不爽的方法,曾经利用这种方法解决了很多稀奇古怪的问题了。
老版本的对比
老版本跑起来之后,随便修改个信息,果不其然是没问题的。于是我去翻看webpack的配置,没发现什么特别的地方。但是在编译完之后发现了一个文件的不同: 新版本的vendor文件大小和老版本的vendor文件大小不一样!
老版本:
vendor-623881.js 726 kB 9 [emitted] [big] vendor
新版本:
vendor-a80884.js 721 kB 9 [emitted] [big] vendor
于是我赶紧打开文件比较器,发现二者还真有不同的地方:
在老版本的最开始地方有这么一段代码:
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [], result;
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/ if(executeModules) {
/******/ for(i=0; i < executeModules.length; i++) {
/******/ result = __webpack_require__(__webpack_require__.s = executeModules[i]);
/******/ }
/******/ }
/******/ return result;
/******/ };
然后在老版本中那段诡异的代码变成了这样:
for(var chunkId in installedChunks)
{ // eslint-disable-line no-lone-blocks
/*globals chunkId */
hotEnsureUpdateChunk(chunkId);
}
看着这样才觉得符合逻辑呀。
在对比的时候还发现了一个让我想通问题的根源的点:
在新版本的出问题的地方vendor.js
的代码是这样的:
/******/ var chunkId = 4;
/******/ { // eslint-disable-line no-lone-blocks
/******/ /*globals chunkId */
/******/ hotEnsureUpdateChunk(chunkId);
/******/ }
这个赋值不就是之前出错的时候chrome调试器打印的值吗?于是我把所有编译出来的chunk文件打开,发现每个chunk文件都有一段几乎一模一样的代码,都是关于hot-module的,唯一不同的是,chunkId
的赋值不同,等于自身的chunkId值。
然后看这些重复代码我就知道了,这些代码执行相互覆盖了!进而导致某个chunkId的文件更新了执行错误的这个hot-module代码。于是检测不到更新!
解决方案
既然知道原因,那么我们根据问题的根源就很容易想到webpack的一个插件: CommonsChunkPlugin。于是我去翻旧版本的webpack配置果然如此,旧版本配置了这个插件,新版本在优化的时候少加了这个插件,而且在vendor入口文件中少了'react-hot-loader/patch'这个入口,这个文件可以让你的热更新生效到react中去。
附上简化的webpack热更新重要的配置:
entry: {
'test': [
'./src/client/test/index.js',
],
'vendor': [
'react-hot-loader/patch',
'webpack-hot-middleware/client?path=http://' + host + ':' + port + "/__webpack_hmr"
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
...
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
}),
]
react-hot-loader
的配置以及用法就不贴了,大家可以参考官网文档。
总结
通过这次问题的解决,个人觉得有的时候很有必要去看看你引用的工具包的代码,看了调试了可以得到更多你不知道的东西,比如这次,你就知道webpack热更新流程,以及commonChunkPlugin插件的用处。所以以后遇到问题,不要就想着马上谷歌,自己尝试解决问题未尝不是一个更好的解决方案。
公众号关注一波~
网站源码:linxiaowu66 · 豆米的博客
Follow:linxiaowu66 · Github
关于评论和留言
如果对本文 关于webpack热更新出现`Nothing hot updated`的解决方案 的内容有疑问,请在下面的评论系统中留言,谢谢。