Vue 与 Vite 生态最新进展:迈向一体化与智能化的未来

VueConf 2025 于 7 月 12 日在深圳举办!

VoidZero 创始人 & CEO,Vue.js & Vite 作者尤雨溪在现场给大家带来《Vue 和 Vite 生态最新进展》的主题演讲,以下内容基于他演讲的内容整理。

Hello 大家好,欢迎大家又来到又一年的 VueConf。大家有多少人是去年来过的?举个手。okay 欢迎,第一次来的请举手。很多第一次来的,okay,非常好。

今天,我将和大家分享 Vue 和 Vite 生态的最新进展。废话不多说,我们直接进入正题。

首先,按照惯例看一下数据,其实有点超乎我的意料,过去一年,Vue 的增长率达到了 50%。最近的这个下降一般是因为统计数据还不完全。 6 月 22 号这一周的数据,达到了将近 760 万的周下载,比较去年同期有 50% 的增长,去年大概在 500 万左右。然后橙色的这条线,是 Vue 3 底下的 runtime-core 这个依赖,所以我们通常用这个包的数据来看 Vue 3 的下载量占比。

Vue 3 现在下载量占比从去年同期的 61.5% 上升到了今年的 71.7%,差不多涨了 10%。那就是说 Vue 2 现在市占量大概连 3 成都不到了,如果大家有还没升级的,希望可以继续努力一下。如果单看 Vue 3 的增长数据,过去一年 Vue 3 差不多有 76.7% 的增长。

图片

最近我比较希望大家留意的一个比较重大的发布,就是 Johnson 的 Vue Language Tools 3.0。我相信一直以来,很多朋友在使用 Vue Language Tools 时,都曾遇到过稳定性问题,尤其是在大型 TypeScript 项目中。这是因为 Language Tools 本身内部的复杂度,我们有的时候需要跟 TypeScript Language Server 沟通,有的时候 VSCode 和 TypeScript 内部行为的变化,导致的一些稳定性问题。经过 Johnson 和御守,他们的不断的努力,在 3.0 里面稳定性得到了非常大幅的改进。如果大家还没有升级的话,一定要升级。如果升到 3.0 以后用得好,记得去 VSCode 商店给 Johnson 留个好评,谢谢!

那接下来 Vue 要干啥?那就是我们即将要发布的 3.6。我们前两天,大家基本上都在做这方面的准备。(编者注: 3.6 于 7 月 12 日下午正式发布)

3.6 有什么呢?

API 方面没有什么改动,核心的主要是两个东西。

一个是我们的响应式系统,又又又又又重做了一次,每一次都变得更快、内存占用更少、更强大。在 Vue 2 到 Vue 3 的时候,响应式系统重写过一次,3.4 的时候重写过一次,3.5 的时候又重写过一次,3.6 呢?它又又又被重写了一次。这个在吴博(Doctor Wu)今天的演讲里面会有更深入的分享,大家一定要记着去看。

Alien Signals 性能

可能有些朋友已经知道,TC39 其实有一个 Signals Polyfill。

对于 Signals 不太熟悉的同学,Signals 究竟是什么呢?

Signals 其实就是响应式的一个基本单位,它是一个响应式的原语。一个 Signal 可以被理解为是包含了一个值的一个响应式的包装。那 Signals 可以被侦听,然后你可以去追踪它的变化,每当 Signals 有变化的时候,你就可以去做对应的操作,这个操作被叫做 effect。

这个其实跟 Vue 的响应式系统内部的核心逻辑是一样的,所以严格来说,Vue 也算是 Signals 的一种实现。

近年来其实大家也发现,社区里面实际上主流框架 Vue、Angular(Angular signals)、Preact、Solid、Svelte 5,他们都有一个 Signals 的实现。实际上除了 React 之外,现在所有的框架基本上都已经变成了基于 Signals 范式的框架。那么在这个前提下,TC39 其实有一个很多框架作者参与的一个 Working Group,在把 Signals 做成语言级别的提案。当时有一个 Polyfill,这个 Polyfill 非常的慢,Johnson 做了 Alien Signals 之后,发现可以去替换这个官方 Polyfill。总之 Alien Signals 就是目前已知市面上最快的 Signals 实现。Solid Team 有一个哥们,他号称有一个更快的实现,5 月份的时候,他在我面前吹了一下,目前还没有出来,所以我们拭目以待。

Vapor mode

聊完 Alien Signals,我们来说说 Vapor mode。其实已经说了挺久了,去年演讲也有说这个问题,现在发布在即,我觉得还是稍微再补充一下上下文,尤其是给之前没有太关注 Vapor Mode 的同学们。

Vapor Mode 的定义:它是一个为了极致性能而存在的全新的编译和渲染模式。

我们知道 Vue 1 的渲染模式是有点像 Angular 1 那样,我们遍历真实的 DOM 模板,然后去创建响应式的绑定。

Vue 2 采用了 Virtual DOM,当时采用 Virtual DOM,一方面出于服务端渲染的需求,另一方面是为了方便用户自己也可以写 Virtual DOM 的渲染函数。Virtual DOM 确实存在一些性能的开销。

然后我们在 Vue 3 里面,对编译器做了大幅的改进,当编译器把 Vue 的模板编译成渲染函数的时候,可以添加各种通过静态分析出来的优化。

