Webpack HMR 原理全解析

article/2025/10/7 13:10:27

执行 npx webpack serve 命令后,WDS 调用 HotModuleReplacementPlugin 插件向应用的主 Chunk 注入一系列 HMR Runtime,包括:

  • 用于建立 WebSocket 连接,处理 hash 等消息的运行时代码

  • 用于加载热更新资源的 RuntimeGlobals.hmrDownloadManifest 与 RuntimeGlobals.hmrDownloadUpdateHandlers 接口

  • 用于处理模块更新策略的 module.hot.accept 接口

  • 等等

关于 Webpack Runti me,可参考  [Webpack 原理系列六:彻底理解 Webpack 运行时](() 。

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

2.2 增量构建


除注入客户端代码外,HotModuleReplacementPlugin 插件还会借助 Webpack 的 watch 能力,在代码文件发生变化后执行增量构建,生成:

  • manifest 文件:JSON 格式文件,包含所有发生变更的模块列表,命名为 [hash].hot-update.json

  • 模块变更文件:js 格式,包含编译后的模块代码,命名为 [hash].hot-update.js

增量构建完毕后,Webpack 将触发 compilation.hooks.done 钩子,并传递本次构建的统计信息对象 stats。WDS 则监听 done 钩子,在回调中通过 WebSocket 发送模块更新消息:

{“type”:“hash”,“data”:“${stats.hash}”}

实际效果:

2.3 加载更新


客户端接受到 hash 消息后,首先发出 manifest 请求获取本轮热更新涉及的 chunk,如:

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

manifest 请求完成后,客户端 HMR 运行时开始下载发生变化的 chunk 文件,将最新模块代码加载到本地。

2.4module.hot.accept回调


经过上述步骤,浏览器加载完最新模块代码后,HMR 运行时会继续触发 module.hot.accept 回调,将最新代码替换到运行环境中。

module.hot.accept 是 HMR 运行时暴露给用户代码的重要接口之一,它在 Webpack HMR 体系中开了一个口子,让用户能够自定义模块热替换的逻辑。module.hot.accept 接口签名如下:

module.hot.accept(path?: string, callback?: function);

它接受两个参数:

  • path:指定需要拦截变更行为的模块路径

  • callback:模块更新后,将最新模块代码应用到运行环境的函数

例如,对于如下代码:

// src/bar.js

export const bar = ‘bar’

// src/index.js

import { bar } from ‘./bar’;

const node = document.createElement(‘div’)

node.innerText = bar;

document.body.appendChild(node)

module.hot.accept(‘./bar.js’, function () {

node.innerText = bar;

})

示例中,module.hot.accept 函数监听 ./bar.js 模块的变更事件,一旦代码发生变动就触发回调,将 ./bar.js 导出的值应用到页面上,从而实现热更新效果。

module.hot.accept 的作用并不复杂,但使用过程中还是有一些值得注意的点,下面细讲。

2.4.1 失败兜底

module.hot.accept 函数只接受具体路径的 path 参数,也就是说我们无法通过 glob 或类似风格的方式批量注册热更新回调。

一旦某个模块没有注册对应的 module.hot.accept 函数后,HMR 运行时会执行兜底策略,通常是刷新页面,确保页面上运行的始终是最新的代码。

2.4.2 更新事件冒泡

在 Webpack HMR 框架中,module.hot.accept 函数只能捕获当前模块对应子孙模块的更新事件,例如对于下面的模块依赖树:

示例中,更新事件会沿着模块依赖树自底向上逐级传递,从 foo 到 index ,从 bar-1 到 bar 再到 index,但不支持反向或跨子树传递,也就是说:

  • 在 foo.js 中无法捕获 bar.js 及其子模块的变更事件

  • 在 bar-1.js 中无法捕获 bar.js 的变更事件

这一特性与 DOM 事件规范中的冒泡过程极为相似,使用时如果摸不准模块的依赖关系,建议直接在应用的入口文件中编写热更新函数。

2.4.3 无参数调用

除上述调用方式外,module.hot.accept 函数还支持无参数调用风格,作用是捕获当前文件的变更事件,并从模块第一行开始重新运行该模块的代码,例如:

// src/bar.js

console.log(‘bar’);

module.hot.accept();

示例模块发生变动之后,会从头开始重复执行 console.log 语句。

2.5 小结


回顾整个 HMR 过程,所有的状态流转均由 WebSocket 消息驱动,这部分逻辑由 HMR 运行时控制,开发者几乎无感。

唯一需要开发者关心的是为每一个需要处理热更新的文件注册 module.hot.accept 回调,所幸这部分需求已经被许多成熟的 Loader 处理,作为示例,下一节我们挖掘 vue-loader 源码,学习如何灵活使用 module.hot.accept 函数处理文件更新。

三、 vue-loader 如何实现 HMR

========================

vue-loader 是一个用于处理 Vue Single File Component 的 Webp 《大厂前端面试题解析+Web核心总结学习笔记+企业项目实战源码+最新高清讲解视频》无偿开源 徽信搜索公众号【编程进阶路】 ack 加载器,它能够将如下格式的内容转译为可在浏览器运行的等价代码:

除常规的代码转译外,在 HMR 模式下,vue-loader 还会为每一个 Vue 文件注入一段处理模块替换的逻辑,如:

“./src/a.vue”:

/!*****************!\

!*** ./src/a.vue ***!

*******************/

/***/

((module, webpack_exportswebpack_require) => {

// 模块代码

// …

/* hot reload */

if (true) {

var api = webpack_require( /*! …/node_modules/vue-hot-reload-api/dist/index.js */ “…/node_modules/vue-hot-reload-api/dist/index.js”)

api.install(webpack_require( /*! vue */ “…/node_modules/vue/dist/vue.runtime.esm.js”))

if (api.compatible) {

module.hot.accept()

if (!api.isRecorded(‘45c6ab58’)) {

api.createRecord(‘45c6ab58’, component.options)

} else {

api.reload(‘45c6ab58’, component.options)

}

module.hot.accept( /*! ./a.vue?vue&type=template&id=45c6ab58& */ “./src/a.vue?vue&type=template&id=45c6ab58&”, WEBPACK_OUTDATED_DEPENDENCIES => {

/* harmony import */

a_vue_vue_type_template_id_45c6ab58___WEBPACK_IMPORTED_MODULE_0_ = webpack_require( /*! ./a.vue?vue&type=template&id=45c6ab58& */ “./src/a.vue?vue&type=template&id=45c6ab58&”);

(function () {

api.rerender(‘45c6ab58’, {

render: a_vue_vue_type_template_id_45c6ab58___WEBPACK_IMPORTED_MODULE_0_.render,

staticRenderFns: a_vue_vue_type_template_id_45c6ab58___WEBPACK_IMPORTED_MODULE_0_.staticRenderFns

})

})(WEBPACK_OUTDATED_DEPENDENCIES);

})

}

}

// …

/***/

}),


http://chatgpt.dhexx.cn/article/37k7tXGF.shtml

相关文章

vite1.x 热更新(HMR)的实现原理

前言 将近一年前自己尝试阅读vite源码(2.x),虽然也有些收获但整体并没有到达我的预期,对于vite也是停留在一知半解的程度上。最近想重新开始学习vite,但回顾之前的学习历程,感觉不太想继续之前的方式&…

Webpack HMR 原理解析

Hot Module Replacement(以下简称 HMR)是 webpack 发展至今引入的最令人兴奋的特性之一 ,当你对代码进行修改并保存后,webpack 将对代码重新打包,并将新的模块发送到浏览器端,浏览器通过新的模块替换老的模…

Webpack的HMR原理解析

Hot Module Replacement(以下简称 HMR)是 webpack 发展至今引入的最令人兴奋的特性之一 ,当你对代码进行修改并保存后,webpack 将对代码重新打包,并将新的模块发送到浏览器端,浏览器通过新的模块替换老的模…

Esbuild Bundler HMR

Esbuild 虽然 bundler 非常快,但是其没有提供 HMR 的能力,在开发过程中只能采用 live-reload 的方案,一有代码改动,页面就需要全量 reload ,这极大降低开发体验。为此添加 HMR 功能至关重要。 经过调研,社…

Vite HMR

传统webpack的hmr是使用webpack的HotModuleReplacementPlugin,而vite则是采用native ES Module的devServer。 初始化本地服务器加载并运行对应的plugin 最重要的一件事就是运行plugin,目前vite支持的plugin大体如下图所示 1、建立ViteDevServer服务器…

webpack HMR

HMR或者hot模式下,启动webpack会在浏览器与服务器之间会建立一个websocket连接,使得浏览器可以和服务端建立全双工通信;当应用程序的代码更新时,会要求HMR runtime检查更新,有更新时,在websoket连接中会返回…

webpack中的HMR(热更新)原理剖析

简介 Hot Module Replacement(以下简称 HMR)是 webpack 发展至今引入的最令人兴奋的特性之一 ,当你对代码进行修改并保存后,webpack 将对代码重新打包,并将新的模块发送到浏览器端,浏览器通过新的模块替换…

HMR(热替换)

HMR 即模块热替换(hot module replacement)的简称,它可以在应用运行的时候,不需要刷新页面,就可以直接替换、增删模块。webpack 可以通过配置 webpack.HotModuleReplacementPlugin 插件来开启全局的 HMR 能力&#xff…

面试官:说说Webpack的热更新是如何做到的?原理是什么?

一、是什么 HMR全称 Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用 例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整…

Webpack 热更新HMR 原理全解析

一、什么是 HMR HMR 全称 Hot Module Replacement,中文语境通常翻译为模块热更新,它能够在保持页面状态的情况下动态替换资源模块,提供丝滑顺畅的 Web 页面开发体验。 HMR 最初由 Webpack 设计实现,至今已几乎成为现代工程化工具…

curl.perform() pycurl.error: (23, 'Failed writing body (0 != 59)')

在使用python3.7编码时,引入pycurl模块和StringIO模块后,容易引起上述错误 导入StringIO模块的解决方案: 只有在python2中才能导入StringIO模块,直接 from StringIO import StringIO 即可 但是python3,STringIO和…

关于python的url处理

基本环境: python2.7 1 完整的url语法格式: 协议://用户名密码:子域名.域名.顶级域名:端口号/目录/文件名.文件后缀?参数值#标识 2 urlparse模块对url的处理方法 urlparse模块对url的主要处理方法有:urljoin/urlsplit/urlunsplit/urlpar…

windows10+python3.7使用pip安装pycurl失败

使用pip install pycurl安装pycurl失败: python setup.py egg_info did not run successfully. 可以单独下载pycurl依赖文件然后安装 sArchived: Python Extension Packages for Windows - Christoph Gohlke (uci.edu) 选择Python对应版本的文件进行下载&#xff0…

Pycurl介绍

pycurl — A Python interface to the cURL library Pycurl包是一个libcurl的Python接口.pycurl已经成功的在Python2.2到Python2.5版编译测试过了. Libcurl是一个支持FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE 和 LDAP的客户端URL传输库.libcurl也支持HTTPS认证,H…

[windows]python 安装pycurl

问题描述 pip install pycurl 报错 手动安装 下载地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/ 页面搜索: pycurl 下载对应版本的whl文件,我是windows环境 python3.8 所以下载pycurl-7.45.1-cp38-cp38-win32.whl 安装:…

Python实用模块之pycurl

软硬件环境 ubuntu 19.04 64bitanaconda3 with python 3.7.3pycurl 7.43.0.2 简介 CURL是一个基于URL进行数据传输的命令行工具,使用C语言编写,支持http,https,ftp,telnet,file,ldap等常见网络传…

ipcs -a

消息队列、共享内存、信号量

ipcc

IPCCX装完后连接不了LDAP,怎么解决?? 装完了IPCCX, 靠, 直接给我来歌60秒关机, 后面还有LDAP连接问题, 我的IPCCX server 可以ping通ccm server, 为什么LDAP会挂呢??? 请问我现在要怎…

ipcs报错:kernel not configured for shared memory、semaphore、message queues [解决方法]

前言 今天在复习linux进程间通信的shared memory 共享内存时,在PC端的VMare Workstation虚拟机的Ubuntu上测试我写的shared_memory_CREAT.c 和shared_memory_CONSUME.c 时正常在PC端运行,就想着把程序用交叉编译器编译成arm格式放到linux开发板上运行试…

ipcs

 linux命令-ipcs 格式:ipcs [-asmq] [-tclup] ipcs [-smq] -i id ipcs -h 功能描述:ipcs命令用来显示系统存在的ipc(进程间通信)相关信息。 参数:-i 显示指定id的ip…