关于webpack热更新出现`Nothing hot updated`的解决方案

4,299 阅读5分钟
原文链接: blog.5udou.cn

前言

今天同事反馈为什么我们的项目突然之间不能热更新了?于是我试了一下,咦,果然不能热更新了。然后试了好几个版本,发现原来热更新已经好久都不能使用了。现在是不能回滚到很老的版本的了(之前webpack做过一个很大的配置优化),那么只能在这个新版本下找到问题的根源所在。好在所有的代码都是可以调试的,这也是我喜欢js的一个最大的原因。于是我就撸起袖子开始找问题。

寻找问题根源

从控制台的打印来看:

有一句话引起我的注意:[HMR] Nothing hot updated.

webpack HRM竟然判断我没有热更新的内容,明明我改了其中的一个模块的。

于是我们开始追溯process-update.js文件,找到这个调用:

var result = module.hot.check(false, cb);

发现这里会去校验热更新,当然这个触发是因为__webpack_hmr请求,这是一个websocket.

然后我们继续追踪下去,发现这个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插件的用处。所以以后遇到问题,不要就想着马上谷歌,自己尝试解决问题未尝不是一个更好的解决方案。