记录生活
简单不先于复杂

Vite 构建工具常用知识点总结

1. 何为vite

法语意为 “快速的”,发音 /vit/,同 “veet”。

是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

  • 一个开发服务器,它基于 原生 ES 模块 (ESM)提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
  • 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。

2. 为什么选Vite

1)性能瓶颈

构建越来越大型的应用时,需要处理的 js 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。基于 js 开发的工具就会开始遇到性能瓶颈。通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。

2)开发环境基于打包器的方式已经落伍

基于打包器的方式启动必须优先抓取并构建你的整个应用,然后才能提供服务。

基于打包器启动时,重建整个包的效率很低。原因显而易见:因为这样更新速度会随着应用体积增长而直线下降。

3)浏览器 ESM 支持

浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。

在开发环境下,Vite 以 原生 ESM 方式提供源码。这实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。

详见文档

3. Vite 与 webpack 主要差异

Webpack Vite
先打包生成bundle,再启动开发服务器 先启动开发服务器,利用新一代浏览器的ESM能力,无需打包,直接请求所需模块并实时编译
热更新(HMR)时需要把改动模块及相关依赖全部编译 热更新时只需让浏览器重新请求该模块,同时利用浏览器的缓存(源码模块协商缓存,依赖模块强缓存)来优化请求

4.Vite 开发环境构建性能高原因

Webpack 从入口开始需要逐步经历语法解析、依赖收集、代码转译、打包合并、代码优化,最终将高版本的、离散的源码编译打包成低版本、高兼容性的产物代码,这可满满都是 CPU、IO 操作啊,在 Node 运行时下性能必然是有问题。而热更新(HMR)时也很慢,即使只有很小的改动,Webpack依然需要构建完整的模块依赖图,并根据依赖图来进行转换。

