Vite原理浅析

工作项目里使用Vite作为构建工具,效果不错。研究了一下大致原理,记录下。

1 Vite是什么

1.1 现有的问题

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

1.2 新一代的构建工具

Vite是新一代的前端构建工具,具有极速的服务启动,轻量快速的热重载等特点,为开发提供极速响应。

一旦你体验到 Vite 的神速,就再也回不去了。

2 Vite原理

2.1 浏览器开始原生支持 ESM

浏览器支持ESM,这是Vite实现极速冷启动和热重载的主要背景。

在浏览器支持 ES 模块之前,JavaScript 并没有提供原生机制让开发者以模块化的方式进行开发。

如下图,可以看到前端构建工具的变迁。

  • 无模块化:JS内联外联方式,需要手动维护依赖引入的顺序。
  • 运行时模块化 AMD/CMD:RequireJs和SeaJS出现,采用异步的方法加载模块。
  • nodejs出现,第一批构建工具出现(Grunt/Gulp),js可以处理代码压缩、编译、单元测试等工作。
  • Browserify,实现浏览器端CommonJS规范
  • ESM规范出现,webpack,Rollup出现。Webpack支持AMD\CommonJS\ESM,babel,样式预处理和各种MVC 框架,成为主流。
  • ESM浏览器原生支持,esbuild出现,依赖esbuild的构建工具(Vite,Snowpack)出现。

详细可看前端构建工具进化历程

2.2 极速的服务冷启动

Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。

  • 依赖 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。
  • 源码 通常包含一些并非直接是 JavaScript 的文件,需要转换(例如 JSX,CSS 或者 Vue/Svelte 组件),时常会被编辑。同时,并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。

2.2.1 预处理依赖

Vite 使用 esbuild 预构建依赖。esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。

2.2.2 基于ESM的Dev server

传统的打包工具如Webpack是先解析依赖、打包构建再启动开发服务器,Dev Server 必须等待所有模块构建完成,当我们修改了 bundle模块中的一个子模块, 整个 bundle 文件都会重新打包然后输出。

项目应用越大,启动时间越长。

而Vite利用浏览器对ESM的支持,当 import 模块时,浏览器就会下载被导入的模块。先启动开发服务器,当代码执行到模块加载时再请求对应模块的文件,本质上实现了动态加载。灰色部分是暂时没有用到的路由,所有这部分不会参与构建过程。

随着项目里的应用越来越多,增加route,也不会影响其构建速度。

对于浏览器不能直接识别的文件,如tsx,scss。Vite怎么处理? 在读取到index.tsx文件的内容之后,Vite会对文件的内容进行编译成浏览器可以识别的代码,与此同时,一个import语句即代表一个HTTP请求,Vite Dev Server会读取本地文件,返回浏览器可以解析的代码,当浏览器解析到新的import语句,又会发出新的请求,以此类推,直到所有的资源都加载完成。 对于scss这些样式预处理文件,Vite会编译成script文件。

2.3 快速的热重载

在目前所有的打包工具实现热更新的思路都大同小异:主要是通过WebSocket创建浏览器和服务器的通信。

  1. 创建一个websocket服务端和client文件,启动服务
  2. 通过chokidar监听文件变更
  3. 当代码变更后,服务端进行判断并推送到客户端
  4. 客户端根据推送的信息执行不同操作的更新

Vite中热更新构建过程也是类似,Vite是在本地启动Vite Server服务,通过WebSocket与浏览器进行连接通信,并加入了WebSocket的定时心跳检测机制,拿到已修改更新的文件路径以及时间戳标识,然后再次带上这个时间戳作为参数去重新请求该文件修改后的版本,防止缓存。

差异主要在于,Webpack收到热更新通知时,从entry入口文件开始,将其依赖的资源文件通过loader打包,然后通过server传递到客户端运行;也正是因为这样的运行机制,更新速度会随着应用体积增长而直线下降。

Webpack热更新慢的问题可以通过 babel-plugin-dynamic-import-node 插件来得到明显改善,或者通过手动实现动态按需加载(修改entry为当前项目中需要编译的部分或模块)亦可大幅提升热更新速度,但它也仍需要重新构建这一部分并重载页面。

在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新。

Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):

  • 源码模块的请求会根据 304 Not Modified 进行协商缓存
  • 依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。

2.4 生产环境

为了在生产环境中获得最佳的加载性能,Vite使用Rollup打包。原因:

  • 仍有部分浏览器不支持原生 ESM。
  • 由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 效率低下(即使使用 HTTP/2)。
  • 对比esbuild:打包模块类型复杂的应用程序方面,生态更成熟,具备代码分割等功能。

Vite不排除在未来使用 esbuild 进行生产构建的可能性。

3 对比其他工具

  • WMR:Preact 团队的 WMR 提供了类似的特性集,而 Vite 2.0 对 Rollup 插件接口的支持正是受到了它的启发。就使用范围而言,它更加贴合于 Preact 框架。

  • @web/dev-server:(曾经是 es-dev-server)是一个伟大的项目,基于 koa 的 Vite 1.0 开发服务器就是受到了它的启发。它并未提供官方的框架集成,并且需要为生产构建手动设置 Rollup 配置。

  • Snowpack:也是一个与 Vite 十分类似的非构建式原生 ESM 开发服务器。该项目已经不维护了。团队目前正在开发 Astro,一个由 Vite 驱动的静态站点构建工具。Astro 团队目前是我们生态中非常活跃的成员,他们帮助 Vite 进益良多。

参考

相关推荐
cy玩具19 分钟前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161771 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test2 小时前
js下载excel示例demo
前端·javascript·excel
Yaml42 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事2 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶2 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo2 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v2 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
知孤云出岫2 小时前
web 渗透学习指南——初学者防入狱篇
前端·网络安全·渗透·web
贩卖纯净水.2 小时前
Chrome调试工具(查看CSS属性)
前端·chrome