结论
这篇文章就先把结论放在最前面啦,应该有利于阅读体验。
- webpack构建的项目,分为server端和client端(也就是浏览器),项目启动时,双方会保持一个socket连接,用来通话。
- 当webpack监听到文件改动,server向client发送一个hash值,client保存下来,然后server再发送一个ok消息。client接收到表示知道可以加载更新了,于是调用reloadApp方法。
- reloadApp 会调用 check方法,然后check调用hotDownloadManifest方法,这时候会下载我们提到过的json文件。
- hotEnsureUpdateChunk完成后调用hotDownloadManifest方法,这个方法会通过jsonp的格式加载新的代码。
- 加载完成之后,会有其他的方法对其对比分析,用目前最快的方法去更新文件。
起源
上回书说到git的用法,这篇文章就说下我学习webpack热更新原理的记录。
还是因为上次面试的事,面试官问我是否知道webpack热更新的原理,我回答的是我使用vue-cli3构建项目,初始化的时候,webpack集成了一个可以监听文件变化,并且通知页面刷新的组件,他问能详细点吗,我...
回家后,打开电脑,运行项目,打开浏览器控制台,选择 network ,先清除之前的加载记录。这时候去修改我的项目文件,保存,回来看下控制台,再修改,再回来看下控制台,这个时候让我发现了这个:
一个hot-update.json文件和一个hot-update.js文件。有些年头的前端开发者会发现,hot-update.js返回的内容,怎么很像jsonp返回的内容。我突然觉得,我是不是要入门了,怀着激动的心情,我四处探索,现在按正确的时间线梳理一下。
1.保持浏览器控制台,刷新网页,如下图,关键点在于 app.js,hot-update.js,websocket。
2.打开app.js,很快能找到如下代码
3.修改文件,控制台会变成如下
4.同时页面中也会插入一个script
从以上截图,我先做个推论:wepack在启动的时候开启一个node服务,这个服务通过websocket与浏览器保持持续的通信,在检测到文件发生变化时,服务端向浏览器发送一个json和一个js文件。同时每次服务端发送的消息的 hash 将作为下次 hot-update.json 和 hot-update.js 文件的 hash值。
综合上面的分析,我觉得我已经完全掌握热更新流程,本文结束,嘻嘻睡了...
热更新实现原理分析
上面的推论是错的,也不是完全错,只是细节错了。
一开始我的目标是直接查看webpack的源码,里面有一个hot的文件夹,在大概读了其中代码,只在里面找到了一些log输出和方法,没有找到在哪里调用的。感谢 HMR的原理 这篇文章让我找明了方向。代码是在webpack-dev-server的源码下面= =
下面就开始正文了:
当我们构建项目的时候,webpack-dev-server会为我们自己的js文件包上一层代码,加上两个依赖:
// 这个模块是新加的,我们的入口就是 index,而这里加了一个模块,引用了 index,并且额外加了两行 require
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__("./node_modules/webpack-dev-server/client/index.js?http://localhost:8080");
__webpack_require__("./node_modules/webpack/hot/dev-server.js");
module.exports = __webpack_require__("./src/index.js");
/***/ })
/******/ })
其中client/index.js主要负责建立socket通信,为我们提供一些监听方法等等,dev-server提供热更新的方法。
client/index.js 中有如下代码:
const onSocketMsg = {
hash: function msgHash(hash) { // 在 `hash` 事件触发的时候,把 `hash` 记下来
currentHash = hash;
},
ok: function msgOk() { // `ok` 事件触发的时候,表示server已经便已完成最新代码,
// ...
reloadApp();
}
};
// ...
function reloadApp() {
if (hot) {
log.info('[WDS] App hot update...');
// eslint-disable-next-line global-require
const hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash) // 触发这个事件
}
// ...
}
hot/dev-server.js中:
hotEmitter.on("webpackHotUpdate", function(currentHash) {
lastHash = currentHash;
if (!upToDate() && module.hot.status() === "idle") {
log("info", "[HMR] Checking for updates on the server...");
check(); // 触发check方法
}
});
var check = function check() {
module.hot
.check(true) // 这里的check方法最终进入到webpack\lib\HotModuleReplacement.runtime.js文件中
...
}
HotModuleReplacement.js中有:
function hotCheck(apply) {
...
return hotDownloadManifest(hotRequestTimeout).then(function(update) {
...
{
// 取到了 manifest后,就可以通过jsonp 加载最新的模块的JS代码了
hotEnsureUpdateChunk(chunkId);
}
...
});
}
最终通过jsonp的方式加载,加载js的代码如下:
function hotDownloadUpdateChunk(chunkId) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.charset = "utf-8";
script.src = $require$.p + $hotChunkFilename$;
if ($crossOriginLoading$) script.crossOrigin = $crossOriginLoading$;
head.appendChild(script);
}
到此为止,我们就已经得到了新模块的JS代码了,下面就是调用对应的accpet回调。
本文结束,这些内容已经有很多人写过啦,本文只是用来记录自己的学习历程和加深自己的印象,如有错误,请指正!