Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 编码专题

  • 深入浅出 Vite

    • 00-目录大纲
    • 07-Vite预构建上手
    • 09-Esbuild 插件开发实战
    • 10-Rollup 打包基本概念及使用
    • 11-Rollup打包机制及插件开发
    • 12-Vite插件开发实战
    • 13-Vite热更新
      • 0.前言
      • 1.概念梳理
        • 问题 1:什么是 HMR?
        • 问题 2:Vite 在实现 HMR 时如何使用的 import.meta?
      • 2.HMR 实践
        • 问题 3:hmr 存在几种更新情况?
        • 问题 4:除了 hot.accept/dispose 还有其余的钩子吗?
      • 3. 疑问?
        • 问题 5:什么时候需要手动 HMR?
    • 16-Vite搭建 SSR 框架
  • 快速上手 API

  • 深入浅出Babel

  • 深入浅出 React

  • 百问掘金
  • 深入浅出 Vite
wangjiasheng
2023-04-06
目录

13-Vite热更新

# 0.前言

本节课程代码的仓库 (opens new window):15-vite-hmr

# 1.概念梳理

# 问题 1:什么是 HMR?

HMR 的全称叫做Hot Module Replacement,即模块热替换或者模块热更新。

通过 HMR 技术主要想在开发阶段实现如下以下两个效果:

  • 局部刷新:服务器启动后,修改源代码,以模块为最小更新单位对页面进行无刷更新。
  • 状态保存:当改变的模块涉及状态概念时,如 setTimeout 组件,每次更新时需将前一个定时器先进行销毁再创建;再如React 组件,模块更新阶段会对组件进行销毁操作,此时状态信息也会被丢失掉。

# 问题 2:Vite 在实现 HMR 时如何使用的 import.meta?

vite 主要利用的是一个现代浏览器的一个内置对象 import.meta,在此基础上扩展了一个 hot 属性。

内置的定义如下:

