什么是HMR
- Hot Module Replacement是指当你对代码修改并保存后,webpack将会对代码进行得新打包,并将新的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,以实现在不刷新浏览器的前提下更新页面。
- 相对于live reload刷新页面的方案,HMR的优点在于可以保存应用的状态,提高了开发效率
流程图
webpack-dev-server如何跑起来的?
- 实例化webpack得到compiler
- 设置compiler
- 将compiler的outputFileSystem设置为memory-fs以提高性能
- 监听compiler.hooks.done钩子
一旦编译成功,通过websocket将stats.hash推送给客户端
- 启动compiler.watch模式
- 监听文件变化,有变化会自动编译
- 设置WebServer
- 实例化express
- 挂载express中间件(以支持memory-fs)
- 创建socket服务器
一旦有client连进来,将最新hash推送给client
主要步骤
1. 服务器&webpack
1.1 webpack工作在watch模式
一旦文件以变化会自动编译,完成后通过触发compiler.hooks.done钩子通知监听者(webpack-dev-server)1.2 server拿到编译后的newHash通知client
2. 浏览器
2.1 client收到newHash
2.2 check && download
prevHash(表示当前的),newHash(最通知收到最新的)根据
prevHash
下载manifest.json描述文件并拿到需要更新的chunkId1
2//`/${prevHash}.hot-update.json`
{"h":"2f46879973b3c1f743f1","c":{"main":true}}根据
prevHash
和chunkId通过jsonp请求要更新的chunkjs文件1
2
3
4
5
6
7
8//`/${chunkId}.${prevHash}.hot-update.js`
webpackHotUpdate("main", {
"./src/title.js": (function (module, __webpack_exports__, __webpack_require__) {
;
__webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = ('hello97');
})
});
2.3 替换要更新的js模块
补丁JS取回来后会调用JsonpMainTemplate.runtime.js
的webpackHotUpdate
方法来完成热替换的使用了
HotModuleReplacementPlugin
后,每个module对象上有个hot对象,主要作用两个:- 注册支持热更新的依赖
- 热更新时child通过parents查找父module的hot对象,并执行热更新依赖
1
2
3
4
5
6
7
8
9// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {},
hot: hotCreateModule(moduleId),
parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
children: []
};
支持热更新的模块需要将子模块提前注册到hot._acceptedDependencies对象上(
作用1
)1
2
3
4
5
6
7
8
9
10
11
12let root = document.getElementById('root');
function render(){
let title = require('./title').default;
root.innerHTML= title;
}
render();
if(module.hot){
//如果title变化 了,会重新调回调函数
module.hot.accept(['./title'],render)
}
//注册后的效果
hot._acceptedDependencies['./src/title.js'] = renderwebpackHotUpdate(子更新了,子通过执行父的hot来reload)(
作用2
)一句话
1
2
31. 新代码包装成module执行并更新到installedModules
2. 通过parents属性找到父模块的hot对象对应热更新依赖的回调并执行(会重新require)
2.1 hot._acceptedDependencies['./src/title.js'] = render详细
1
2
3
41. 根据chunkId从installedModules找到已加载的旧模块,取出parents和children
2. jsonp得到的新代码包裹成一个新模块并执行,更新到installedModules
3. 通过parents属性找到父模块的parentModule.hot._acceptedDependencies[moduleId]热更新依赖并执行
4. 执行callback(render函数)重新require并执行js实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22window.webpackHotUpdate = (chunkId, moreModules) => {
for (let moduleId in moreModules) {
//找到旧的已经加载过的模块
let oldModule = __webpack_require__.c[moduleId];
let { parents, children } = oldModule;
//了出parents和children
//新为新补丁包装一个module
var module = __webpack_require__.c[moduleId] = {
i: moduleId,
l: false, exports: {},
parents, children,
hot: window.hotCreateModule(moduleId)
};
moreModules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
parents.forEach(parent => {
let parentModule = __webpack_require__.c[parent];
parentModule.hot && parentModule.hot._acceptedDependencies[moduleId] && parentModule.hot._acceptedDependencies[moduleId]();
});
hotCurrentHash = newCurrentHash;
}
}
2.4 加载结束
将prevHash置为newHash