webpack 热更新原理解析

2023-11-07

一、什么是 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 ,设置为false
        static:path.resolve(__dirname,'static')  // webpack5
    },
  • 调用 module.hot.accept 接口

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

import component from"./component";

let demoComponent = component();

document.body.appendChild(demoComponent);

// HMR interface

if (module.hot) { 

 // Capture hot update

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

2.5 小结

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

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

 参考:

Webpack 热更新HMR 原理全解析

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

webpack 热更新原理解析 的相关文章

  • TypeScript 编译速度极慢 > 12 秒

    只是把它放在那里看看其他人是否也遇到这个问题 我已经使用 webpack 作为我的构建工具 使用 typescript 构建了一个 Angular 2 应用程序 一切都运行良好 但是我注意到 typescript 编译超级超级慢 我现在只有
  • 带 Rails 6/Webpack 的 Gmap

    我正在尝试让以前与早期版本的 Rails 一起使用的 Google 地图设置使用 Rails 6 显示 显然 Rails 6 现在使用 webpack 来处理 javascript 资源 并且我无法让我的应用程序识别用于识别的 Gmaps
  • 将 jquery-mobile 与 Webpack 结合使用

    我正在尝试使用 webpack 加载 jquery mobile 但到目前为止还没有运气 我知道 jquery mobile 依赖于 jquery ui 而 jquery ui 又依赖于 jquery 如何在 Webpack 中设置这样的场
  • 具有客户端/服务器节点设置的 Webpack?

    我正在尝试为带有节点后端服务器的 Angular2 应用程序设置基于 webpack 的流程 经过几个小时的努力 我已经成功地让客户端愉快地构建了 但我现在不知道如何集成我的服务器构建 我的服务器使用生成器 因此必须以 ES6 为目标 并且
  • 使用 npm 安装 JS 包并使用 webpack laravel mix 进行编译

    我是 Laravel 新手 正在关注 Laravel 5 4 的 Laracast 教程我了解了 Laravel mix 以及如何使用 Webpack Laravel Mix 工具编译我们的资源 所以我尝试添加一个 JavaScript 包
  • React中ComponentDidMount生命周期方法被调用两次

    在我的 React 应用程序中 加载应用程序时会进行两次初始 API 调用 我查看了 Chrome gt inpsect 中的网络选项卡 启动器 调用堆栈显示第一个调用是从VM123000 bundle js而第二个调用只是从实际的bund
  • 无法在 typeScript 和 Webpack 中使用 p5.js

    我有一个使用图书馆的项目p5 js https github com processing p5 js Details 我的 Webpack 配置是 const path require path module exports entry
  • Karma 测试报告运行速度快,但实际上运行速度慢

    最好的解释是a video https youtu be Zwwi01JuPrQ 或参见下面的 gif 您会注意到 Karma 进度报告器报告测试只需要几毫秒 但显然需要相当长的时间 我在推特上提到了这一点 https twitter co
  • 图片是在外部库中加载的,如何用webpack加载它们?

    首先 我需要说的是 我对 Webpack 的基础知识知之甚少 这可能就是我找不到解决方案的原因 所以我知道为了加载图像我需要一个路径而不是仅仅将其作为字符串输入require path to image 然后我得到了一个外部库 我需要在其中
  • 从字符串变量导入模块

    我需要从内存变量导入 JavaScript 模块 我知道这可以使用SystemJS and Webpack 但我找不到一个好的工作示例或文档 文档主要讨论 js 文件的动态导入 基本上我需要像下面这样导入模块 let moduleData
  • 如何让 webpack 转换 React 生产文件?

    当我使用 webpack 与 React 16 捆绑我的应用程序时 我在浏览器中收到 Uncaught ReferenceError require is not Defined 对于react和react dom 导致错误的资源是reac
  • workbox webpack 插件从预缓存清单中排除文件夹

    我正在将 workbox webpack 插件与 vue cli 3 一起使用 并且我想将文件夹中的文件排除在外 以免添加到预缓存清单中 请参阅下面我当前的文件结构 src 资产 CSS 壳文件1 svg文件2 svg文件3 svg svg
  • WebpackError:ReferenceError:Gatsby 上未定义窗口

    我已经在互联网上进行了大量搜索 但无法解决这个问题 我正在使用 Gasby 开发静态页面 但遇到此错误 WebpackError ReferenceError window is not defined 我的线索是 这与我正在使用的引导 模
  • 如何让热模块重新加载在打字稿 monorepo 中工作

    因此 在过去的几天里 我一直在尝试在基于 Typescript React Koa Mongo 的 monorepo 中进行热模块重新加载 但完全徒劳无功 我感觉我的头一直在撞砖墙 HMR 的文档少之又少 几乎互联网上的所有内容都只是par
  • Webpack:如何使用动态捆绑组合两个完全独立的捆绑包

    我花了很多时间研究这个问题 但毫无结果 我知道代码分割和动态捆绑在 Webpack 中如何使用import承诺API 然而 我的用例是我有两个完全独立的包 使用不同的 webpack 版本分别生成 为了给您提供视角 我正在构建 React
  • AOT 编译器 - 包括延迟加载的外部模块

    我试图将外部模块 托管在 git npm 存储库中 作为延迟加载模块包含在我的 Angular 应用程序中 我正在使用 ngc 编译器编译我的外部模块 node modules bin ngc p tsconfig aot json 这是我
  • 导入 ng2-datetime 时遇到问题

    我正在尝试将 ng2 datetime 导入到我的应用程序中 因为它似乎是唯一可用的日期选择器弹出窗口 可以让您限制日期范围 无论如何 根据其文档 https www npmjs com package ng2 datetime 我安装了n
  • 使用热重新加载器时无法解析模块“webpack/hot/emitter”

    我正在尝试实施react hot loader在我现有的应用程序中 我跟着tutorial http gaearon github io react hot loader getstarted 并使用了此中的样板文件link https g
  • 如何解决 fs.existsSync 不是函数

    在 NodeJS 中我有 const fs require fs if fs existsSync some path 但我收到错误 类型错误 fs existsSync 不是函数 经过一番搜索后 我读到了Webpack自带require哪
  • 哪些 babel 设置适合导出库?

    我是 Babel Webpack 的新手 对 babelrc 配置有一些困惑 第一个配置 presets babel env modules false useBuiltIns usage targets gt 0 25 not dead

随机推荐

  • 基于语义对比学习的低光照图像增强网络

    文章链接 https ojs aaai org index php AAAI article view 20046 项目链接 https dongl group github io project pages SCL LLE html 导读
  • 无向图的深度优先遍历

    描述 简单介绍一下图 图就是由一些小圆点 称为顶点 和连接这些小圆点的直线 称为边 组成的 例如下图的由五个顶点 编号1 2 3 4 5 和五条边 1 2 1 3 1 5 2 4 3 5 组成 现在从1号顶点开始遍历这个图 遍历是指把图的每
  • css中的line-height设置数字,em和百分比的区别

    疑惑 关于line height这个css属性在平常布局中真的很熟悉了 以前简单的用法都是直接设置具体像素值 line height 28px 随着考虑到响应式等因素 慢慢开始了解无单位数字和使用em或百分比 一直以来都觉得为什么需要定义三
  • 中科蓝讯-库文件的选择

    以530xSDK为例 库文件存储路径如下 sdk ab530x v06x s6597 20210706 app platform libs 在某些项目中 可能不需要打开所有的功能 此时可以重新选择相应的库文件 以节省代码空间 以下为Read
  • 2020Ti电赛体会与经验

    2020Ti电赛体会与经验 写在前面 要想打好电赛 必须要提前做好充足的软硬件准备 要想打好电赛 必须做好一定的知识技能储备 要想打好电赛 必须有几个 降维打击 的高招 写在前面 2020年的Ti电赛我们选择的是E题 四天三夜的结果是可喜可
  • Caused by: org.apache.logging.log4j.LoggingException: log4j-slf4j-impl cannot be present with log4j

    Caused by org apache logging log4j LoggingException log4j slf4j impl cannot be present with log4j SpringBoot出现该错误 log4j冲
  • 如何在Word文档中粘贴漂亮的代码

    写该博客的背景 最近做毕业设计 在写论文的时候 需要粘贴一些核心的代码 但是直接从IDEA中粘过去的代码又显得十分乱 关键不美观 对于我这样一个追求完美的人 这肯定是不能达到我的满足的 经过百度之后 决定写该博客记录一下这个转变过程 步骤如
  • Android文件存储

    参考文章 https blog csdn net baidu 36385172 article details 79695308 https www jianshu com p a39bc4b3a1a6 内部存储 外部存储 Android系
  • stable diffusion webui中的sampler

    Stable Diffusion 采样器篇 知乎采样器 Stable Diffusion的webUI中 提供了大量的采样器供我们选择 例如Eular a Heum DDIM等 不同的采样器之间究竟有什么区别 在操作时又该如何进行选择 本文将
  • Linux基础——Framebuffer(应用层驱动lcd)

    Framebuffer 简介 Framebuffer的API函数 1 open函数 2 ioctl 函数 3 mmap 函数 编写函数操作 开发环境 韦东山Linux开发板 IMX6ULL PRO 开发板 简介 Framebuffer 是用
  • 正则表达式简要笔记

    昨天在给领导演示的时候发现需要替换文件 结果发现同事那机器上只有UltraEdit我还不太会用 淦 没找到正则替换的入口 结果不了了之 后来回来在自己电脑上试了试 发现自己想的正则也不太对 索性现在总结一下最基础 通用 重要的符号 剩下的就
  • Fiddler抓包基础使用(二)

    续上一篇文章 软件测试人员电脑需要安装的基础工具 可可爱爱的程序员的博客 CSDN博客 软件测试资料领取方式 1 Issue 可可爱爱的程序员 软件测试资料合集 GIT CODE 响应HTTP状态过滤规则 Hide success 202
  • Redis 经典面试题合集详解

    作者主页 欢迎来到我的技术博客 个人介绍 大家好 本人热衷于Java后端开发 欢迎来交流学习哦 如果文章对您有帮助 记得关注 点赞 收藏 评论 您的支持将是我创作的动力 让我们一起加油进步吧 文章目录 1 谈谈你对 Redis 的了解 2
  • Java基础——Object类和Objects工具类

    目录 1 Object类 1 1 常用方法 1 2 Object类中方法常见的问题 1 为什么重写equals时必须重写hashCode方法 2 wait和notify为什么定义在Object类当中 wait和notify或notifyAl
  • Brocade_porterrshowt(博科交换机端口状态分析)

    Brocade Switches 如何确定是SFP 或是光纤线导致 Loss of Link 丢失链接 问题 问题描述 一个有问题的SFP或光纤线会造成丢失与主机 存储或另一台交换机的连接问题 在交换机的error log中可能有如下显示
  • RPM包基本命令

    RPM包基本命令 原文链接 https www cnblogs com zqwang0929 p 3352237 htmls 例子 rpm ivh example rpm 安装 example rpm 包并在安装过程中显示正在安装的文件信息
  • python二级菜单_python二级登陆菜单

    1 三级菜单 注册 登陆 注销 2 进入每一个一级菜单 都会有下一级的菜单 user item dict try while True print Welcome sir input choice int input Please ente
  • 利用cordova打包apk

    声明 电脑未安装过android studio 不会使用android studio 在此基础上将vue cli3项目打包成apk 百度了一下如何将vue项目打包成apk 百度说用HBuilderX 10秒就可以完成 然后我就按照百度的方法
  • Bert CNN信息抽取

    Github参考代码 https github com Wangpeiyi9979 IE Bert CNN 数据集来源于百度2019语言与智能技术竞赛 在上述链接中提供下载方式 感谢作者提供的代码 1 信息抽取任务 给定schema约束集合
  • webpack 热更新原理解析

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