Vite HMR 原理探索

197 阅读4分钟

在当今的前端开发环境中,快速和高效的开发工具成为了开发者的重要需求。Vite,作为一种新型的前端构建工具,凭借其快速的冷启动、即时的热模块更新(HMR)和丰富的特性,已经吸引了大量的开发者的关注。特别是其HMR的实现,相比传统的构建工具如Webpack,Vite提供了更快速、更高效的模块热更新,大大提高了开发效率。

启动服务器

当我们在本地运行 vite 命令时,Vite 会启动一个开发服务器,这个服务器将用于提供开发环境,如按需编译,热更新,代理等等。

创建文件系统监听

Vite 使用文件系统监听(file system watchers)来检测项目中的文件变化。当启动 Vite 开发服务器时,Vite 会设置一个文件系统监听器,用于监听项目中的文件变化。

文件系统监听主要使用 Node 中的一个库 chokidar 来实现。这个库可以监听文件的删除新增和修改等操作。

const watcher = chokidar.watch('file, dir, glob, or array', {
  ignored: /(^|[/\])../, // ignore dotfiles
  persistent: true
});

watcher
  .on('add', path => rebuild)
  .on('change', path => rebuild)

建立 WebSocket 连接

当浏览器加载应用程序时,Vite 客户端会与开发服务器建立一个 WebSocket 连接。这个连接用于实时接收来自服务器的更新通知和更新的模块。

image.png

重新编译模块

当一个文件被修改后,Vite 会根据不同文件类型选择相应的编译器进行编译。

  1. 假如是一个 .vue 文件,Vite 使用对应的 Vue 编译器 @vitejs/plugin-vue 来编译,主要分为三部分:

    • 模版部分 @vitejs/plugin-vue 使用 vue-template-compiler 将 Vue 模板编译为 JavaScript 渲染函数。这样,当组件在运行时被实例化时,渲染函数可以直接生成虚拟 DOM,提高渲染性能。
    • 脚本部分 @vitejs/plugin-vue 使用 @vue/compiler-sfc 对 Vue 文件中的脚本部分进行处理。这包括对 Vue 3 的新特性,如 <script setup> 的支持,以及对 TypeScript 的支持。
    • 样式部分 @vitejs/plugin-vue 使用 postcss 处理 Vue 文件中的样式部分。这包括对 CSS 预处理器(如 SCSS、LESS 等)的支持,以及对 CSS Modules 的支持。
  2. 假如是 .jsx ts 或者 tsx,Vite 默认会使用 ESBuild 来编译。这是一个使用 Go 语言开发的编译器,利用来多核处理器,速度明显优于 Webpack 或者 Rollup

  3. .less 或者 .scss 会使用配置的预处理器来编译。

值得一提的是,这里不仅会根据文件编译相应的模块,有必要的话,也会递归编译相关的文件,例如当我们修改了一个模块的依赖项时。

发送更新的模块

Vite 开发服务器会将更新的模块通过 WebSocket 发送给浏览器。更新的模块可能包括被修改的文件本身以及与其相关的其他文件。

image.png

浏览器接收更新的模块并应用

  1. 接收更新的模块:当 Vite 开发服务器通过 WebSocket 向浏览器发送更新的模块时,Vite 客户端会接收到这些模块。

  2. 分析模块依赖关系:Vite 客户端会分析更新的模块的依赖关系,找到需要更新的模块及其依赖的模块。

  3. 替换旧模块:Vite 客户端会将更新的模块替换到浏览器的模块缓存中,从而替换旧模块。这个过程涉及到以下操作:

    • 删除旧模块的引用。
    • 加载更新的模块。
    • 更新模块缓存,使其引用新模块。
  4. 执行 HMR API:Vite 客户端会执行更新模块的 HMR API,如 dispose 和 accept 回调。这些回调用于处理模块更新的副作用,例如清理资源、重新渲染组件、更新状态等。这个过程是自动执行的,但可以根据需要自定义回调函数。

    if (import.meta.hot) {
      import.meta.hot.dispose(() => {
        // 清理资源,例如取消事件监听器
        window.removeEventListener('event', eventHandler);
      });
    }
  1. 传播更新:Vite 客户端会将模块更新传播到依赖的模块。这意味着,当一个模块被更新时,所有依赖该模块的模块也会被更新。这个过程会递归地应用到整个依赖链。

写在最后

总之,Vite 作为一个高效的开发工具,为开发者提供了实时的开发体验,并简化了模块化代码的处理过程。通过了解 Vite 的原理和实现细节,开发者可以更好地利用 Vite 的功能,提高开发效率。