![a194d9e553368afd90aa4d2a7eac8743.png](https://img-blog.csdnimg.cn/img_convert/a194d9e553368afd90aa4d2a7eac8743.png)
开发环境下的热更新是 webpack 的一个特色功能,其大大提高了开发者的工作效率 -- 我们修改代码的同时浏览器就可以执行更新操作,且会保留当前状态。本文将讲解 webpack 的热更新原理。
提示
- 建议读者尽量关注热更新的流程,并辅以源码加以理解。下面对于源码的分析只会列出主干部分的内容,具体细节及边界处理感兴趣可自行查阅。
- 另外,webpack 的源码随着版本的更新改动比较大,文末参考资料的第一篇文章是去年十二月份写的,和现在(2020-12)对比有很多代码都发生了巨大的变化,再早一点的教程也是变化很大。但是总体的热更新流程万变不离其宗。
本文的 webpack 配置如下所示:
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require("path");
module.exports = {
entry: {
app: "./src/index.js",
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
title: 'Hot Module Replacement'
})
],
devtool: 'source-map',
devServer: {
hot: true
}
}
index.js
import printMe from './print.js';
function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
element.innerHTML = "hello world!";
btn.innerHTML = 'Click me and check the console!';
btn.onclick = printMe;
element.appendChild(btn);
return element;
}
let element = component(); // 当 print.js 改变导致页面重新渲染时,重新获取渲染的元素
document.body.appendChild(element);
if (module.hot) {
module.hot.accept('./print.js', function () {
console.log('Accepting the updated printMe module!');
document.body.removeChild(element);
element = component(); // 重新渲染页面后,component 更新 click 事件处理
document.body.appendChild(element);
});
}
print.js
export default function printMe() {
console.log('hello world');
}
直观感受
当我们修改 print.js
的内容时,我们能够看到的是控制台打印的内容更改了,说明热替换一切正常,这很明显。但是我们还可以看看 调试工具 network 选项卡的一些信息,在这里我们可以推测出一个大致的流程。
让我们启动项目,页面初次刷新,我们得到了 app.bundle.js
的入口js文件,值得注意的是,浏览器还和开发服务器保持了一个 websocket 连接,根据传输的信息我们可以肯定这和热更新密切相关。
![31c8fc211986122a205151466508cfb2.png](https://img-blog.csdnimg.cn/img_convert/31c8fc211986122a205151466508cfb2.png)
现在我们修改 print.js 的内容,然后等待热更新加载。
再次抓包查看,发现更新了三部分内容:
- 一个json文件
![f42d57658676340be11221137afbf776.png](https://img-blog.csdnimg.cn/img_convert/f42d57658676340be11221137afbf776.png)
- 一个js文件
从这个 js 里面我们可以找到我们修改的内容,可以猜出这个文件里面的内容将把旧代码替换。
同时,观察代码发现,返回的内容是一个函数,可知这个js文件可以在适当地位置被执行, 这种通过函数通信的方式也称为 JSONP。
![cfb64472eca523dd61a910c57a95e1a6.png](https://img-blog.csdnimg.cn/img_convert/cfb64472eca523dd61a910c57a95e1a6.png)
- websocket 连接内容的变化
我们可以看到 websocket 的信息也添加了一些内容,而且第一次生成的 hash 值刚好是新请求到的 js 文件的 hash 值。新生成的 hash 值应该是下一次热更新的文件。
![c926c7192768689f38722baf2fd03ad2.png](https://img-blog.csdnimg.cn/img_convert/c926c7192768689f38722baf2fd03ad2.png)
我们不难得出这样的一个大致的流程:
- 热更新通过浏览器和 webpack 开发服务器的 websocket 实现通信。
- 当我们修改文件时,webpack 开发服务器会通过 websocket 通知浏览器需要更新。
- 浏览器知晓需要更新时,利用之前的 hash 值,通过 ajax 请求获取新的 js 文件,然后执行一系列业务逻辑,让新的代码覆盖旧的代码。
接下来我们结合源码,深入分析整个流程。
本地代码监听与编译
我们不难想到,开发环境下一定有一个监听模块,用来监听本地文件的更新情况。这个功能由 webpack-dev-middleware
来实现,来看源码,只列出主干部分。
// webpack-dev-middleware/index.js
module.exports = function wdm(compiler, opts) {
const context = createContext(compiler, options);
// 开始监听
context.watching = compiler.watch(options.watchOptions, (err) => {