Webpack的HMR 运行机制

调试工具

首先利用 Chrome 的 dev-tools 中的 network 观察,代码改变的时候,页面与后端之间发生了什么?示例中的项目代码地址已上传 Github。

页面初始加载

运行 dev 模式,本地打开页面并开启 dev-tools,我们看到除了加载页面所依赖的文件外,多了一个连接__webpack_hmr,这是一个叫做 Server-sent Events 的长连接,主要用于后端不断的向前端推送数据,其原理图如下所示,相关的介绍可以参考这篇文章。

后端每次推送的数据内容主要在 EventStream 中的 Data 字段中,如下图所示:

Data 中存储的是一个对象,对象有 action、hash、modules 等字段。

action:sync 操作;

hash:0c0d327c2abaa1fd4b88,是 bundle 的 hash,因为和产出文件 app.0c0d327c2abaa1fd4b88.js 的内容 hash 值相同;

modules:产出 bundle 中的 module id 和对应的文件地址。

修改代码

修改一处代码,webpack 自动编译后,发现 network 中发生了几处变化,首先是客户端收到后端发出的若干事件。

action:built 操作,通知浏览器 webpack 完成了编译;

hash:最新产出 bundle 的内容 hash 值为215d3b813666fbaea5a3;

modules:bundle 中的模块id 和对应模块的文件地址。

在前端收到 built 事件之后,前端向后端发起了两个请求,请求了 0c0d327c2abaa1fd4b88.hot-update.json 和 0.0c0d327c2abaa1fd4b88.hot-update.js 两个文件,文件的 hash 值正好是未发生修改之前后端发送前端的 bundle hash 值。

json文件的内容:

h:215d3b813666fbaea5a3,bundle 内容的最新 hash 值;

c:"0": true, 表示 bundle id 为 0 的文件被修改了;

js文件的内容:

内容是一个函数,类似 jsonp 的返回形式,也就是页面收到请求后执行了 webpackHotUpdate 函数,对 bundle id 为 0 的文件中的 moudle id 为 11 的模块进行修改。

推测结论

根据上面 network 中的信息,我们可以推测出这个交互过程:

webpack 首次编译时,为前端页面注入后端推送事件监听(event-source)和拉取、更新模块的方法(update-method)的代码,并打包到 bundle 之中;

webpack 进入 watch 模式,在项目代码发生变化的时候重新编译,并将编辑的进展实时通知前端;

将编译产出存放在 dev-server,此处的编译只针对变动的模块,产出应该包含上文中提到的 oldbundlehash.hot-update.json 和 oldbundlehash.hot-update.js 文件;

dev-server 中使用 hot-middleware 中间件向前端发送 built 事件;

前端收到通知后,向后端请求最新的变动文件,请求到的 js 文件通过 script 标签加载后执行(类似 Jsonp),其实就是执行已经预埋到 bundle 中的函数(update-method),从而修改 bundle 文件。

以上过程可以用下图表示:

配置文件

接下来我们从项目的配置文件来验证一下,配置文件主要参考 vue-cli 中的webapck 项目(1.1.2),不同的版本会存在差异。

涉及到 Hot Module Replacement 的地方主要有两处:

1. entry 的配置:

在每个入口 bundle 开头引入了 event-source,即在页面中接收后端发送的事件

2. 插件的配置:

引入 HotModuleReplacementPlugin 插件,将 update-method 的代码打入 bundle

涉及到 Hot Module Replacement 的地方主要有两处:

1. 将 compiler 挂载在 devMiddleware 上:

对编译产出提供静态文件服务

2. 将 compiler 挂载在 hotMiddleware 上:

通知前端 event-source 对象发生了 rebuilt

由配置文件可以基本验证之前通过 network debug 得到的推论,接下来去看一

下官方文档验证一下。

官方文档

官方文档中先是总体介绍了一下 Hot Module Replacement 的基本原理,然后将原理中涉及到几个知识点进行了介绍。

1. 基本原理

webapck 在编译的过程中,将 HMR Runtime 嵌入到 bundle 中;编译结束后,webpack 对项目代码文件进行监视,发现文件变动重新编译变动的模块,同时通知 HMR Runtime,然后 HMR Runtime 加载变动的模块文件,尝试执行热更新操作。更新的逻辑是:先检查模块是否能支持 accept 方法,不支持的话,则冒泡查找模块树的父节点,直到入口模块,accept 方法也就是模块 hot-replace 的 handler。

2. 知识点

(1)compiler

这里的 compiler 也就是指 webapck,主要提供 update 的信息,也就是 update menifest(json 文件格式)和 update chunks(js文件格式);

(2)app

app 也就是指前端页面,app 中的代码主要调用 HMR Runtime 下载最新的模块代码,然后调用 HMR Runtime 执行 update 操作;

(3)HMR Runtime

HMR Runtime 是 webapck 内嵌到前端页面的代码,主要提供来能给个职能 check 和 apply。check 用来下载最新模块代码,runtime 能够接收后端发送的事件和发送请求;apply 用于更新模块,主要将要更新的模块打上 tag,然后调用模块的(也有可能是父模块)的更新 handler 执行更新。

(4)module

HRM是一个可插拔的工具,只能影响包含HMR code的模块。通常情况下,没有必要为每个模块写入HMR code,更新的时候会进行冒泡检查HMR code的是否存在。

根据官方文档的介绍,基本和我们的推论吻合,区别在于官方文档引入了 HMR Runtime 的概念,这个可以看作是推论中的 event-source 和update-method的结合体。

现在大家应该清楚了 webpack的Hot Module Replacement 的基本原理了,官方文档中提到了如何根据最新的模块替换旧模块的方法,这个知识点暂不在本文进行介绍。

如果你对本文有疑问,

或者想和作者进行技术交流,

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180501B1AZAX00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券