在有些项目中,我们会遇到某些页面由特别多的模块组成,比如m1,m2,m3...mn
在开发工程中,如果我们修改了其中一个模块,页面都是重新加载,那么之前填写的数据都清空了。测试整体流程又需要重新填写数据,过程比较繁琐。那能否实现某个模块更新而又不清空其他模块的数据呢,其实就是webpack 实现的热更新功能。
定义
热更新(HMR: Hot Module Replacement)指当对代码修改并保存后,webpack将会对代码进行重新打包,并将改动的模块发送到浏览器端,浏览器用新的模块替换掉旧的模块,去实现局部更新页面。我们看下面的一个例子。
与监听代码变更刷新整个页面不同,HMR具有以下两个特征:
- 不需要刷新页面,
- 可以保存应用的当前状态
实现原理
要实现更新,需要解决两个问题,一个是服务端能将更新资源实时推送给浏览器;另一个是浏览器实现局部更新。
1.通知浏览器端文件发生改变
在开发中通常使用的http协议,但是HTTP 协议有一个缺陷:通信只能由客户端发起。我们也可以通过轮询来实现,但是效率低下,而且浪费资源。这个时候 websoket[1] 应运而生。websocket的最大特点是服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,实现双向通信息;在webpack 中,就是使用websocket ,以下是具体实现:
代码语言:javascript复制// server.js
createSocketServer() {
// 实现一个websocket长链接
const io = socket(this.server);
io.on("connection", (socket) => {
console.log("a new client connect server");
this.clientSocketList.push(socket);
socket.on("disconnect", () => {
let num = this.clientSocketList.indexOf(socket);
this.clientSocketList = this.clientSocketList.splice(num, 1);
});
// 向客户端发送最新的一个编译hash
socket.emit('hash', this.currentHash);
// 再向客户端发送一个ok
socket.emit('ok');
});
}
代码语言:javascript复制// client/index.js
const onSocketMessage = {
hash(hash) {
console.log("hash",hash);
currentHash = hash;// 获取最新hash
},
ok() {
console.log("ok");
reloadApp();// 开始热更新
},
connect() {
console.log("client connect successful");
}
};
2.浏览器端实现更新
在浏览器端收到服务端资源更新通知后,会进行以下步骤:
- 使用XHR 拉取更新的模块和文件hash
let hotDownloadManifest = () => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
let hotUpdatePath = `${lastHash}.hot-update.json`
xhr.open("get", hotUpdatePath);
xhr.onload = () => {
let hotUpdate = JSON.parse(xhr.responseText);
// {"h":"58ddd9a7794ab6f4e750","c":{"main":true}}
resolve(hotUpdate);
};
xhr.onerror = (error) => {
reject(error);
}
xhr.send();
})
}
- 根据获取的hash 和chunkId拉取最新的js 代码,此时使用的是JSONP,可以即刻执行js代码。
let hotDownloadUpdateChunk = (chunkID) => {
let script = document.createElement("script")
script.charset = "utf-8";
//chunkID.xxxlasthash.hot-update.js
script.src = `${chunkID}.${lastHash}.hot-update.js`
document.head.appendChild(script);
}
- webpackHotUpdate实现热更新
window.webpackHotUpdate = (chunkID, moreModules) => {
// 循环新拉来的模块
Object.keys(moreModules).forEach(moduleID => {
// 1、通过__webpack_require__.c 模块缓存可以找到旧模块
let oldModule = __webpack_require__.c[moduleID];
// 2、更新__webpack_require__.c,利用moduleID将新的拉来的模块覆盖原来的模块
let newModule = __webpack_require__.c[moduleID] = {
i: moduleID,
l: false,
exports: {},
hot: hotCreateModule(moduleID),
parents: oldModule.parents,
children: oldModule.children
};
// 3、执行最新编译生成的模块代码
moreModules[moduleID].call(newModule.exports, newModule, newModule.exports, __webpack_require__);
newModule.l = true;
// 4、让父模块中存储的_acceptedDependencies执行
newModule.parents && newModule.parents.forEach(parentID => {
let parentModule = __webpack_require__.c[parentID];
parentModule.hot._acceptedDependencies[moduleID] && parentModule.hot._acceptedDependencies[moduleID]()
});
})
}
总结
本文主要讲了热更新的实现要点:使用websoket 进行通信;使用jsonp拉取更新模块执行。如果想了解更多,欢迎读者去阅读相关源码。
[1]阮一峰,WebSocket 教程: http://www.ruanyifeng.com/blog/2017/05/websocket.html
[2]https://zhuanlan.zhihu.com/p/138446061
[3]https://juejin.cn/post/6844904020528594957#heading-59
[4]https://segmentfault.com/a/1190000020310371