那么 Vapor Mode 其实是在这个基础上更进一步。它的几个重点:

它支持一个 Vue 现在的子集。

Vapor Mode 只支持 SFC,只支持 script setup,也就是说在 Vapor Mode 下,你只能用 Composition API。

但是在它支持的功能范围子集中,它的行为跟 Virtual DOM 下的行为是一致的。唯一的区别是,它底下的编译输出完全不一样,这个编译输出让我们在运行时大幅的提升性能。

另一个重点就是它支持细粒度的启用,也就是说,你不需要为了用 Vapor Mode 把你整个应用重写,你可以在一个现有的应用中的一部分里面,选择性的启用 Vapor Mode,你也可以在一个全新的应用中使用纯 Vapor Mode。

Vapor Mode 整体设计架构上,其实对于 Vue 现有的功能是几乎没有影响的。这也是得益于我们在 Vue 2 到 Vue 3 升级的时候,对于内部架构做了比较好的设计,保证了这种比较好的扩展性。

我们可以看到,在 Vapor 之前,Vue 内部依赖结构,我们有 compiler-core,compiler-core 上面有 compiler-dom,compiler-dom 上面又有 compiler-sfc。然后在 reactivity 这一块,这个响应式系统是最底下的依赖,然后 runtime-core 依赖响应式系统,runtime-dom 又依赖 runtime-core。

从现在来说,compiler-dom 和 runtime-dom 更应该被称之为 compiler-vdom 和 runtime-vdom。因为在 Vapor 进来之后,我们发现,其实要实现 Vapor,我们就只要加上 compiler-vapor 和 runtime-vapor。底下的 compiler-core 和 runtime-core,包含响应式系统是完全直接复用。这样的话,整个系统的可维护性还是非常强的。

虽然在做 Vapor 和 VDOM 兼容的时候,需要对原来的一些地方做一些改动,但是整体结构上而言,我们可以保证,在 Vapor Mode 接入实装之后,对现有的应用不会产生实质性的影响。也就是说,如果在目前 Alpha 阶段,可能还有一些细小的问题,但是在 Beta 发布的时候,我们的Ecosystem CI 应该是全过的。

所谓的不同的编译输出,到底是什么概念。同样的单文件组件的 source,上面是 VDOM 下的编译输出,下面是 Vapor 的编译输出。

VDOM 大家有所了解的话,有点像 React JSX 之后的输出,但是 Vue 3 的编译器会做一些静态分析的优化,比如说有 Block Tree 的优化,有 Patch Flags 的优化,像这些数字都是通过静态分析出来的,使得 VDOM 在运行时进行更新的时候,可以跳过不必要的操作。但是这些优化属于是在一个本身已经有大量 Overhead 的情况下,尽可能地去消减 Overhead。

Vapor 的 compiler output,是受了 SolidJS 的启发,同时我们发现 Svelte 5 的输出,跟这个也已经非常相似,所以大家会发现,它们的性能其实也已经变得很接近。

我们可以这样讲,Virtual DOM 的编译,我们是在一个相对慢的基础上,去进行一点一点的优化。那么 Vapor 就是反其道而行之,如果我们从头开始一个不同的编译,如果要我手写出性能最好的输出,我应该会怎么写?然后再把这个手写的最优的东西,设计成一个适合由编译器输出的格式,最后就得到了这样的。

我们可以来具体分析一个例子,这就是刚才那个 Vue 的 SFC 组件。我们在编译时候的第一步,是分析这个模板中连续的静态结构部分,并生成创建一个静态的 DOM 片段的工厂函数,这个 t0 是一个函数。

第二步,script setup 里面的这部分代码,其实我们原样照搬过来。然后你会发现,script setup 里面这部分代码,跟我们模板编译出来的代码,是直接放在一起的,在一个 scope 里面。所以编译出来的代码,你去阅读它的话,也挺容易理解的,而且也只执行一次。这个让我一直都觉得,这个比 React Hook 要符合直觉很多。

第三步,这边调用这个 n0,就是我们之前创建的这个片段的工厂函数,获得了一个实际的 DOM 节点。如果我们这个模板很大的话,其实这个里面大部分静态的内容,都会被包含在这里,这边就直接拿到了一块静态的 DOM 片段。

我们发现这个 msg 里面只有它的 Text Child,它的文字子节点是动态的,所以我们就直接把它给提取出来。提取出来之后,直接针对这个 Text node,进行响应式的绑定。

如果这个 Text node 嵌套的很深的话,我们这里第三步生成的代码,就会是用一个比较轻量的 helper,还是直接进行 DOM traversal(遍历),就是直接去抓到嵌套在很里面的那个子节点。那这样的话,我们获取子节点的这个操作的开销,只有在创建的时候执行一次。从此之后,我们就拿到了所有动态节点的引用。

在这个回调函数里面的这一部分,是每次更新的时候实际上会跑的。它会直接拿到我们之前已经取到的引用,所以在实际更新的时候,这个开销非常非常的低。 这其实就是 Vapor 的核心思路。其实难点就在于,如何从基于 Vue 的这个模板,去生成符合我们需求的这个编译输出,然后要去兼容我们现有的各种 API,然后更大的一个难点是,要在这个基础上,去支持 SSR Hydration,也就是在服务端渲染下的这个水合。

