为了前端的深度-webpack热更新

1,925 阅读4分钟

结论

这篇文章就先把结论放在最前面啦,应该有利于阅读体验。

  1. webpack构建的项目,分为server端和client端(也就是浏览器),项目启动时,双方会保持一个socket连接,用来通话。
  2. 当webpack监听到文件改动,server向client发送一个hash值,client保存下来,然后server再发送一个ok消息。client接收到表示知道可以加载更新了,于是调用reloadApp方法。
  3. reloadApp 会调用 check方法,然后check调用hotDownloadManifest方法,这时候会下载我们提到过的json文件。
  4. hotEnsureUpdateChunk完成后调用hotDownloadManifest方法,这个方法会通过jsonp的格式加载新的代码。
  5. 加载完成之后,会有其他的方法对其对比分析,用目前最快的方法去更新文件。

起源

上回书说到git的用法,这篇文章就说下我学习webpack热更新原理的记录。

还是因为上次面试的事,面试官问我是否知道webpack热更新的原理,我回答的是我使用vue-cli3构建项目,初始化的时候,webpack集成了一个可以监听文件变化,并且通知页面刷新的组件,他问能详细点吗,我...

回家后,打开电脑,运行项目,打开浏览器控制台,选择 network ,先清除之前的加载记录。这时候去修改我的项目文件,保存,回来看下控制台,再修改,再回来看下控制台,这个时候让我发现了这个:

1

一个hot-update.json文件和一个hot-update.js文件。有些年头的前端开发者会发现,hot-update.js返回的内容,怎么很像jsonp返回的内容。我突然觉得,我是不是要入门了,怀着激动的心情,我四处探索,现在按正确的时间线梳理一下。

1.保持浏览器控制台,刷新网页,如下图,关键点在于 app.js,hot-update.js,websocket。

2

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回调。

本文结束,这些内容已经有很多人写过啦,本文只是用来记录自己的学习历程和加深自己的印象,如有错误,请指正!

本文参考:

HMR的原理 (特别鸣谢,写的真好)

Webpack的HMR原理分析

Webpack 热更新实现原理分析