认识
一、认识
Vite
是一个前端开发与构建工具。采用双引擎架构, 开发阶段使用 Esbuild
+ no-bundle
服务,生产环境用 Rollup
编译构建。
Vite
在开发阶段, Vite
项目的启动可以分为两步。第一步是依赖预构建,借助 Esbuild
超快的编译速度来做第三方库构建和 TS/JSX
语法编译, 第二步是 Dev Server
的启动, 基于浏览器原生 ESModule
的支持实现了 no-bundle
服务,实现开发阶段的 Dev Server
, 进行模块的按需加载, 可以直接在浏览器中运行源码,无需事先打包。每一个文件请求进来都会经历一系列的编译流程,然后 Vite
会将编译结果响应给浏览器。
Vite
生产环境借助 Rollup
, 从 AST
解析的功能开始,完成代码的词法分析(tokenize
)和语义分析(parse
),实现模块依赖图和作用域链的搭建,并完成 Tree Shaking
、循环依赖检测及 Bundle
代码生成
二、工作
-
搭建开发环境: 安装必要的依赖,并搭建项目的构建脚本,同时完成
cli
工具的初始化代码 -
依赖预构建: 通过
Esbuild
实现依赖扫描和依赖构建的功能-
确定预构建入口
-
从入口开始扫描出用到的依赖
-
对依赖进行预构建
-
-
搭建
Vite
的插件机制: 也就是开发PluginContainer
和PluginContext
两个主要的对象 -
实现
no-bundle
服务的编译构建能力: 包括入口HTML
处理、TS/TSX/JS/TSX
编译、CSS
编译和静态资源处理 -
实现
HMR
热更新: 从搭建模块依赖图开始,逐步实现HMR
服务端和客户端的开发
三、问题
3.1 Vite 是如何使用 EsBuild 的呢?
Vite
将 Esbuild
作为自己的性能利器,将 Esbuild
各个垂直方向的能力(Bundler
、Transformer
、Minifier
)利用的淋漓尽致,给 Vite
的高性能提供了有利的保证。
-
作为
Bundler
: 首先是开发阶段的依赖预构建阶段, 为了解决ESM
格式的兼容性问题和海量请求的问题, 使用EsBuild
对于第三方依赖,需要在应用启动前进行打包并且转换为ESM
格式。 -
作为
Transformer
: 在TS(X)/JS(X)
单文件编译上面,Vite
也使用Esbuild
进行语法转译,也就是将Esbuild
作为Transformer
来用。也就是说,Esbuild
转译TS
或者JSX
的能力通过Vite
插件提供,这个Vite
插件在开发环境和生产环境都会执行, 当Vite
使用Esbuild
做单文件编译之后,提升可以说相当大了 -
作为
Minifier
: 那为什么Vite
要将Esbuild
作为生产环境下默认的压缩工具呢?因为压缩效率实在太高了!传统的方式都是使用Terser
这种JS
开发的压缩器来实现,在Webpack
或者Rollup
中作为一个Plugin
来完成代码打包后的压缩混淆的工作。但Terser
其实很慢,主要有2
个原因: 首先压缩这项工作涉及大量AST
操作,并且在传统的构建流程中,AST
在各个工具之间无法共享,比如Terser
就无法与Babel
共享同一个AST
,造成了很多重复解析的过程。然后JS
本身属于解释性 +JIT
(即时编译) 的语言,对于压缩这种CPU
密集型的工作,其性能远远比不上Golang
这种原生语言。因此,Esbuild
这种从头到尾共享AST
以及原生语言编写的Minifier
在性能上能够甩开传统工具的好几十倍。
3.2 Vite 是如何使用 Rollup 的呢?
Vite
是 Vite
用作生产环境打包的核心工具, Vite
默认选择在生产环境中利用 Rollup
打包,并基于 Rollup
本身成熟的打包能力进行扩展和优化,主要包含 3
个方面:
-
CSS
代码分割: 如果某个异步模块中引入了一些CSS
代码,Vite
就会自动将这些CSS
抽取出来生成单独的文件,提高线上产物的缓存复用率。 -
自动预加载:
Vite
会自动为入口chunk
的依赖自动生成预加载标签<link rel="modulepreload">
,如:<head>
<!-- 省略其它内容 -->
<!-- 入口 chunk -->
<script type="module" crossorigin src="/assets/index.250e0340.js"></script>
<!-- 自动预加载入口 chunk 所依赖的 chunk-->
<link rel="modulepreload" href="/assets/vendor.293dca09.js">
</head>这种适当预加载的做法会让浏览器提前下载好资源,优化页面性能。
-
异步
Chunk
加载优化: 在异步引入的Chunk
中,通常会有一些公用的模块,如现有两个异步引入的Chunk: A
和B
,而且两者有一个公共依赖C
,如下图:Preview一般情况下,
Rollup
打包之后,会先请求A
,然后浏览器在加载A
的过程中才决定请求和加载C
,但Vite
进行优化之后,请求A
的同时会自动预加载C
,通过优化Rollup
产物依赖加载方式节省了不必要的网络开销。
3.3 Vite 开发环境与生产环境的 Plugin 是如何兼容呢?
无论是开发阶段还是生产环境,Vite
都根植于 Rollup
的插件机制和生态,如下面的架构图所示:
在开发阶段,Vite
借鉴了 WMR
的思路,自己实现了一个 Plugin Container
,用来模拟 Rollup
调度各个 Vite
插件的执行逻辑,而 Vite
的插件写法完全兼容 Rollup
,因此在生产环境中将所有的 Vite
插件传入 Rollup
也没有问题。
反过来说,Rollup
插件却不一定能完全兼容 Vite
(这部分我们会在插件开发小节展开来说)。不过,目前仍然有不少 Rollup
插件可以直接复用到 Vite
中
Vite
的做法是从头到尾根植于的 Rollup
的生态,设计了和 Rollup
非常吻合的插件机制