我们现在计划马上要发 3.6 Alpha(编者注:3.6 Alpha 已发布)。那么在 3.6 Alpha 里面,现在是个什么状态呢?

首先,不推荐你立马上生产,这个毕竟是个 Alpha。

只需要在现有的 script setup 上面加 vapor 属性,就启用了。如果你用的 API 是在 Vapor 下支持的 API,行为上其实你不会感觉到有什么变化,它就是变快了。

如果你想要创建一个纯 Vapor 应用,你可以用 createVaporApp。createVaporApp 的重点就是,它可以避免把 VDOM 运行时的这些代码拉进来。也就是说,如果你用 createVaporApp 去创建一个新的应用,大概只有 7kb 左右,所以它的 baseline 会比 VDOM 的 App 要小很多。

Async Component、Transition、KeepAlive、SSR hydration 等功能的目标是在 Beta 之前 merge 进去,现在都已经有实现有 PR,还在 review 过程中。merge 完了之后,我们 Beta 基本就处于一个可用的状态。

我们已经用 Vapor 做了一些 TodoMVC 和跑分,那实际有多快呢?我们拭目以待。Spoiler alert,最近这两天,刚 merge 了 Johnson 的几个大的优化 PR,在 merge 这些 PR 之前,已经跟 Svelte 5 和 SolidJS 基本上处于同一水平线,就是你跑多次的话,可能一会儿是 Vue 快,一会儿是 Svelte 快,一会儿是 SolidJS 快,这样的一个状态。那么在 Johnson 这些优化进去之后,Vapor Mode 应该 consistently 是最快的。我们希望发了 Alpha 之后,去跑一个官方的分,然后我们再去宣传这些数据。

Vite 生态

我们现在来聊聊 Vite 生态。现在有点黑啊(注:受电影院灯光影响),大家现在在用 Vite 的举个手。哎呀!我看不清楚。还在用 webpack 的请举个手。好像还是有那么几个。

我们也知道,Vite 这两年的增长是非常爆炸的。我们现在最新的数据是,周下载量 3,200 万,这个其实已经远远超过 Vue 的下载量。Vite 现在每月的下载量已经超过了一亿次,这个量其实我自己也没有想到,因为去年这个时候差不多是 13.5 个 million,也就是 1,350 万左右,过去一年将近增长了 140%。这个其实很大程度上得益于 AI Coding 的爆炸,现在有很多 Vibe Coding 的工具,像 Bolt.new、Replit、Lovable 这些在国外比较火的 Vibe Coding 工具,它们默认的应用搭建都是用 Vite,所以这部分增长其实受益还挺大的。那么也促使我们会去思考一些关于 Vite 怎么跟 AI 结合的问题,这个我们后面会提到。

在生态方面,我们发现去年这个时候,趋势就已经基本上非常明显,今年我们基本上会发现,大部分的工具都以 Vite 支持作为它们的一个首要重点。除了 Next.js 之外,所有的高层元框架,如 SvelteKit、React Router、SolidStart、TanStack Start 等等,现在都是基于 Vite。而且有相当多的这些框架,它们现在的设计方式是叫做 framework as a Vite plugin。也就是说,整个框架就是以一个 Vite 插件形式存在。那么如果你有一个基础的 Vite 应用,你可以通过简单的引入一个插件的形式,你就获得了一个框架。这其实也是在 Vite 这个生态中,现在越来越显著的一个趋势。

那么大家也知道,去年官宣我成立了一家公司,叫做 VoidZero。VoidZero 是专注于前端和 JS、TS 生态工具链开发的一家公司。现在主力维护 Vite、Vitest、Rolldown、OXC 这些项目的成员基本上都是 VoidZero 的雇员。当然 Vite Core Team 和 Vitest Core Team 里面,有一些不是我们的直接的员工,但是也跟我们有密切的合作关系。

为什么我会想要成立这样一家公司?

一直以来,我都对 JS 生态的碎片化是有一些想法的。就是我们做工具链做多了之后,尤其是在做 Vite 的时候,我自己也面对大量生态的兼容性问题。比如说用户用了这个插件,又跟 Vite 有什么不兼容;然后 Vite 内部 esbuild 跟 Rollup 这边、那边行为又会有不一样;然后有的用户要用 Babel,有的用户要用 SWC,这个行为又会有不一样;然后有的用户想用 Jest,有的用户想用 Mocha,然后跟 Vite 又有兼容性的问题。 包括当年做 Vue CLI 的时候,我们也做过把各种各样的工具粘在一起,想要给大家一个统一化的体验这样的事情,做了很多年。

其实很多时候这些痛点我自己也是非常切身有体会的。对于工具维护者是这样,那对于终端用户其实也是这样。那么对于在公司团队里面负责基建的同学,肯定也是花了很多时间在挖掘这些生态去对比方案,然后去把东西拼在一起,然后升级的时候又遇到兼容性问题,等等。

这个问题由来已久。那这个问题的根源是因为 JS 这个生态,它本身是没有一个中心化引导的。这个是一把双刃剑。

我觉得 JS 生态之所以能成为今天这样繁荣的状态,本质上是因为它的去中心化的,这是它的一个优点,这也是它的一个强项。

