上一章重学webpack系列(六) -- webpack的sourceMap实践与原理我们讲解了sourceMap
的的基础实践与原理,这一章我们来谈一谈热更新
。
什么是热更新(HotModuleReplace)
热更新
与自动刷新
有区别吗,肯定是有区别的,自动刷新
我可以简单的说成window.reload()
,这种是能够让用户有体感
的刷新的,热更新
的使命是不刷新
浏览器,自动更新内容的一种方式。换句话说,热更新就是在运行过程中模块的及时变化。
热更新的好处
window.reload(
)之后,整个浏览器需要重新去渲染dom
;数据
、状态
什么的都会被清空,在中后台大量表单
的场景下,这将无疑会带来无数的吐槽
甚至愤怒
,热更新
很好的解决了这个问题。- 针对上一条,我们可以用
额外
的操作把数据,状态写入内存
,等window.reload()
之后,重新读取内存,可以有效的解决这个问题,但是有多余的冗余
操作。
HMR的基础实践
老样子我们还是来看一看webpack.config.js
文件的配置,我们通过配置hot
属性来开启HMR
,不过热更新需要用webpack
内置HotModuleReplaceMentPlugin
来支持。
const webpack = required('webpack');
module.exports = {
...
plugins: [
...
// 支持热更新的插件
new webpack.HotModuleReplaceMentPlugin()
...
]
devServer: {
// 目录
static: {
directory: path.join(__dirname, 'build'),
},
open:'main.html',
// 开启热更新
hot: true
// hotOnly: true
// 启用gzip压缩
compress: true,
// httpServer端口
port: 9000,
proxy: {"/api":{..}},
},
...
}
不过webpack5
中已经内置了这个插件,不需要去配置了。
HMR的使用注意事项
HMR
支持css
模块的热更新,但是不支持js
、模块的热更新
,因为在HMR
中并没有对js
模块做热更新
的处理,css
模块的热更新来自于loader
本身的热更新的支持,css
变化只是替换掉页面上的样式就可以了。
为什么HMR没有对js模块做处理呢?
因为在不同的js
模块当中,模块的导出可能是对象,可能是函数,又可能是字符串,不同的导出产物可能有不同的使用方法,这样的毫无规律可寻的结果导致webpack
无从下手去实现一个通用的HMR
解决方案,但是在框架中,比如React中要求每个模块的导出必须是一个函数或者类,这样就有了统一的规律,所以实现HMR,也就变得实际可作。
如何去处理HMR对js模块的支持呢?
webpack
的HMR
提供了一个hot
对象,对象上的accept
方法能够在HMR
之前,处理模块更新的逻辑。比如我们在编码一个form
的时候,改动了form
的js
逻辑,我们应该这样做。
// 获取到js模块的入口函数
import createForm from './handleForm.js'
// 获取到form
const form = document.querySelector('./handleForm.js')
if(module.hot){
module.hot.accept('form的路径', ()=>{
// 保存当前form的现有内容
const value = form.innerHTML;
// 从nody上移除form
document.body.removeChild(form);
// 更新form位最新的js模块
form = createForm();
// 置入老的form的内容
form.innerHTML = value;
// 插入到document中
document.body.appendChild(form)
})
}
// 处理图片
const img = document.querySelector('img')
import imageUrl from './image.png'
if(module.hot){
module.hot.accept('./image.png', ()=>{
img.src = imageUrl
})
}
- 运行在
HMR
中的错误的处理- 一旦在
HMR
中发生了错误,那么HMR
的功能就会立即失效
,从而导致浏览器自动刷新
,所以同学们在开发过程中也可以关注
一下这个问题。 - 如果不想因为这样的问题发生导致浏览器自动刷新,可以配置
hotOnly:true
,解决这个问题。
- 一旦在
HMR的原理
在重学webpack系列(五) -- webpack的devServer实践与原理中我们已已经讲到了,webpack
监听文件构建,创建httpServer
,与client
建立socket
联系,检测hot && allowToHot
、liveReload && allowToLiveReload()
,执行热更新或者直接本地更新,本地浏览器刷新我们不谈,我们看一看热更新
是怎么做的。
// node_modules/webpack/hot/dev-server.js
/* globals __webpack_hash__ */
if (module.hot) {
var lastHash; // 每一次传入的hash是一个全局对象,此用来记录上一次的hash,以作比较
// 根据文件hash来判定是不是需要更新
var upToDate = function upToDate() {
return lastHash.indexOf(__webpack_hash__) >= 0;
};
var log = require("./log");
//
var check = function check() {
module.hot
.check(true) // 断点走向webpack\lib\hmr\HotModuleReplacement.runtime.js
.then(function (updatedModules) {
// HMR失效,自动走浏览器刷新
if (!updatedModules) {
log(...)
window.location.reload();
return;
}
// 不需要更新文件,走一下个文件的check
if (!upToDate()) {
check();
}
// ***引入更新函数,执行更新***
require("./log-apply-result")(updatedModules, updatedModules);
if (upToDate()) {
log("info", "[HMR] App is up to date.");
} // 更新完成
})
.catch(function (err) {
// 异常处理,直接刷新浏览器
...
window.location.reload();
});
};
var hotEmitter = require("./emitter");
// 添加webpackHotUpdate事件,更新中执行。
hotEmitter.on("webpackHotUpdate", function (currentHash) {
lastHash = currentHash;
// 检查server的hash与client的_webpack_hash_是否一致
// 检查是否处于idle(空闲)阶段。才能触发更新
if (!upToDate() && module.hot.status() === "idle") {
log("info", "[HMR] Checking for updates on the server...");
check();
}
});
log("info", "[HMR] Waiting for update signal from WDS...");
} else {
throw new Error("[HMR] Hot Module Replacement is disabled.");
}
上述代码都是devServer
的前戏,我们来看一看具体核心check
函数是怎么做的更新是怎么做的。
// node_modules/webpack/lib/hmr/HotModuleReplacement.runtime.js
...
check: hotCheck,
...
// hotCheck
function hotCheck(applyOnUpdate) {
// 处理状态
if (currentStatus !== "idle") {
throw new Error("check() is only allowed in idle status");
}
// 只有check(true)才会走到这里
return setStatus("check")
// 传入hmrDownloadManifest函数去下载mainfist文件
.then($hmrDownloadManifest$)
.then(function (update) {
...
}
return setStatus("prepare").then(function () {
// 创建更新模块数组
var updatedModules = [];
currentUpdateApplyHandlers = [];
return Promise.all(
Object.keys($hmrDownloadUpdateHandlers$).reduce(function (
promises,
key
) {
// 执行hmrDownloadUpdateHandlers,读取chunk文件,比较文件并完成热更新。
$hmrDownloadUpdateHandlers$[key](
update.c,
update.r,
update.m,
promises,
currentUpdateApplyHandlers,
updatedModules
);
return promises;
},
[])
).then(function () {
return waitForBlockingPromises(function () {
if (applyOnUpdate) {
return internalApply(applyOnUpdate);
} else {
return setStatus("ready").then(function () {
return updatedModules;
});
}
});
});
});
});
}
总结
接着重学webpack系列(五) -- webpack的devServer实践与原理来讲,HMR
的底层原理大致可以用下图表示。
下一章我们将会讲一讲
webpack
的执行机制,顺便看一下loader
、plugin
、HMR
在webpack
流程中,是怎么执行的, 直通车 >>> 重学webpack系列(八) -- webpack的运行机制与工作原理