Vite 2.0 技术解析:框架无关核心、依赖预构建、CSS 一等支持与 SSR 的落地
本文以 Vite 官方 2.0 发布公告为骨架,完整拆解 Vite 2.0 的五大核心能力——框架无关的核心架构、Rollup 兼容的新插件体系、esbuild 驱动的依赖预构建、一等公民级 CSS 支持以及实验性 SSR——并结合当前仓库中的源码实现(依赖预构建调度器、SSR 模块加载、legacy 插件 等)逐项印证这些设计在代码层面是如何落地的。读完本文,你将理解 Vite 2.0 各特性的设计动机与实现原理,并能对照仓库源码定位其关键实现位置。
背景:Vite 2.0 在 Vite 演进史中的位置
Vite(法语中"快速"的意思,发音 /vit/)是一种面向前端 Web 开发的新型构建工具:可以理解为一个预配置好的开发服务器 + 打包器组合,但更轻、更快。它利用浏览器的原生 ES Modules 支持,以及 esbuild 等编译到原生语言的编译工具,提供流畅的现代开发体验。
公告中特别强调了一点:由于团队在 1.0 发布前就决定彻底重构内部实现,Vite 2.0 实际上是 Vite 的第一个真正稳定的版本(first stable release)。也就是说,1.x 时期的快速迭代都被视为 RC 前的探索,2.0 才是架构定型后的起点。公告同时建议:
- 想了解 Vite 设计动机的读者,阅读 Why Vite;
- 想看后续演进,可继续阅读 Vite 3.0 发布公告。
当前仓库中的 vite 包 版本已演进到 8.x(描述为 "Native-ESM powered web dev build tool"),底层工具链也已从 esbuild + Rollup 统一迁移到 Rust 编写的 Rolldown。但 2.0 奠定的五大能力——框架无关核心、插件格式、依赖预构建、CSS 支持与 SSR——至今仍是 Vite 的架构支柱,这也是本文以 2.0 公告为主线的价值所在。
框架无关的核心(Framework Agnostic Core)
从 Vue 专用原型到框架无关
公告中回顾了 Vite 的起源:最初它是一个"服务于 Vue 单文件组件(SFC)的原生 ESM hack 原型",Vite 1 则是这个思路的延续,在其上实现了 HMR(热模块替换)。
Vite 2.0 吸取了早期经验,从零重新设计了内部架构,核心原则是:框架无关(framework agnostic),所有框架特定的支持全部委托给插件处理。公告发布时已提供 Vue、React、Preact、Lit Element 的官方模板,Svelte 集成也在社区推进中。
在当前仓库中的印证
这一"框架无关"的设计在仓库目录结构中依然清晰可见:
- packages/create-vite 中的官方模板覆盖 vanilla、vue、react、preact、lit、qwik、solid、svelte 等全家桶,每个框架各有一对 JS/TS 模板(如
template-react与template-react-ts),模板本身只是静态脚手架,不依赖 Vite 内部的框架逻辑; - 框架相关能力全部以插件形式存在:仓库根目录下的 monorepo 包含 plugin-legacy 等官方插件包,而 Vue/React 等框架编译器插件则交由各自的社区插件包(如
@vitejs/plugin-vue等,不在本仓库内)实现,Vite 内核只负责加载与编排插件。
从 create-vite 的 package.json 可见,模板包本身只是一个 CLI(create-vite / cva 命令),职责就是复制模板并安装依赖——这正是"核心不感知框架"理念的体现。
新的插件格式与 API
设计:扩展 Rollup 插件接口
公告指出,新的插件系统"受 WMR(Preact 团队)启发,扩展了 Rollup 的插件接口",关键特性包括:
- 与大量现有 Rollup 插件开箱兼容——现有生态的 Rollup 插件可以近乎零成本地在 Vite 中工作;
- 插件可以使用 Rollup 兼容的钩子,同时拥有 Vite 特有的钩子和属性,用于调整 Vite 独有行为,例如:
- 区分 dev(开发)与 build(构建)两种场景;
- 自定义 HMR 处理逻辑;
- 改进后的编程 API,方便在 Vite 之上构建更高层的工具和框架。
在当前仓库中的印证
- 插件体系的核心实现在 packages/vite/src/node/plugin(插件解析与 hook 调用编排),以及内置插件目录 packages/vite/src/node/plugins 下;
- 该目录下的
css.ts、importAnalysis.ts、esbuild.ts、worker.ts、manifest.ts等文件正是公告中"框架无关核心 + 内置能力插件化"思路的具体产物——每个能力都是一个独立插件,而非硬编码进服务器主流程; - 插件 API 与编程 API 的完整文档见 插件 API 和 JavaScript API。
值得注意的架构细节:从 plugins 的组织方式可以推断,Vite 内置插件按"功能域"切分(解析、CSS、资源、Worker、Manifest……),框架作者只需在 Rollup 兼容钩子之上实现自己的插件即可接入整条管线,这正是 2.0 公告所承诺的扩展性。
esbuild 驱动的依赖预构建(Dep Pre-Bundling)
这是 2.0 公告中性能提升最显著的一项。
为什么需要预构建
Vite 是基于原生 ESM 的开发服务器,浏览器会直接以 ESM 方式加载模块。这带来两个必须解决的问题(当前仓库的 依赖预构建文档 完整解释了这一点):
-
CommonJS / UMD 兼容性:开发时所有代码都以原生 ESM 形式提供,因此必须以 ESM 形式先转换 CJS/UMD 依赖,并做"智能 import 分析",使得对动态赋值导出的 CJS 模块(如 React)的命名导入也能正常工作:
// works as expected import React, { useState } from 'react' -
性能:一些包把 ESM 构建拆成数百个内部互相引用的文件(文档以 lodash-es 有 600+ 内部模块为例),如果不预构建,浏览器会同时发起数百个 HTTP 请求,造成网络拥塞、页面明显变慢。预构建将其合并为单一模块后,只需 1 个请求。
2.0 的关键变化:esbuild 取代 Rollup
公告明确指出:此前 Vite 1 用 Rollup 做预构建,2.0 改用 esbuild,使依赖预构建速度提升 10-100 倍。公告给出的参考数据:在一个包含 React Material UI 等重依赖的测试应用上冷启动,M1 MacBook Pro 上从 28 秒降到约 1.5 秒。
在当前仓库中的印证
预构建的完整实现位于 packages/vite/src/node/optimizer,包含:
- optimizer.ts:预构建调度器。可以看到
createDepsOptimizer会为每个开发环境创建独立的优化器实例,通过 100ms 的 debounce 合并"新发现依赖"触发的重复重打包,并在重打包完成后自动触发页面重载(对应 文档 中"服务器运行中发现新依赖会重新 pre-bundle 并刷新页面"的描述); - scan.ts:依赖扫描器,即"自动依赖发现"(爬取源码中的 bare import 作为预构建入口)的实现;
- rolldownDepPlugin.ts:当前版本中预构建打包器相关的插件逻辑。
这里有一个重要的版本事实:当前仓库中的 Vite 已用 Rolldown(Rust 编写)替代 esbuild 作为预构建引擎——依赖预构建文档 明确写道 "The pre-bundling is performed with Rolldown, so it's typically very fast"。也就是说,2.0 时"esbuild 带来 10-100x 提升"的演进方向,后来进一步延续为"全链路 Rust 化",但预构建的整体模型(发现 → 扫描 → 打包 → 缓存 → 服务)保持不变。
配套的定制能力也值得掌握(详见 dep-optimization-options):
optimizeDeps.include/exclude:显式包含/排除依赖,典型场景是插件 transform 后才产生的、初次扫描发现不了的 import;- 缓存:预构建产物缓存于
node_modules/.vite,是否重跑取决于 lockfile 内容、patches 目录修改时间、vite.config.js中相关字段、NODE_ENV等;可用--force强制重打包。
一等公民级的 CSS 支持
公告列出了 Vite 2.0 对 CSS 的三项开箱即用能力,这些能力在 内置 CSS 插件 中实现:
- 解析器增强(Resolver enhancement):CSS 中的
@import和url()路径会经过 Vite 的 resolver 增强,使其遵循别名(alias)配置并能解析 npm 依赖中的资源; - URL rebasing:
url()中的路径会自动基于"资源最终服务位置"而非"导入方文件位置"重新计算,因此无论从哪个目录导入同一个 CSS,其相对资源引用都指向正确; - CSS 代码分割(CSS code splitting):一个代码分割的 JS chunk 会同时产出对应的 CSS 文件,请求该 JS chunk 时对应的 CSS 会被自动并行加载。
公告的原始表述(完整继承):
Vite treats CSS as a first-class citizen of the module graph and supports the following out of the box:
- Resolver enhancement:
@importandurl()paths in CSS are enhanced with Vite's resolver to respect aliases and npm dependencies.- URL rebasing:
url()paths are automatically rebased regardless of where the file is imported from.- CSS code splitting: a code-split JS chunk also emits a corresponding CSS file, which is automatically loaded in parallel with the JS chunk when requested.
这三项能力在仓库中均有对应的测试用例可验证,例如:
- CSS 代码分割:playground/css-codesplit 与 playground/css-no-codesplit;
@import、url()rebasing、模块化 CSS(.module.css)等:playground/css 及其 子目录(含 less/sass/stylus 预处理链)。
服务端渲染(SSR)支持
公告中的设计要点
Vite 2.0 随附实验性 SSR 支持(完整文档见 SSR 指南),核心设计有三点:
- 高效的 ESM 源码加载与更新:Vite 提供一组 API,让 Node.js 端在开发期间能高效加载并更新基于 ESM 的源码——"几乎像服务端 HMR":编辑文件后 SSR 输出立即更新,无需重启;
- 依赖外部化(externalization):自动将 CommonJS 兼容的依赖 externalize 掉(即用 Node 原生的
require加载),从而提升开发服务器响应速度和 SSR 构建速度; - 生产服务器与 Vite 解耦:生产环境的服务端可以完全不依赖 Vite 运行,同一套配置也容易被改造为预渲染 / SSG。
公告还强调:Vite SSR 是作为**低层能力(low-level feature)**提供的,预期是更高层的框架在其之上构建(例如当时的 Nuxt 3、SvelteKit 等)。
在当前仓库中的印证
SSR 相关源码集中在 packages/vite/src/node/ssr:
- ssrModuleLoader.ts:SSR 模块加载器,对应"Node.js 端加载与更新 ESM 源码"的能力;
- ssrTransform.ts:SSR 场景下的模块转换(将浏览器端的 import/导出语义转译为 Node 可执行的 ESM);
- ssrManifestPlugin.ts:SSR manifest 生成,支撑"生产服务器与 Vite 解耦"——构建时生成资源清单,生产端按清单按需加载 CSS/资源即可,无需 Vite 本身;
- fetchModule.ts 等文件则服务于"按需拉取模块"的场景。
依赖外部化的策略由 external.ts 与 nodeResolve.ts 共同决定;行为可用 playground/ssr 与 playground/ssr-deps 中的测试用例交叉验证(后者专门覆盖 SSR 场景下依赖 externalize 的各种组合)。
可选的 Legacy 浏览器支持(@vitejs/plugin-legacy)
Vite 默认目标是支持原生 ESM 的现代浏览器,但通过官方插件 @vitejs/plugin-legacy 可以**选择性地(opt-in)**为旧浏览器提供兼容。公告中的原始承诺:
The plugin automatically generates dual modern/legacy bundles, and delivers the right bundle based on browser feature detection, ensuring more efficient code in modern browsers that support them.
即:自动生成 modern/legacy 双份构建产物,基于浏览器特性检测分发正确的产物,确保支持 ESM 的现代浏览器继续拿到高效的原生 ESM 代码。
当前仓库中该插件位于 packages/plugin-legacy,README 完整说明了其默认行为与全部选项:
默认行为(引用自 plugin-legacy README):
- 为最终 bundle 中的每个 chunk 生成对应的 legacy chunk,经
@babel/preset-env转换并以 SystemJS 模块形式产出(仍支持代码分割); - 生成 polyfill chunk,包含 SystemJS 运行时以及根据浏览器目标与实际使用(
useBuiltIns: 'usage'检测)决定的必要 polyfill; - 向生成的 HTML 注入
<script nomodule>标签,仅在特性缺失的浏览器中加载 polyfills 与 legacy bundle; - 注入
import.meta.env.LEGACY环境变量(仅 legacy 生产构建中为true)。
典型用法:
// vite.config.js
import legacy from '@vitejs/plugin-legacy'
export default {
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
}),
],
}
核心选项速览(完整说明见 plugin-legacy README):
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
targets |
string | string[] | { [key: string]: string } |
'last 2 versions and not dead, > 0.3%, Firefox ESR' |
传给 @babel/preset-env,决定 legacy chunk 的语法降级目标,兼容 Browserslist 语法,会自动读取项目的 browserslist 配置 |
modernTargets |
string | string[] |
'edge>=105, firefox>=106, chrome>=105, safari>=16.4, chromeAndroid>=105, iOS>=16.4' |
收集 modern chunk polyfill 时的目标;仅在 renderLegacyChunks 为 false 时使用 |
polyfills |
boolean | string[] |
true |
是否/如何生成 legacy polyfill chunk;传字符串数组可显式指定 core-js 模块 |
additionalLegacyPolyfills / additionalModernPolyfills |
string[] |
— | 手动追加 DOM API polyfill(usage 检测只覆盖 ES 语法特性) |
modernPolyfills |
boolean | string[] |
false |
为 modern 构建单独生成 polyfill chunk;README 明确提示 core-js@3 的自动检测非常激进,不建议无脑开 true |
renderLegacyChunks |
boolean |
true |
设为 false 可只输出 modern 构建,把插件降级为"仅向 modern 构建注入 polyfill" |
externalSystemJS |
boolean |
false |
设为 true 时不内联 systemjs/dist/s.min.js,改为外部引用 |
renderModernChunks |
boolean |
true |
设为 false 时只输出 legacy bundle(也适用于 file: 协议下本地打开的场景) |
另外两点实操细节:使用 terser 压缩时需安装 terser;若项目有严格 CSP,需引入导出的 cspHashes 将内联脚本哈希加入 script-src 白名单(值会随次版本变化,建议动态生成而非硬编码)。
测试验证可参考 playground/legacy(含 __tests__ 下的 10 个测试文件,覆盖多输出、自定义文件名、modern target 等场景)。
快速上手:一分钟启动 Vite 应用
公告给出的入门方式(发布时要求 Node.js >=12):
npm init @vitejs/app
需要说明的适用前提:npm init @vitejs/app 是 Vite 2.0 时代的命令;在当前仓库对应的版本中,官方脚手架已演进为 create-vite,命令为:
npm create vite@latest my-app
当前 create-vite 包 的 bin 字段暴露了 create-vite 与 cva 两个命令,且 engines 要求 node ^20.19.0 || >=22.12.0。模板目录(template-vanilla、template-vue、template-react、template-svelte 等)均可在 packages/create-vite 下直接查看,每个模板都附带最小可运行的 index.html、package.json 与框架入口文件,是理解"框架无关核心 + 模板即框架适配层"最直接的教材。
小结:2.0 五大能力在今天的映射
| 2.0 能力 | 2.0 时的形态 | 当前仓库中的位置 |
|---|---|---|
| 框架无关核心 | 内核去 Vue 化,框架能力插件化 | packages/create-vite 模板族 + 外部框架插件 |
| 新插件格式 | 扩展 Rollup 插件接口,兼容现有 Rollup 插件 | packages/vite/src/node/plugin.ts、内置插件 |
| 依赖预构建 | esbuild 引擎,10-100x 提速 | packages/vite/src/node/optimizer(引擎现为 Rolldown) |
| 一等 CSS 支持 | 解析增强 / URL rebasing / CSS 代码分割 | css.ts、playground/css-codesplit |
| SSR 支持 | 实验性,低层 API | packages/vite/src/node/ssr、playground/ssr |
| Legacy 浏览器 | @vitejs/plugin-legacy 双产物 | packages/plugin-legacy |
Vite 2.0 公告的价值在于它定义了 Vite 从"快而巧"走向"稳而全"的架构基线:框架无关让它可以成为任何框架的底座,Rollup 兼容的插件格式让生态平滑迁移,预构建让原生 ESM 开发体验可用,CSS 与 SSR 补齐了生产级需求。对照当前仓库源码可以确认:这些能力不仅被保留,而且被持续加深——预构建从 esbuild 演进到 Rolldown,SSR 从实验性能力成长为支撑整个生态框架的核心运行时,这正是阅读 2.0 公告与源码对照时最值得体会的一点。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00