但是生态是会进化的。我们可以想象一下,从过去的十几年,差不多 JS 是从大家不把它当回事的一个脚本语言,慢慢的进化成了一个几乎是世界第一语言了。

我们用 JS 写前端、写后端,现在甚至在 AI 里面,最新的这些 Agent 里面也有好多都是用 TS 写的。然后现在很多主流的 Agent,都是直接用 NPM 发布的。所以,这个语言其实已经进化到了一个非常不一样的状态,它现在被赋予了很大的责任,它有很强的能力,同时我们也有很多的最佳实践,慢慢的被沉淀了下来。

JS 生态的庞大,它带来丰富的选择,但是它也带来了所谓的复杂税。这个复杂税呢,我认为随着最佳实践的沉淀,我们需要在这个生态中,一层一层的把这些沉淀下来,最佳实践去做成一个可以被广泛使用的一个标准化的方案。

所以这家公司的一个初心,就是我们认为现在做一体化供应链的时机到了。

这个时机还是挺重要的。其实跟大家聊起,关于 Vue 当年为什么能起来,我一直都说,在合适的时间做合适的事情很重要。那我希望 VoidZero 是在一个合适的时间点,开始去做这个努力。我们也不知道,之前有些公司他也想做同样的事情,他可能起步太早,时机不对,他没做成。但是我们现在是在这个时间点上去做一次这个认真的冲击吧。

VoidZero 整体架构

我们看一下 VoidZero 的整体架构。

底下有 OXC,是我们的 Rust 的基建层,它是一个语言工具链。包含了最底层的 Parser,是所有东西的基底。在这个 Parser 的基础上,我们有 Semantic Analysis 语义的分析,然后有 Transformer 转译。这里 Transformer 这个 scope,我们现在已经完成了。但其实过去一两年中,花了非常多的时间在 Transformer 上面,因为我们基本要把 Babel 大部分现有的 Transformer 都复刻一遍,包括了 TypeScript 的 transform,JSX 的 transform,TypeScript isolatedDeclarations,以及 Syntax Downleveling,把 ES 现在新的语法降级到 ES2015。这个工作量是非常巨大的。然后 Resolver、Linter,Linter 就是要对标 ESLint;然后 Formatter 现在也对标 Prettier,Formatter 也在做,差不多进度已经过半了。

在 Oxc 的基础上,我们有 Rolldown。大家也知道 Rolldown,现在 Rolldown-Vite 已经可有一个可用的版本了。如果大家还没试的话,推荐大家试一下,因为已经有不少人用在生产环境里了。在 Rolldown 基础上去支持 Vite,Vite 再去支持 Vitest,然后再把所有这些东西放一起,我们要做一个叫做 Vite+ 的东西,这个后面我们细讲。

那么目前的进度呢,刚才已经说了,OXC 其实每一个环节都是相当大的工作量。因为说实话,我们要从最底下开始做起,整个 Parser,然后通过 Parser 去支持 Linter、 Transformer、Minifier 等等。然后 Minifier 现在是 Alpha 的状态,但其实这个 Minifier 现在已经相当可用。在这个 Benchmark 里面,其实它跟主流的 Minifier 相比,其实也就只有 SWC 和 Terser 比它的压缩效率更好一些,但是在速度上,我们比 SWC 要快很多。

说到底,就是如果我们要从头做一个标准化的工具链,性能是一个基本。OXC 团队在抠性能这一块是很有水平的。我三天两头会看到说,哪个 PR 又把这个又变快了多少多少,他们用 CodSpeed 去 track 他们的性能提升。然后我们团队成员有的时候做着做着就发现,这边可以优化一下,那边可以优化一下。所以有的时候我们会发现确实有很多活要干,但是还是挡不住他们会这边那边优化一下,当然这个也是必要的。

OXC 的 Parser 比其他的 Rust Parser 要快三倍,Linter 比 ESLint 要快很多,其实应该比 Biome 也快不少。resolver、Transformer 这些其实都是老生常谈了。所以性能这种东西,大家自己升级上去之后就会感受的到,尤其是在 Rolldown-Vite 的情况下。

Rolldown 可能是我们公司成立以来,目前花的精力最多的一块,因为毕竟它会支撑整个 Vite 的未来。 Rolldown 的 scope 也是非常非常大的,为什么呢?

因为我们首先要从头写一个 Rust Bundler,其次我们要同时覆盖 esbuild 和 Rollup 的所有功能集。 esbuild 的功能级其实非常大,esbuild 是一个非常牛的项目,但很可惜它在做了这么多功能的时候,还是有一些功能它就是不加,比如说关于怎么去做高级 chunk splitting,怎么做 Module Federation 等等,以及它的 tree-shaking、dead code elimination,这些产物优化做得不太行。

esbuild 最大的问题就是它的产物优化会导致生产环境加载性能受到很大影响。

我们有一个合作方 Framer,他们之前是用 esbuild 去打包,他们是一个帮大家 LowCode 生成网站的公司。他们之前用 esbuild 打包用户生成的网站,有很大的性能问题,然后迁移到 Rolldown 之后,彻底解决了这些问题,同时又覆盖了原来的功能,甚至还能更快一点。