Vite 开发环境编译快:

  1. 开发环境冷启动无需打包,只是使用esbuild对依赖进行预构建,将CommonJS和UMD发布的依赖转换为浏览器支持的ESM
  2. 无需分析模块之间的依赖,无需在启动开发服务器前进行编译
  3. 在一开始将应用中的模块区分为 依赖 和 源码 两类,源码模块协商缓存,依赖模块强缓存(依赖大多为开源的不会变动的node_modules模块,请求路径满足 /^\/@modules\// 格式就会被认为是依赖)

Vite 热更新更快:

  1. 利用了ESM和浏览器缓存技术,构建后的依赖请求(http头的max-age=31536000,immutable)进行强缓存,以提高页面性能。
  2. 更新速度与项目复杂度无关。

5. 预构建原理

Vite 对 js/ts 的处理没有使用如 glup, rollup 等传统打包工具,而是使用了 esbuildesbuild 是一个全新的js打包工具,底层使用了go,大量使用了并行操作,可以充分利用CPU资源。esbuild支持如babel, 压缩等的功能。esbuildrollup等工具快十几倍。

预构建:

当你首次启动 vite 时,使用esbuild在启动开发服务器前把检测到的依赖进行预构建。

  1. 将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。
  2. 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能(预先把一个模块用到的所有内部分支模块全部打包成一个bundle),这样就浏览器在请求某个模块时,便只需要发送一次请求了。
  3. 对部分文件如 tsx 进行类型解析,转换为js

当开发服务器启动后,除非遇到一个新的依赖关系导入,而这个依赖关系还没有在缓存中,Vite 将重新运行依赖构建进程并重新加载页面。

Vite 的基本实现原理:

当浏览器解析 import HelloWorld from './components/HelloWorld.vue' 时,会向当前域名发送一个请求获取对应的资源(ESM支持解析相对路径)。

 

744467c7d090665

就是启动一个 koa 服务器拦截由浏览器请求 ESM的请求。通过请求的路径找到目录下对应的文件做一定的处理最终以 ESM的格式(响应类型为js)返回给客户端。

e956802c1b366fc

客户端注入本质上是创建一个script标签(type=’module’),然后将其插入到head中,这样客户端在解析html是就可以执行代码了。

浏览器下载对应的文件,然后解析成模块记录。接下来会进行实例化,为模块分配内存,然后按照导入、导出语句建立模块和内存的映射关系。最后,运行上述代码,把内存空间填充为真实的值。

静态资源加载

当请求的路径符合 image, media, fonts 或 JSON 格式,会被认为是一个静态资源。静态资源将处理成ESM模块返回。

vue文件

解析vue文件是实时的,是实时编译的。不会预先编译。需要配置@vitejs/plugin-vue插件。编译时使用Vue中的@vue/compiler-sfc将Vue文件编译为AST语法树,抽离其中的 template, css, script。使用@vue/compiler-dom处理template模板转为js。

当 Vite 遇到一个 .vue 后缀的文件时。由于 .vue 模板文件的特殊性,它被拆分成 template, css, script 模块三个模块进行分别处理。最后会对 script, template, css 发送多个请求获取。

75e8f2aa3838077

如上图中请求 App.vue 获取 script 代码,App.vue?type=template 获取 模板;App.vue?type=style 获取样式。这些代码都被插入在 App.vue 返回的代码中。

5fd30ef19d96ac3

js/ts处理

Vite使用esbuild将ts转译到js,约是tsc速度的20~30倍,同时HMR更新反应到浏览器的时间会小于50ms。但是,由于esbuild转换ts到js对于类型操作仅仅是擦除,所以完全保证不了类型正确,因此需要额外校验类型,比如使用tsc –noEmit。

将ts转换成js后,浏览器便可以利用ESM直接拿到js资源。

6. 热更新原理

在客户端与服务端建立了一个 websocket 连接,当代码被修改时,服务端发送消息通知客户端去请求修改模块的代码,完成热更新。

Vite 中客户端的 websocket 相关代码在处理 html 时被写入代码中。Vite 会接受到来自服务端的消息。通过不同的消息触发一些事件。做到浏览器端的即时热模块更换(热更新)。

7. 为什么生产环境仍需打包

尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。

对比在开发环境Vite使用esbuild来构建依赖,生产环境Vite则使用了更加成熟的Rollup来完成整个打包过程。因为esbuild虽然快,但针对应用级别的代码分割、CSS处理仍然不够稳定,同时也未能兼容一些未提供ESM的SDK。

为了在生产环境中获得最佳的加载性能,仍然需要对代码进行tree-shaking、懒加载以及chunk分割(以获得更好的缓存)。

8. 构建工具和打包工具的区别?

构建过程应该包括 预编译、语法检查、词法检查、依赖处理、文件合并、文件压缩、单元测试、版本管理等 。

打包工具更注重打包这一过程,主要包括依赖管理和版本管理。

9. Vite有什么缺点?

  1. 目前 Vite 还是使用的 es module 模块不能直接使用生产环境(兼容性问题,如果你的项目不需要兼容 IE11 等低版本的浏览器,自然是可以使用的)
  2. 生产环境使用 rollup 打包可能会造成开发环境与生产环境的不一致。
  3. 很多 第三方 sdk 没有产出 ESM格式的的代码,这个需要自己去做一些兼容。目前支持 CommonJS(CJS)代码快速转化为 ESM,但是对于一些格式不规范的代码,可能还是需要单独处理。

10. 动态导入多个组件

使用import.meta.glob方法:

// 1.上面的方法相当于一次性加载了 views 目录下的所有.vue文件,返回一个对象
const modules = import.meta.glob('../views/*/*.vue');
const modules ={
    "../views/about/index.vue": () => import("./src/views/about/index.vue")
}
// 2.动态导入的时候直接,引用
const router = createRouter({
  history: createWebHistory(),
  routes: [
    // ...
    {
      path: 'xxxx',
      name: 'xxxxx',
      // 原来的方式,这个在开发中可行,但是生产中不行
      // component: () => import(`../views${menu.file}`),
      // 改成下面这样
      component: modules[`../views${filename}`]
    }
    // ...          
  ],
})

其他基础可看文档…

教程 | 配置 | 插件

赞(0)
未经允许不得转载:爱安普 » Vite 构建工具常用知识点总结