Vite 架构
Vite 底层使用的两个构建引擎 Esbuild 和 Rollup 下面我们分析一下 Esbuild 和 Rollup 在 Vite 中有什么作用。
性能利器——Esbuild
Esbuild 在很多关键的构建阶段让 Vite 获得了相当优异的性能,如果这些阶段用传统的打包器/编译器来完成的话,开发体验要下降一大截。
一、依赖预构建——作为 Bundle 工具
node_modules 依赖的大小有可能几百 MB 甚至上 GB,如果这些依赖直接在 Vite 中使用会出现ESM 格式的兼容性问题和海量请求的问题,所有对于第三方依赖,需要在应用启动前进行打包并且转换为 ESM 格式。
Esbuild 作为打包工具的缺点。
- 不支持降级到
ES5的代码。这意味着在低端浏览器代码会跑不起来。 - 不支持
const enum等语法。这意味着单独使用这些语法在 esbuild 中会直接抛错。 - 不提供操作打包产物的接口,像 Rollup 中灵活处理打包产物的能力(如
renderChunk钩子)在 Esbuild 当中完全没有。 - 不支持自定义 Code Splitting 策略。传统的 Webpack 和 Rollup 都提供了自定义拆包策略的 API,而 Esbuild 并未提供,从而降级了拆包优化的灵活性。
二、单文件编译——作为 TS 和 JSX 编译工具
-
在依赖预构建阶段, Esbuild 作为 Bundler 的角色存在。而在 TS(X)/JS(X) 单文件编译上面,Vite 也使用 Esbuild 进行语法转译,也就是将 Esbuild 作为 Transformer 来用。
-
虽然 Esbuild Transfomer 能带来巨大的性能提升,但其自身也有局限性,最大的局限性就在于 TS 中的类型检查问题。这是因为 Esbuild 并没有实现 TS 的类型系统,在编译
TS(或者TSX) 文件时仅仅抹掉了类型相关的代码,暂时没有能力实现类型检查,因此vite build之前会先执行tsc命令,也就是借助 TS 官方的编译器进行类型检查。
三、代码压缩——作为压缩工具
传统的方式都是使用 Terser 这种 JS 开发的压缩器来实现,在 Webpack 或者 Rollup 中作为一个 Plugin 来完成代码打包后的压缩混淆的工作。但 Terser 其实很慢,主要有 2 个原因。
- 压缩这项工作涉及大量 AST 操作,并且在传统的构建流程中,AST 在各个工具之间无法共享,比如 Terser 就无法与 Babel 共享同一个 AST,造成了很多重复解析的过程。
- JS 本身属于解释性 + JIT(即时编译) 的语言,对于压缩这种 CPU 密集型的工作,其性能远远比不上 Golang 这种原生语言。
因此,Esbuild 这种从头到尾共享 AST 以及原生语言编写的 Minifier 在性能上能够甩开传统工具的好几十倍。
构建基石——Rollup
虽然 ESM 已经得到众多浏览器的原生支持,但生产环境做到完全no-bundle也不行,会有网络性能问题。为了在生产环境中也能取得优秀的性能,Vite 默认选择在生产环境中利用 Rollup 打包,主要包含 3 个方面:
- CSS 代码分割。如果某个异步模块中引入了一些 CSS 代码,Vite 就会自动将这些 CSS 抽取出来生成单独的文件,提高线上产物的
缓存复用率 - 自动预加载。Vite 会自动为入口 chunk 的依赖自动生成预加载标签
<link rel="moduelpreload"> - 异步 Chunk 加载优化。在异步引入的 Chunk 中,通常会有一些公用的模块,如现有两个异步引入的 Chunk:
A和B,而且两者有一个公共依赖 C