所以在 Rolldown 里面,我们除了基础的核心打包,和整合 OXC 这里的所有的 Transformer 和 resolver 级别的功能,其实高阶功能里面已经花了非常非常多的功夫,其实经过最新一轮优化,已经可以对标 Rspack。后面还会有更多的优化。大量的时间也花在兼容性对齐上,要对标 esbuild 和 Rollup 所有功能。

如果你用过 Rollup,你会发现 Rollup 的很多基础功能都是需要插件的,比如说像 Node resolve、CommonJS 的支持,包括像 define、inject,这些 Rollup 都是要通过插件去走,这些 Rolldown 都是内置的。很多东西其实一旦通过插件,它的性能开销就会变得很大,所以 Rolldown 会尽可能的把这些常用功能都做成内置。

Rolldown 现在已经 1.0 beta 相当一段时间,我们在这个过程中,在和很多生产环境中的用户测试 Rolldown 的稳定性,并且优化性能、优化产物。现在就是一个打磨的阶段。

说到跟 Vite 的整合,其实 Rolldown-Vite 已经可以在很多的生产项目中完整的替代 Vite。比如说另一个我们的合作方 Linear,大家可能知道或者用过这个进度管理工具 Linear,是现在非常非常流行的一个任务管理工具。他们其实是从 Vite Beta 的时候就开始用,一直用到现在。他们最近成功的切到了 Rolldown-Vite,可以打包他们的 20 多个 MB 的 JavaScript bundle。所以 Rolldown-Vite 生产环境的稳定性,已经得到一定程度的验证,所以我非常推荐大家现在试用一下。

我们目标是在下一个大版本的 Vite 里面,就会切到以 Rolldown-Vite 作为默认。所以你现在提前去试一下,发现兼容性问题,fix 的时候,等到我们切换的时候,你就不会有问题了。

我们再看一下 Rolldown-Vite 的一些性能案例。我们其实有一个 GitHub 仓库,专门 track 这些东西。 最快的一个典型例子 Excalidraw,这个可能不少同学也用过,他们是一个纯 React 应用基于 Vite,然后切到 Rolldown-Vite 以后,构建直接快了 16 倍。

很多类似的应用,也得到了这种相同级别的性能提升。我们会发现,有些公司的应用性能提升比较明显、比较大,十几倍,有些只有两到三倍。

三到四倍,我觉得可能是一个比较常见的区间。为什么?因为这取决于你之前用过多少插件。比如说 Linear,他切到 Rolldown-Vite 之后,构建快了 3.5 倍,其实他在纯 Rolldown-Vite 的基础上,他还在跑一个额外的 SWC 的 transform,因为他可能依赖 SWC transform 的一些旧的插件,比如说他们还在用 commons。就是在纯 Rust Bundler 下,如果你有额外的 JS 插件,那你会有大量的从 Rust 到 JS 调用的开销,以及在 JS、Rust 之间来回传递大量数据的开销。所以通常来说,取决于你用了多少这种可能做了额外 transform 的这些插件。

尤其是如果你用了一个基于 Babel 的 custom 插件,那这个性能Rolldown 是没有办法帮你优化的,这就会影响你整体获得的这个性能收益度。一般来说这些倍数比较低的,就说明他们可能用了比较多的这些额外插件。

长线来说,我们希望能够帮助社区,慢慢的去优化掉这些东西。

有时候你可能只是想要 parse 一下 AST,然后把一些东西替换掉,在整体构建里面,尤其是在纯 Rust 的环境下,这个开销挺大的。所以我们希望把这些常见的一些用例,能够让你用一个高效的 API 去解决掉它。 所以我们现在正在帮 Linear 看一下,能不能把他们的 SWC 这个额外的 parse 去掉,如果去掉的话,说不定也可以达到接近十倍的性能提升。我们会发现 Vue 应用的提升估计一般会在 3-4 倍左右,因为这里面现在还有一部分开销,是在 Vue 的编译过程中。虽然 Vue 的编译其实是挺快的,但是跟 Rust 比起来还是没有那么快。所以我们发现大部分的性能提升在 3-4 倍区间之内。

Rolldown 产物优化是我们最近花了很多力气的地方。因为之前 Vite 打包的产物的能力受限于 Rollup。Rollup 是一个纯 ESM 打包器,所以在一些大厂环境里面的一些用户,他们还是喜欢 webpack 或者 Rspack,他们主要的一些需求,就是 webpack 特有的一些高阶功能,像这个高级分包 Advanced chunks,那么现在 Rolldown 也具有对等功能。

Rolldown 这些产物优化的东西,是我们在 esbuild 和 Rollup 的基础上做的,进一步的。我们已经基本上完成了 esbuild 和 Rollup 所有能做的功能,现在我们再开始进一步,在它们之上,加一些原先 Vite 也不存在的功能。

StrictExecutionOrder 是关于严格的模块执行顺序。关注 Bundler 的同学,可能最近看到有过一篇文章,关于 Bundler 的一个叫做 scope hoisting 的优化,就是把多个 ESM 模块全部整在一个 function 的 scope 里面。这是一个非常常见的打包优化,但是 Edge Case 下它会导致没有办法按照原有的模块语义去按顺序执行。

那 StrictExecutionOrder 其实是我们团队云飞自己想出来的一个特定解决方案,可以在保留 ESM 以及保留 live binding 的前提下,去保证模块的严格执行顺序。