interface ImportMeta {
  readonly hot?: {
    readonly data: any;
    accept(): void;
    accept(cb: (mod: any) => void): void;
    accept(dep: string, cb: (mod: any) => void): void;
    accept(deps: string[], cb: (mods: any[]) => void): void;
    prune(cb: () => void): void;
    dispose(cb: (data: any) => void): void;
    decline(): void;
    invalidate(): void;
    on(event: string, cb: (...args: any[]) => void): void;
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

重要的有:

  • import.meta.hot.accept 区分热更新边界,监听模块变化
  • import.meta.hot.data 缓存数据。
  • import.meta.hot.dispose 销毁钩子。

# 2.HMR 实践

# 问题 3:hmr 存在几种更新情况?

  • 自身模块更新

    img

  • 父模块对子模块更新

    img

下面通过一个例子来体验下 vite 中这两种热更新模式。

存在两个模块:

  1. renderClient 负责渲染

    export const render = () => {
      const app = document.querySelector<HTMLDivElement>('#app')!
      app.innerHTML = `
        <h1>Hello Vite!</h1>
        <p target="_blank">This is hmr test</p>
      `
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
  2. state.js 通过操纵 dom 的方式改变 count

    export function initState() {
      let count = 0;
      setInterval(() => {
        let countEle = document.getElementById('count');
        countEle!.innerText =  ++count + '';
      }, 1000);
    }
    
    1
    2
    3
    4
    5
    6
    7

    页面结构如下:

    <body>
      <div id="app"></div>
      <p>count: <span id="count">0</span></p>
      <script type="module" src="/src/main.ts"></script>
    </body>
    
    1
    2
    3
    4
    5

当对 state.js 或是 renderClient.js 文件进行修改时,默认是没有 hmr 效果的,整个页面直接刷新。

此时可以给这两个模块添加 hmr 触发边界。

首先是 renderClient.js,对于自身 hmr 边界非常简单,可添加如下:

/* 子页面监听监听模块变化 */
if (import.meta.hot) {
  /* 回调中的 mod 即为当前模块,此时可调用当前模块暴露的 renderClient 方法 */
  import.meta.hot.accept((mod) => mod.renderClient());
}
1
2
3
4
5

而对于 state.js 文件来说,则比较复杂,由于涉及到定时器和状态,需使用 import.meta.hot.dispose 在每次模块更新时销毁定时器,还需要对状态 count 进行缓存。

// @ts-nocheck
let timer: number | undefined;

if (import.meta.hot) {
  // 当监听到当前模块变化时,需要手动清除定时器
  import.meta.hot.dispose(() => {
    if (timer) {
      clearInterval(timer);
    }
  });
}

/* 子页面监听监听模块变化 */
if (import.meta.hot) {
  /* 回调中的 mod 即为当前模块,此时可调用当前模块暴露的 renderClient 方法 */
  import.meta.hot.accept((mod) => mod.initState());
}

/* 缓存数据 */
if (!import.meta?.hot.data.count) {
  import.meta.hot.data.count = 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

还需倾入性地改造 initState 函数,使用 getAndIncCount 取代 count 如下:


export function initState() {
  // 每次 initState 刷新时,需从 import.meta.hot.data 上取数据
  const getAndIncCount = () => {
    const data = import.meta.hot?.data || {
      count: 0,
    };
    // 注:import.meta.hot.data 的引用地址
    data.count = data.count + 1;
    return data.count;
  };
  timer = setInterval(() => {
    let countEle = document.getElementById("count");
    countEle!.innerText = getAndIncCount() + "";
  }, 1000);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上述可以发现,每次模块更新了都必须要在更新的模块下写下如下代码:

/* 子页面监听监听模块变化 */
if (import.meta.hot) {
  /* 回调中的 mod 即为当前模块,此时可调用当前模块暴露的 xxxMethod 方法 */
  import.meta.hot.accept((mod) => mod.xxxMethod());
}
1
2
3
4
5

当模块越来越多时,显然是不合适的,可以使用 import.meta.hot.accept([],()=>{}) 第一个参数可以改造为 数组 格式,可通过解构的方式顺序获取对应的模块。

/* 主页面监听 module 变化 */
if (import.meta.hot) {
  import.meta.hot.accept(["./render.ts", "./state.ts"], (modules) => {
    const [renderModule, stateModule] = modules;
    if (renderModule) {
      // 触发 render 模块刷新
      renderModule.renderClient();
    }
    if (stateModule) {
      // 触发 state 模块刷新
      stateModule.initState();
    }
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 问题 4:除了 hot.accept/dispose 还有其余的钩子吗?

  • import.meta.hot.decline():模块不可热更新。

  • import.meta.hot.invalidate():强制刷新页面。

  • import.meta.hot.on("event", (data)=>{}) :监听自定义事件。

    如:结合 handleHotUpdate 实现对 custom 事件的监听

    // 插件 Hook
    handleHotUpdate({ server }) {
      server.ws.send({
        type: 'custom',
        event: 'custom-update',
        data: {}
      })
      return []
    }
    // 前端代码
    import.meta.hot.on('custom-update', (data) => {
      // 自定义更新逻辑
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

# 3. 疑问?

# 问题 5:什么时候需要手动 HMR?

此题待定,在日常开发中很少手动去编写 import.meta.hot.accept() 这部分代码,从代码可读性上以及维护性上讲,都显得比较冗余。

对于 renderCient 的改造可能还稍微轻量些,我想的是可以使用工具webpack 等工具实现自动注入代码,而对于state.ts 这种状态相关的函数,改造倾入性就很强。不仅需要收集改变的状态并挂载到 import.meta.hot.data 上,还需要构造类似 getAndIncCount() 这类辅助函数去修改状态,难度还是很大的,没有头绪。

还有一块像 React 组件的 HMR 更新,平时我们只需要用现成的插件,对于此块部分的改造还需要涉及 React 的理解。好在已有插件可用,真不知道那些人是怎么编出来的。

编辑 (opens new window)
上次更新: 2023/04/12, 9:04:00
12-Vite插件开发实战
16-Vite搭建 SSR 框架

← 12-Vite插件开发实战 16-Vite搭建 SSR 框架→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式