Vite 5.1 发布解析:Runtime API、性能飞跃与 API 收敛
Vite 5.1 发布于 2024 年 2 月 8 日,是 Vite 5 系列中最具技术分量的一个版本:它引入了后来演化为 Module Runner / Environment API 的实验性 Runtime API、让 build.assetsInlineLimit 支持回调函数、将 CSS 预处理器搬进线程池,并在一万模块的冷加载基准上再创纪录。本文基于 Vite 官方发布公告(docs/blog/announcing-vite5-1.md)逐条解析这些特性,并结合当前仓库中的源码实现,说明每一项功能在代码层面是如何落地、以及它如何影响你在 monorepo、SSR 和大型项目中的实际开发体验。
版本背景与快速入口
Vite 5 发布于同年 11 月,是 Vite 生态的一次重大跃升。发布 5.1 时,Vite 已达成每周 1000 万 npm 下载量与 900 名贡献者的里程碑。对首次接触 Vite 的读者,官方建议先阅读 Getting Started 指南 与 Features 指南;完整的变更记录见 packages/vite/CHANGELOG.md。
Vite Runtime API:与 Server 解耦的模块执行层
5.1 最值得关注的新特性是实验性的 Vite Runtime API:它允许任意代码先经过 Vite 插件管线处理后再执行。它区别于 server.ssrLoadModule 的关键在于运行时实现与 dev server 完全解耦——库作者和框架作者可以在 server 与 runtime 之间自建通信层(message channel、fetch、直接函数调用或 websocket 均可),并且:
- 支持 SSR 场景下的 HMR;
- 单个 server 不再受客户端数量限制,每个 client 拥有独立的模块缓存;
- 不依赖 Node/Bun/Deno 的任何内建 API,可在任意环境运行;
- 易于集成到自带代码执行机制的工具中(例如可以传入一个用
eval代替new AsyncFunction的 runner)。
这条路线并非凭空而来:最初的设想由 Pooya Parsa 提出,由 Anthony Fu 以 vite-node 包实现,用于支撑 Nuxt 3 的 Dev SSR,随后成为 Vitest 的底座——即“vite-node”这一思想已经被长时间的生产环境验证过。5.1 中的 API 是 Vladimir Sheremet 基于 Vitest 中重新实现 vite-node 的经验所做的新一代迭代,相关设计讨论历时约一年。
重要演进:正如公告中的说明,Vite Runtime API 最终演化为 Module Runner API,作为 Environment API 的一部分在 Vite 6 中正式发布。当前仓库中已经可以完整看到这条演进链:docs/guide/api-environment.md 描述 Environment API,docs/changes/ssr-using-modulerunner.md 记录了
server.ssrLoadModule被moduleRunner.import(url)取代的迁移说明,核心实现位于 packages/vite/src/module-runner/index.ts 与 packages/vite/src/node/ssr/runtime/serverModuleRunner.ts。如果你今天基于 Vite 5.1 使用该实验 API,迁移路径就是指向这套 Environment/Module Runner 体系。
新特性
.css?url 支持改进
将 CSS 文件以 URL 形式导入(import url from './style.css?url')现在可以可靠且正确地工作。公告特别指出,这是 Remix 迁移到 Vite 所遇到的最后一道障碍(对应 issue #15259)。对于依赖“引用 CSS 资源 URL 而非注入样式”的框架(尤其是服务端渲染框架中按 URL 下发样式的模式),这一修复消除了此前的不确定性。
build.assetsInlineLimit 支持回调
此前 build.assetsInlineLimit 只能是一个字节数阈值;5.1 起可以传入一个返回布尔值的回调,针对特定资源选择内联或走文件,返回 undefined 时则回落到默认逻辑。当前仓库中该逻辑的实现位于 packages/vite/src/node/plugins/asset.ts:
const { assetsInlineLimit } = environment.config.build
if (typeof assetsInlineLimit === 'function') {
const userShouldInline = assetsInlineLimit(file, content)
if (userShouldInline !== undefined) {
return userShouldInline
}
} else {
limit = Number(assetsInlineLimit)
}
也就是说回调签名为 (file: string, content: string) => boolean | undefined:回调拿到文件路径与二进制内容,决定“是否内联”;不表态(返回 undefined)就把决定权交还给大小阈值。典型用法例如“logo 类 svg 一律走文件、小图标一律内联”。参数完整说明见 docs/config/build-options.md。
循环导入下的 HMR 改进
Vite 5.0 中,位于循环导入链内、即使声明了 accept 的模块一旦发生变更也会触发整页刷新。5.1 放宽了这一限制:允许 HMR 在客户端正常应用而无需整页刷新;但若 HMR 应用过程中出现任何错误,仍会回退到整页刷新作为兜底(对应 #15118)。该行为对应的测试场景可见 playground/hmr/accept-exports/ 下的多个用例,其中覆盖了 self-accept、accept 导出等循环依赖组合。
ssr.external: true 强制外部化所有 SSR 包
Vite 历史行为是“外部化所有包、但保留 linked packages(workspace 内软链依赖)”。新选项 ssr.external: true 可以强制把所有包(含 linked packages)都外部化。公告给出两个典型场景:
- monorepo 测试中,模拟“所有包均被外部化”的常规生产行为;
- 通过
ssrLoadModule加载任意文件时,若不在意 HMR,希望包始终按 Node 原生方式 require/import。
当前仓库中该判断入口位于 packages/vite/src/node/external.ts 与 SSR 环境配置解析路径中:ssr.external 被设置为 true 时直接短路“按包名外部化”的默认逻辑。参数说明见 docs/config/ssr-options.md。
Preview Server 暴露 close 方法
预览服务器现在暴露 close() 方法,能够正确地拆除整个服务器(包括所有已打开的 socket 连接)。这在 CI 中以编程方式启动 vite preview 并需要干净释放端口的场景中非常关键。实现位于 packages/vite/src/node/preview.ts:closeServer 先摘除 SIGTERM 监听器,再关闭 HTTP server,最后才按序执行 closePreviewServer 插件钩子——即插件可以在服务器完全拆除后才做自己的清理。
性能改进
Vite 官方使用 vite-dev-server-perf 对 Vite 4.0 以来所有小版本测量了“加载 1 万个模块(25 层深树)”的耗时。每个模块是一个带计数器的小 TypeScript 文件并互相导入,因此该基准主要度量 bundle-less 架构下以独立模块逐个请求的开销。演进结果:Vite 4.0 在 M1 MAX 上耗时 8 秒;Vite 4.3 聚焦性能后降到 6.35 秒;Vite 5.1 进一步降到 5.35 秒(Headless Puppeteer 测量,用于版本间对比,不代表用户真实体感)。在 Chrome 无痕窗口中的实测对比:
| 1 万模块指标 | Vite 5.0 | Vite 5.1 |
|---|---|---|
| 加载时间 | 2892ms | 2765ms |
| 加载时间(缓存) | 2778ms | 2477ms |
| 整页刷新 | 2003ms | 1878ms |
| 整页刷新(缓存) | 1682ms | 1604ms |
线程化运行 CSS 预处理器
Vite 5.1 加入按需启用的 CSS 预处理器线程池,通过 css.preprocessorMaxWorkers: true 开启(对应 #13584)。官方报告:对 Vuetify 2 项目,开启后 dev 启动时间减少约 40%。当前仓库中该配置的定义与默认值在 packages/vite/src/node/plugins/css.ts:
// CssPostPluginConfig
preprocessorMaxWorkers?: number | true
// 默认配置
preprocessorMaxWorkers: true,
从这份源码结构看,该选项的语义是“最大 worker 数,true 表示取默认上限”,且当前版本已将其默认置为 true——即 5.1 引入时“opt-in”的特性在后续版本已成为默认行为。开启方式与参数说明见 docs/config/shared-options.md。
optimizeDeps.holdUntilCrawlEnd: false:新依赖发现策略
5.1 提供了 optimizeDeps.holdUntilCrawlEnd 选项。默认策略下,dev server 会等 HTML 爬取(crawl)结束后才开始启动依赖预构建;将其设为 false 则切换到“边爬取边发现依赖”的新策略,官方表示这在大型项目中可能改善冷启动,并正在考虑未来将其设为默认。当前仓库中该逻辑位于 packages/vite/src/node/optimizer/optimizer.ts 与 packages/vite/src/node/optimizer/index.ts 的发现(discovery)流程中:开启新策略后,预构建不再以“爬取完成”为前置门控,从而让首请求更早命中已扫描到的依赖。
解析路径缓存(fs.cachedChecks)默认启用
针对模块 ID 解析的文件系统探测,5.1 将 fs.cachedChecks 优化默认打开(#15704)。官方数据:Windows 上 tryFsResolve 提速约 14 倍,三角(triangle)基准下的整体 ID 解析提速约 5 倍。这与 5.1 整体解析提速的方向一致——减少重复的文件系统 exists/stat 调用。
其他内部性能改进
公告列举了 dev server 的若干增量优化,均可在当前仓库的 server 源码脉络中对应到:
- 新增短路 304 响应的中间件(#15586),避免重复读取磁盘内容;
- 在热路径上避免调用
parseRequest(#15617),减少每次请求的 URL 重复解析开销; - Rollup 改为正确的懒加载(#15621),dev server 启动不再为 build 依赖付出加载成本。
弃用与移除
Vite 团队持续在收敛 API 表面以保证长期可维护性,5.1 有两项重要决定:
import.meta.glob 的 as 选项弃用
随着浏览器标准转向 Import Attributes,import.meta.glob 中的 as 选项被弃用。官方不会引入替代选项,而是建议改用 query(例如 import.meta.glob('./dir/*.css', { query: '?raw' }))。从源码结构看,该弃用由 packages/vite/src/node/deprecations.ts 统一输出警告,glob 参数解析在 importGlobPlugin 中完成。参数语义详见 docs/guide/features.md。
移除实验性构建期预构建(build-time pre-bundling)
Vite 3 引入的实验性“构建时依赖预构建”在 5.1 被移除。官方的理由很清晰:Rollup 4 已切换到原生解析器,Rolldown 又在同步推进,该特性赖以成立的“构建性能”与“dev/build 不一致”两个前提都不再成立。团队结论是:用 Rolldown 同时承担“dev 依赖预构建”与“生产构建”是更好的路径,且 Rolldown 有可能实现比依赖预构建高效得多的构建期缓存(#15184)。dev 与 build 依赖预构建机制的现行说明见 docs/guide/dep-pre-bundling.md。
参与与致谢
Vite 团队感谢 900 名核心贡献者以及插件、集成、工具与翻译的维护者;贡献入口见 CONTRIBUTING.md。Vite 5.1 的完成同样离不开 StackBlitz、Nuxt Labs 与 Astro 对团队成员的雇佣支持,以及 GitHub Sponsors 与 Open Collective 上的赞助者。
小结
- Runtime API 是 5.1 的战略性特性:它把“经 Vite 管线处理代码”从 dev server 中剥离出来,直接铺垫了后来的 Module Runner 与 Environment API(Vite 6+),是当前仓库中 packages/vite/src/module-runner/ 一脉的直接前身;
- 回调式
assetsInlineLimit、ssr.external: true、previewclose()是三个面向工程化的实用 API,分别解决资源内联策略、SSR 外部化语义与程序化生命周期管理; - 性能层面,5.1 通过 CSS 预处理线程化(现已默认开启)、
optimizeDeps.holdUntilCrawlEnd新策略、解析路径缓存与 304 短路等组合拳,把 1 万模块加载时间压到 5.35 秒; - 收敛层面,弃用
glob的as、移除构建期预构建,表明 Vite 正朝着“dev/build 统一由 Rolldown 驱动”的方向做减法。
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 StartedRust0624
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