最近相君也做了 CJS 的 treeshaking,这个是之前 Rolldown 一直没有做的。对于还存在大量 CJS 模块的 code base,会有产物打包大小的提升,这个在 Rolldown-Vite 里面已经实装。

现在还有几个正在 work in progress 的一些功能,比如说像跨模块的 DCE(也就是 Dead Code Elimination),我们可以通过分析,比如 A 模块从 B 模块引入了一个常量,可能它在你的源码里是一个变量,但是在编译时,你用 define 把它替换成了一个常量,然后它被引用到了另一个文件里,在另一个文件里面,又用到了一个 if 里面。那就是说,在理想情况下,我们需要能够分析出来这个 B 模块中那个变量已经被替换成了一个常量,所以在 A 文件里,这个 if 它永远是 false,那这个 if 里面所有的代码,我们都应该可以去掉。所以我们要做到这种跨模块的分析,来优化这些情况。

最后还有一个功能叫做 import map chunking。不知道大家了不了解 importmap 这个浏览器的原生功能,它可以让你在浏览器里面有一段 script type="importmap",然后你用一个 JSON 格式去声明,比如说你 import Foo,然后把 Foo 映射到一个实际的文件路径上面去。实际上就有点像是浏览器里自带的运行时的一个 alias(别名),这个是基于原生 ESM 的。在有这个功能的前提下,我们也知道在生产环境打包的时候,我们很多出来的这些文件都是带 hash 的,在文件名后面有一大串哈希。那这些哈希是为了方便浏览器做长线的缓存。比如说每一次打包的内容变了之后,这个哈希都会变,那我们每次这个哈希就可以用强缓存,下一次打包时候哈希变了,我们就等于说加载的是另一个文件。

这里也会有一个问题,就是常见打包的优化中,会有一个叫做哈希的 Cascade,就是当我们分了多个拆包之后,比如说你的入口有一个 chunk,引用了 A chunk,然后引用了 B chunk,B chunk 里面一个文件变了之后呢,会导致入口 chunk、 A chunk 和 B chunk 的 hash 全都变了。因为 A 引用了 B,B 引用了 C,C 变了,那么 B 里面包含了 C 的 hash,所以 B 也变了,然后 A 包含了 B 的 hash,A 也变了。那你实际上,虽然只改了 C chunk 里的一个文件,你整个的应用基本上都缓存失效。所以利用 importmap,我们可以在生成这些 chunk 的时候,把它们互相之间的引用,B 引用 C 的时候,用的是一个不带哈希的一个路径,然后通过把这个不带哈希的路径,到带哈希的路径的映射,把它挪到 importmap 里面。 这样我们就可以使得你在重新构建时候,减少哈希失效的一个比例,更高的增加缓存的利用率。所以有非常多这些细节的东西,我们一开始的目标,就是要把 Rolldown 做成 the best of all worlds,就是我们既要又要,我们又要快,我们又要功能全,我们还要有好的产物。

之前的 Vite 我们可能在思考的时候还说,Vite 是有 trade off 的,如果你有这些需求,你可能不应该用 Vite。我们的长期目标是:不,你所有情况你都该用 Vite。

另一个正在开发的就是 Full Bundle Mode。这个可能大家也知道,Vite 一开始是以 Native ESM(原生 ESM)不打包的开发服务器而出名的。但是我们会发现它有它的优势,也有它的劣势。

在超大型的应用中,比如说像我们这个客户 Linear,他这个应用太大了,他可能要加载几千个模块。又比如说 GitHub,GitHub 现在也在把他们的这个构建工具要迁移到 Vite,GitHub 他们又有一些特殊情况,比如说 GitHub 要求他们所有的员工在内部开发时候都是远程的,他们用一个叫 Codespaces 的远程 IDE 去工作。

他们把 Vite 的页面加载到本地的时候,所有的模块都是通过一个远程的 network 去加载,这就使得 unbundled 模式会带来网络层面很大的开销,影响代理下页面 load 的性能。所以 Full Bundle Mode,其实就是又变成像 webpack 那样,可以直接把整个东西都打包,去优化这个 load 的性能。

那这个模式,其实更多的就是针对这些超大型的应用。如果你现在觉得页面加载没有问题,那 Full Bundle Mode 对你来说,其实没有太大意义。但是对于一些超大型应用来说,这是他们的一个刚需。

为了解决 Full Bundle Mode,其实我们还发明了一个基于 ESM 的,新的热更新的格式,然后我们还在这个基础上,做了增量构建,以及后面还要做 lazy 构建,也就是说即使要做打包,也可以像 unbundled 的模式一样按需的,就是如果你有 code splitting,Dynamic import 的部分在被用到之前就不需要被打包。

讲完 Rolldown,我们前段时间还发布了 Oxlint 1.0。Oxlint 是基于 OXC 的一个 Rust Linter,基本上是完全兼容 ESLint 的。比如说你如果现在有 ESLint rules 的话,你可以支持超过 500+ 的主流 rule。但现在对于 Vue 用户来讲,他的一个问题就是,对 Vue 只有一个非常基本的支持,他可以 Lint Vue 文件,但是它不能够理解你的 script 里面的东西跟模板里面表达式之间的关系,这个目前还没有支持,这个后续会计划支持。

