// 接收webpackHotUpdate事件,执行check()
hotEmitter.on("webpackHotUpdate", function(currentHash) {
lastHash = currentHash;
if (!upToDate() && module.hot.status() === "idle") {
log("info", "[HMR] Checking for updates on the server...");
check();
}
});
// check执行module.hot.check(),并检查HMR状态消息,进行冒泡更新,如果冒泡后还找不到需要更新的热更新,则刷新整个页面。
var check = function check() {
module.hot
.check(true)
.then(function(updatedModules) {
if (!updatedModules) {
log("warning", "[HMR] Cannot find update. Need to do a full reload!");
log(
"warning",
"[HMR] (Probably because of restarting the webpack-dev-server)"
);
window.location.reload();
return;
}
if (!upToDate()) {
check();
}
require("./log-apply-result")(updatedModules, updatedModules);
if (upToDate()) {
log("info", "[HMR] App is up to date.");
}
})
.catch(function(err) {
var status = module.hot.status();
if (["abort", "fail"].indexOf(status) >= 0) {
log(
"warning",
"[HMR] Cannot apply update. Need to do a full reload!"
);
log("warning", "[HMR] " + err.stack || err.message);
window.location.reload();
} else {
log("warning", "[HMR] Update failed: " + err.stack || err.message);
}
});
};
title: webpack热更新流程 date: 2018-03-27
What?
webpack热更新,即模块热替换(HMR - Hot Module Replacement),用于在开发过程中,实时预览修改后的页面,无需重新加载整个页面。其主要通过一下几种方式来加快开发速度:
NamedModulesPlugin在热加载时直接返回更新文件名,而不是文件的id。 使用NamedModulesPlugin效果:
不使用NamedModulesPlugin效果:
nodejs API启动devserver
const config = require('./webpack.config.js'); const options = { contentBase: './dist', hot: true, host: 'localhost' };
webpackDevServer.addDevServerEntrypoints(config, options); const compiler = webpack(config); const server = new webpackDevServer(compiler, options);
server.listen(5000, 'localhost', () => { console.log('dev server listening on port 5000'); });
webpack.config.js
HMR的工作原理
先简单了解下webpack的工作原理
核心概念
流程
细说 webpack 之流程篇这篇文章对于webpack的流程说的比较好,文中的webpack整体流程图较为详细的阐述了整个流程。
简单工作流
说完webpack的流程,来了解下HMR的工作流程。 开启HMR后,webpack实际上在我们的bundle中加入了一段小型的HMR执行环境,在编译过程中,这个runtime会在我们的页面中运行。 当编译完成时,webpack也不会结束,而是继续监控整个文件是否修改,一旦有修改,就会去编译那些有修改的模块(不会全部重建),然后HMR找到对应修改的模块,尝试在运行状态下进行更新。 更新时,首先检查更新的模块是否能self-accept,即是否支持热替换。如果没有办法确认自己能否直接更新,那么就往上传,通知那些require这个模块的模块进行更新,这样一层层往上,知道有模块可以accept或者寻找结束,表示热更新失败(可刷新整个页面)。
从应用程序的角度
从编译器的角度
Webpack通过 Manifest 来解析和加载模块,通过使用manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。 那么,对编译器来说,发生HMR时,需要发出更新的请求,以运行之前的版本到新的版本。 生成文件结果:
文件hash值为webpack上次编译后生成的hash值,即未发生修改前的值。 这个过程主要两部分完成:
“h“为新的编译hash值(下一次编译生成文件的hash值) “C”为待更新的chunk目录,有ID和是否更新组成。
// 文件名称及其对应的修改内容 // "./es6/src/javascript/components/deregulation/appeal/appealList.html": // (function(module, exports) {
module.exports = "...";
/***/ })
})
webpack-hot-middleware
同webpack-dev-server,在手动配置了webpack-hot-middleware/client作为入口文件,以及HotModuleReplacementPlugin后,webpack会将这些热更新需要的代码打包。
监听编译并作出响应
在服务初始化和编译后执行打包文件时,会分别初始化服务端和client建立连接的代码。webpack监听到文件改变,会对文件进行重新编译和打包,然后保存在内存中,等待热更新的调用。webpack-dev-server和webpack-hot-middleware通过监听编译事件,来对修改后的文件及时作出响应。
webpack-dev-server
webpack-dev-server在webpack-dev-middleware的基础上,使用websocket(依赖于sockjs实现)进行服务端和浏览器之间的通信。
SockJS是一个浏览器JavaScript库,它提供了一个类似于网络的对象。SockJS提供了一个连贯的、跨浏览器的Javascript API,它在浏览器和web服务器之间创建了一个低延迟、全双工、跨域通信通道。SockJS的一大好处在于提供了浏览器兼容性。优先使用原生WebSocket,如果在不支持websocket的浏览器中,会自动降为轮询的方式。
webpack-dev-server监听webpack编译事件,当编译完成后,通过_sendStatus方法将新的hash值或者对应的错误信息等发送给浏览器。
webpack-dev-server中的client依赖sockjs-client接受到消息后,更新对应的hash值,并执行reloadApp()进行页面的更新。
client接收到发出的webpackHotUpdate 事件,执行module.hot.check来进行更新。(找不到对应更新而回退到浏览器进行更新逻辑也在这一步实现)
webpack-hot-middleware
和使用webpack-hot-middleware不同的事,webpack-hot-middleware使用eventsource来实现客户端和webpack之间的通信。
eventsoure,即使用服务器发送事件 - Server-sent events | MDN,易如它所说:
在浏览器中通过http连接到服务器,使用evtSource接口监听事件流,在服务器以text/event-stream 格式发送事件流。使用的是HTTP协议,单向通信,只能从服务器发送到浏览器中。
首先,服务端初始化evtSource(middleware.js)
然后,客户端处理evtSource(client.js)
processUpdate内执行module.hot.check
模块热更新处理
查看每个模块打包后的代码,我们发现每个模块都初始化了module hot的逻辑。 hotCreateModule主要是通过ajax获取热更新文件的内容,内部包含hotChekc和hotApply两个接口。这两部分为热更新实现的核心。
hotCheck
其中,hotDownloadManifest请求xxx.hot-updata.json文件 hotDownloadUpdateChunk请求xxx.hot-update.js文件,然后将文件作为script插入页面head头中。 下载下来的文件内容大概如下:
执行函数webpackHotUpadate在打包过程中已经注入。
webpackHotUpadate回去调用hotApply逻辑来执行更新 hotApply的代码较长,主要的过程主要是一次次冒泡,找到和当前更新模块有依赖的所有模块,查看子模块和父模块是否接收更新,如果接受,则标记为过期模块,不接受,则一直向上冒泡,直到顶部入口点。然后针对标记的模块进行accept更新处理,并删除原有依赖,建立新的依赖。
业务代码要做的事情
注入module.hot.accept,即可接收热更新。实现无需刷新页面而更新的逻辑都在accept内部实现。
css
对CSS来说,style-loader已经集成了热更新逻辑,本质上是把更新后的样式放在标签内加载
vue
开启热更新后,vue-loader会在每一个vue组件构建的代码都会增加一段hotAPI,本质是运用组件的render方法,重新render组件,实现无刷新更新。 具体可见实现API
react
react-hot-loader,通过运用react的render重新渲染每一个组件
END
最后,完整的细节流程图如下
参考
细说 webpack 之流程篇 | Taobao FED | 淘宝前端团队
webpack-dev-server使用方法,看完还不会的来找我~ - JSer - SegmentFault 思否
手把手深入理解 webpack dev middleware 原理與相關 plugins
模块热替换(Hot Module Replacement)
当年校招时,我就死在这个问题上… - CSDN博客
GitHub - liangklfangl/webpack-hmr: 这篇文章来自于我的github文章全集,欢迎star https://github.com/liangklfangl/react-article-bucket
Webpack HMR 原理解析
Webpack 热更新实现原理分析
by dj