webpack学习-HMR原理

2022-05-24 17:56:322022-05-20 19:38:52

研究一下webpack-dev-server的 HMR 原理

基于 webpack5 单页应用构建流程分析

如何使用

在配置文件中添加

module.exports = {
  devServer: {
    hot: true,
  },
};

后,即可启用webpack-dev-server的 HMR 功能,只是启用,还需要在业务代码中告诉webpack-dev-server如何更新代码

if (module.hot) {
  module.hot.accept(["./content.js"], () => {
    render();
  });
}

这样,在content.js发生改变后,会执行回调函数,即重新render

流程

核心流程总结如下

包括webpack-dev-serverwebpack.HotModuleReplacementPlugin

  1. webpack-dev-server会启动静态文件服务器,用于返回打包后的资源,该服务器使用memory-fs模拟原生文件系统

  2. webpack-dev-server会修改entry,将webpack.HotModuleReplacementPlugin的一些运行时代码注入到 chunk 中,并开启webpackwatch模式

  3. 客户端请求文件后,会和webpack-dev-server建立一个ws服务

  4. 文件发生变动后,webpack-dev-server会重新打包文件,生成两个额外文件:chunkId.hash.hot-update.jsonchunkId.hash.hot-update.js,同时会通过ws向客户端发送消息,告知客户端有最新的代码

  5. 客户端收到消息后先请求chunkId.hash.hot-update.json,然后通过返回值去请求chunkId.hash.hot-update.js

  6. 增量更新客户端代码并重新执行module.hot.accept传递进来的回调函数

其中webpack-dev-serverwebpack.HotModuleReplacementPlugin分别完成的工作包括

webpack-dev-server:

  • 启动一个express服务器,返回打包后的文件

  • 修改compiler.outputFileSystemmemory-fs

  • 建立一个ws服务

  • 修改entry文件,将一些代码添加到 chunk 中(和服务端建立ws,处理各种消息等)

webpack.HotModuleReplacementPlugin:

  • 代码变动后生成chunkId.hash.hot-update.jsonchunkId.hash.hot-update.js

  • 请求新模块,热更代码等

原理

将分为以下几个模块分析

建立 HMR 服务

创建一个 websocket 服务:在代码变动并编译完成后,向客户端发送两个消息{ type: "hash", data: "xxx" }{ type: "ok" }

hmr websocket消息

更新 compiler

webpack-dev-server会更新配置文件中的entry,将webpack-dev-server/lib/client.js打包进 chunk 中

webpack-dev-server/lib/client.js会在客户端执行,并和 HMR Server 建立连接,接收消息

注入运行时

webpack.HotModuleReplacementPlugin会注入一些运行时代码

hmr 运行时

此时,构建产物中即包含了所有运行 HMR 所需的客户端运行时与接口。这些 HMR 运行时会在浏览器执行一套基于 WebSocket 消息的时序框架,如图:

hmr 运行时

增量构建

除注入客户端代码外,webpack.HotModuleReplacementPlugin还会在代码发生变动后生成两个文件chunkId.hash.hot-update.jsonchunkId.hash.hot-update.js,稍后webpack-dev-server会向客户端发送hashok消息

hmr 运行时

应用更新

客户端在接收到webpack-dev-serverws服务发送的hashok消息后,会根据最新的hash获取chunkId.hash.hot-update.json文件,然后根据文件中提及到的chunk,再去请求chunkId.hash-hot.update.js

hmr 运行时

注意,在 Webpack 4 及之前,热更新文件以模块为单位,即所有发生变化的模块都会生成对应的热更新文件; Webpack 5 之后热更新文件以 chunk 为单位,如上例中,main chunk 下任意文件的变化都只会生成 main.[hash].hot-update.js 更新文件。

chunkId.hash-hot.update.js拿到后会将代码更新到__webpack_require__.c中,并且执行module.hot.accept回调

至此,热更新完毕