其实目前 Oxlint 的用户,主要也是超大型应用的这些场景,就比如跑 ESLint 要跑几分钟的这种公司,他们跑 Oxlint 之后就发现,原来几分钟现在几秒钟,这个对于他们的开发流程影响是非常大的。

目前来说,Oxlint 另一个重大的在开发中的用户呼声比较高的需求,就是自定义的 JS 插件,或者说自定义 rule 的支持。因为我们也发现很大部分的这些大厂或者说是生产环境团队,都多多少少有一些自定义的 ESLint 规则。我们希望提供一个 API 让他们能够把这些规则跑在 Oxlint 里面。

这里面就涉及一些,怎么样让用 JS 写的自定义规则在 Rust Linter 里面跑的时候也可以快呢?

因为很多时候就是你一跑 JS 插件 Rust 工具就慢,所以这是一个非常大的挑战。我们其实有一些黑科技在研发之中,快做完了,有一个东西叫做 Raw AST transfer,感兴趣的同学可以去了解一下。

大概的说一下,就是在 Rust 这边 Parser parse 完了之后,AST 是存在于 Rust 侧,那我们怎么样把它丢给 JS 插件去使用呢?如果我们直接摸过去的话,会需要进行一遍内存的里面的这个拷贝,其实内存拷贝通常来说是性能杀手,就是损耗最大的部分。

那我们做了 Raw AST transfer 就是得益于 OXC,它的 Parser AST 是放在一段连续的内存里面,叫做 Arena。然后我们可以把整段内存,用一个shared array buffer 直接丢给 JS 进程,然后在 JS 进程侧我们写了一个 lazy deserializer,当你访问这个 AST 的时候,它去把这段内存,按需的反向解码成 JS 对象,然后让你去使用。这也是一个工作量巨大的事情,但是已经快完成了。

所以 JS 插件,我们很快就应该可以对它的性能有一个大概的评估。然后以及计划中的,我们希望 Oxlint 后续能跟 TSSLint 合作,去做 Type-aware,因为 Oxlint 是一个纯语法的 Linter,它不支持 TypeScript ESLint 这些规则,也就说 Type-aware linting,我们认为 TSSLint 是目前情况下的最优解。(注:目前最新的方向是和 tsgolint 整合,基于 tsgo 直接用 Go 写 lint rules 整合进 Oxlint。)

后面 Johnson 的演讲里面也会对 TSSLint 做进一步的分享。

还有就是 tsdown。tsdown 是 TypeScript 的打包器,它是 tsup 的继承者,基于 Rolldown。它核心的一个亮点就是 DTS bundling。首先通过 OXC 的 isolatedDeclarations transform,如果你启用了 isolatedDeclarations,我们就可以绕过 TSC,直接用 OXC 的 transform 去帮你生成 DTS。然后 tsdown 里面又内置了 rolldown-plugin-dts,可以把你的 DTS 进行打包。plugin-dts 目前是基于 JS 实现的,但是我们后续也会把它 port 到 Rust。那么 TS 打包的性能就会比走 TSC 要快一个量级。这个在智子后面的演讲里面也会有更深入的分享。

Vite+

最后就是我们想要做的这个 Vite+。从定位上来讲 Vite+ 就是类似于 Cargo for JavaScript。写过 Rust 的同学可能会用过 Cargo。在用 Rust 的时候,你会发现一些核心的功能诉求,Cargo 都已经帮你包圆了。那我们去引一个新的 Rust 项目的时候,你不需要自己去选一个构建工具,选一个测试工具,选一个 Linter,选一个 formatter,选一个生成项目的东西,再去选一个帮你去做 build Cason 的东西。这些事情,其实你在写 Rust 的时候,工具链已经帮你解决掉了。

JS 为什么没有这样的体验呢?为什么 JS 不能有这样的体验呢?这就是我们想要去回答的问题。 在定位上,Vite+ 是 Vite 的严格超集。也就是说,如果你现在用 Vite,你直接把 Vite+ 替换进去,它的行为是一模一样的。严格来说它会有更多的功能。

也就是说,你可以用 vite test 去替代 Vitest,你可以用 vite lint,vite lint 底下跑的其实就是 Oxlint 加 TSSLint,vite lib 下面跑的是 tsdown,vite new 是在结合了整套工具的前提下,去帮你搭建一个最佳实践的这种 TS Monorepo。

然后 vite task 就是类似于 Turborepo。但是区别就在于 vite task 会跟我们的这些工具做第一方的深入整合,vite task 它的 Cason 是理解我们底下这些工具,这些工具跟 vite task 会有第一方的整合,使得你大部分情况下,比如你跑一个 vite build,你不需要去手动声明说我的这个 input、output 是什么,它就可以精确的知道你用了哪些文件,然后去做对应的这个 cache 的 evaluation。然后还会有 GUI devtools 用可视化的方式去帮你分析你的这些工具中的问题。那这个部分 Anthony 在今天晚点的演讲里面也会有分享。

最后还有一块就是在统一工具链的这个前提下,我们会发现如果你用 AI 去 Coding,现在我们如果工具链是碎片化的,那 AI 其实也会面临很多挑战。

