一、什么是 HMR

HMR 全称 Hot Module Replacement,中文语境通常翻译为模块热更新,它能够在保持页面状态的情况下动态替换资源模块,提供丝滑顺畅的 Web 页面开发体验。

1.1 HMR 之前

在 HMR 之前,应用的加载、更新是一种页面级别的原子操作,即使只是单个代码文件发生变更都需要刷新整个页面才能最新代码映射到浏览器上,这会丢失之前在页面执行过的所有交互与状态,例如:

对于复杂表单场景,这意味着你可能需要重新填充非常多字段信息弹框消失,你必须重新执行交互动作才会重新弹出再小的改动,例如更新字体大小,改变备注信息都会需要整个页面重新加载执行,影响开发体验。引入 HMR 后,虽然无法覆盖所有场景,但大多数小改动都可以实时热更新到页面上,从而确保连续、顺畅的开发调试体验,对开发效率有较大增益效果。

1.2 使用 HMR

Webpack 生态下,只需要经过简单的配置即可启动 HMR 功能,大致上分两步:

  • 配置 devServer.hot 属性为 true
devServer:{hot:true, //支持热更新port:8080,//contentBase:path.resolve(__dirname,'static') //指定(额外的)静态文件目录, // 如果使用 CopyWebpackPlugin ,设置为falsestatic:path.resolve(__dirname,'static')  // webpack5},
  • 调用 module.hot.accept 接口

调用 module.hot.accept 接口,声明如何将模块安全地替换为最新代码,如

import component from"./component";let demoComponent = component();document.body.appendChild(demoComponent);// HMR interfaceif (module.hot) { // Capture hot updatemodule.hot.accept("./component", () => {const nextComponent = component();// Replace old content with the hot loaded one document.body.replaceChild(nextComponent, demoComponent); demoComponent = nextComponent;});}

例如:

let render = ()=>{let title = require('./title.js')root.innerText = title;
}
render()if(module.hot){module.hot.accept(["./title.js"],render)
}

模块代码的替换逻辑可能非常复杂,幸运的是我们通常不太需要对此过多关注,因为业界许多 Webpack Loader 已经提供了针对不同资源的 HMR 功能,例如:

  • style-loader 内置 Css 模块热更
  • vue-loader 内置 Vue 模块热更
  • react-hot-reload 内置 React 模块热更接口

因此,站在使用的角度,只需要针对不同资源配置对应支持 HMR 的 Loader 即可,很容易上手。

二、实现原理

Webpack HMR 特性的原理并不复杂,核心流程:

  1. 使用 webpack-dev-server (后面简称 WDS)托管静态资源,同时以 Runtime 方式注入 HMR 客户端代码;
  2. 浏览器加载页面后,与 WDS 建立 WebSocket 连接;
  3. Webpack 监听到文件变化后,增量构建发生变更的模块,并通过 WebSocket 发送 hash 事件;
  4. 浏览器接收到 hash 事件后,请求 manifest 资源文件,确认增量变更范围;
  5. 浏览器加载发生变更的增量模块;
  6. Webpack 运行时触发变更模块的 module.hot.accept 回调,执行代码变更逻辑;
  7. done;

接下来我会展开 HMR 的核心源码,详细讲解 Webpack 5 中 Hot Module Replacement 原理的关键部分

2.1 注入 HMR 客户端运行时

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

  • 用于建立 WebSocket 连接,处理 hash 等消息的运行时代码;
  • 用于加载热更新资源的 RuntimeGlobals.hmrDownloadManifest 与 RuntimeGlobals.hmrDownloadUpdateHandlers 接口;
  • 用于处理模块更新策略的 module.hot.accept 接口;
  • 等等

经过 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.jsexport const bar = 'bar'
// src/index.jsimport { 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.jsconsole.log('bar');module.hot.accept();示例模块发生变动之后,会从头开始重复执行 console.log 语句。

2.5 小结

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

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

参考:

Webpack 热更新HMR 原理全解析

webpack 热更新原理解析相关推荐

  1. webpack热更新原理

    webpack热更新原理 文章目录 webpack热更新原理 一.什么是热更新? 二.webpack热更新基本原理 1.修改webpack.config.js的entry配置 2.启动本地服务 3.监 ...

  2. webpack热更新原理-连阿珍都看懂了

    前言 在旧开发的时代,前端项目在开发的过程中修改代码,很有可能是手动切到浏览器刷新页面来看到改动效果.操作不方便且页面之前的编辑记录也都丢失,体验可以说为0.想象一下一个表达你努力填满了所有输入项,结 ...

  3. 【Webpack】1047- 轻松理解webpack热更新原理

    一.前言 - webpack热更新 Hot Module Replacement,简称HMR,无需完全刷新整个页面的同时,更新模块.HMR的好处,在日常开发工作中体会颇深:节省宝贵的开发时间.提升开发 ...

  4. webpack配置及热更新原理

    1:配置webpack 1:新建一个vue项目: 进入目录,在终端创建: vue create 项目名: 2:创建新文件 对应的文件目录src下新建存放css文件index.css,项目的入口文件ma ...

  5. webpack热更新

    什么是模热更新?有什么优点 模块热更新是webpack的一个功能,它可以使得代码修改之后,不用刷新浏览器就可以更新. 在应用过程中替换添加删出模块,无需重新加载整个页面,是高级版的自动刷新浏览器. 优 ...

  6. 修改html时webpack热更新,webpack学习之路(二)webpack-dev-server实现热更新

    上一章对webpack的配置有了简单的认识. 这一章,我需要学习的是webpack热更新,因为在开发过程中,不希望当文件更改时,人肉去编译文件,刷新浏览器. webpack热更新 webpack-de ...

  7. less webpack 热更新_webpack---less+热更新 使用

    最近尝试用less写界面,webpack进行打包,然后发现每次修改less时都需要重新执行webpack打包一下,于是就想到了webpack热更新这个功能. 一.使用less less是一门css预处 ...

  8. 理解 webpack 热更新

    一.开启:HotModuleReplacementPlugin/--hot 二.webpack 编译构建后,控制台出现: 1.新的 hash 值 // 作为下次热更新的标识 2.新的 json 文件 ...

  9. 热更新原理及实践注意

    首先要说明几个概念,不要混用,热部署,热加载: 热部署:就是已经运行了项目,更改之后,不需要重新tomcat,但是会清空内存,重新打包,重新解压war包运行,可能好处是一个tomcat多个项目,不必因 ...

最新文章

  1. java写入txt文件 不替换_java非覆盖写入文件及在输出文本中换行
  2. 这十个好习惯助你摆脱消极的态度
  3. 使用 C# 9 的records作为强类型ID - JSON序列化
  4. 5G精华问答 | 除了速度,5G还能带来什么?
  5. USB转串口 TTL RS-232 RS-485 COM口 UART区别
  6. 10款滑动门代码_jquery 滑动门_js滑动门_tab滑动门_jquery 选项卡_js选项卡_tab选项卡效果(一)
  7. VLAN(虚拟局域网)
  8. PAT 甲级 1048 Find Coins
  9. java前端vml_在Web中使用JavaScript和VML实现WebGIS中的测距
  10. 中文计算机期刊影响因子排名,最新计算机类中文核心期刊影响因子排名
  11. matlab 弗洛伊德算法,手写matlab的弗洛伊德算法的函数(注释很详细)
  12. python 使用twilio免费发送短信
  13. SpringBoot+Vue实现邮箱登录注册找回密码(附接口文档)
  14. 大数据时代对存储发展的要求有哪些
  15. 如何下载和使用ST官方例程
  16. MATLAB--数字图像处理 HOG+SVM识别手写数字
  17. 拉链表的详细实现过程(好文点赞收藏!!)
  18. C语言实现小游戏:五子棋
  19. 【微信小程序AR】基于Kivicube零代码实现微信小程序AR
  20. 经纬度距离和直角坐标距离转换

热门文章

  1. Python切割九宫格图
  2. 牛客国庆集训派对Day2 H 卡牌游戏(花式蒙期望)
  3. SSPI方式配置Lattice ECP3系列FPGA
  4. 动力节点面试题mysql真的难_动力节点整理120道面试问题集锦
  5. STM32涉及相关基本电路分析
  6. cad如何修改新的文字注释?
  7. 全志平台SPI接口LCD屏驱动(GC9300,GC9306,ST7789,HX8357C)
  8. 虚假新闻检测,来自美团NLP团队方案
  9. 程序员的算法趣题pdf
  10. MySQL Workbench 6.3CE 汉化教程(内附资源链接)