你会需要去写很多 prompt 或者说是 rules,去告诉 AI 测试工具用的是这个,Linter 用的是这个,构建用的是这个,然后这两个东西一起用的时候你要注意这个,那两个东西一起用的时候要注意这个,升级的时候注意不要升这个,不要动那个。就是会有很多这样细小的东西,AI 在碎片化的工具链下,AI 会不那么得心应手。

如果你用的是比较小众的某一个工具的话,AI 可能不知道怎么用,或者每次用的时候他自己要先跑去看文档,然后理解得可能又不到位。所以我个人认为一个统一的工具链,其实也是更 AI 友好。

因为这样的话,AI 就可以知道说、,我既然跑在 Vite+ 的这个项目里面,我所有的这些事情,都是有既定的这些规则可以去遵循的,那么出岔子的概率就会小很多。

另一方面就是当一个统一的工具链考虑到 AI 的这个兼容性去设计的时候,我们可以做很多针对 AI agent 的优化。比如说,在 AI 需要查询为什么这个文件会被引入,可能默认情况下,它得把所有的这个隐喻链把整个文件全部读一遍,那可能消耗了很多 token,一下子烧了几千几万个 token。但是如果有 Vite+ 的话,他可以直接问 Vite+,把这个文件的 import 的路径给我 print 出来,Vite+ 直接就给他。或者说,他要在代码库里面查找,或者是问一些配置相关的问题,或者说他要帮你 Monorepo 里面创起一个新的应用,包起一个新的库,这些都可以由工具链来直接提供这些能力,而使得 AI 可以避免自己去用他的粗糙的方式去手写,而直接调用工具链的能力来实现。那这样的话,精确度会更高,消耗的 token 也会更少。 最后就是每次 AI 写完了东西之后,你要验证它,那肯定是要跑 Linter 跑 test。这些我们也都可以直接做成工具链,供 AI 调用的这些 API,直接让 AI 去调。然后在知道是 AI 在调用的时候,我们的输出也可以做更进一步的优化。

比如说你跑一遍测试几万行的输出,那是给人看的,其实对于 AI 来说,他想知道的可能就是有没有错误,如果有错误的话,定位在哪一行, stack trace 是什么?那我们在知道是 AI 在做这些事情的时候,我们就可以针对性的去做一些信息的压缩,以及做信息的结构化。

比如说,如果我们原来输出的是一大段给人阅读的东西,现在我们可以输出一些 JSON 格式的或者 XML 格式的东西,直接告诉 AI,遇到了一个 Lint error,然后在哪个文件第几行,可能的原因是什么;遇到了一个测试的 error 是怎么样;浏览器里有一个 error,然后都用格式化的信息去给 AI,帮助 AI 更好地理解,更精确地理解。

所以,这一块还有很多值得探索的东西,但是从整体的大方向来说,我们希望把跟 AI 的这个亲和度,作为工具链的一个 first-class concept,就是设计的一个原则之一。

Vite+ 现在还在开发的过程中,但是我们也会发现底下核心的这些功能,其实就是我们过去这一两年在慢慢搭建的过程。所以我们是先把底下的积木一块一块的弄出来,然后再把它们搭到一起,做成一个完整的东西。 所以希望大家也可以看到我们整个的这样的一个思路,是怎么样的。那么也希望大家在 Vite+ 出来之后,能够来帮我们一起去验证它的可行性。

再往更大的一块讲,当 Vite+ 发布之后,我之前也说到了现在有很多框架,他们的设计是 framework as a Vite plugin。那么在当 Vite+ 出现之后,那么这些框架在 Vite+ 中也可以得到相应的能力拓展。 比如说一个框架它可以往 Vite+ 的 Lint 里面,去注入它自定义的 Lint rules;在 Vite 里面我们现在有 Environment API,那么可以把运行时环境给抽象掉,结合 natural。然后那针对这个测试,我们也可以让框架直接在插件里面,去注入对应的测试环境,如果是 Vue 的话,可以注入一些 Vue 专用的 test 的 helper,如果是 React 也可以注入 React test helper。

框架还可以在 Devtools 里面,去注入他们自己的 Devtools 的工具,来帮助用户可视化一些东西的理解。比如以后 Vue 可以在里面注入这个响应式追踪的这些功能等等。

所以 Vite+ 长线来说也可以是一个 framework for frameworks。以上就是我们这个已经做的事情、想做的事情,希望大家能够 keep an eye on what we're doing,然后谢谢大家!

相关推荐
李明卫杭州6 分钟前
Sass颜色函数介绍
前端
pe7er15 分钟前
使用CDN、ImportMap增强Vue playground
前端
ze_juejin25 分钟前
Angular的懒加载由浅入深
前端
JSON_L25 分钟前
Vue 详情模块 4
前端·javascript·vue.js
码间舞32 分钟前
什么是Tearing?为什么React的并发渲染可能会有Tearing?
前端·react.js
gnip43 分钟前
做个交通信号灯特效
前端·javascript
小小小小宇44 分钟前
Webpack optimization
前端
尝尝你的优乐美1 小时前
前端查缺补漏系列(二)JS数组及其扩展
前端·javascript·面试
咕噜签名分发可爱多1 小时前
苹果iOS应用ipa文件安装之前?为什么需要签名?不签名能用么?
前端
她说人狗殊途1 小时前
Ajax笔记
前端·笔记·ajax