加入 Cloudflare 后的四个月里,VoidZero 发布了超过 80 个版本,重点提升 JavaScript 编译、代码检查与测试的速度。公开成果包括速度提升达到 10 倍的 React 编译器,以及 Vite+ 1.0。这里值得关注的不只是某一项跑分,而是一条更清晰的工程路线:把工具链延迟压低,让开发者和 AI Agent 都能更频繁、更可靠地执行验证循环。
速度提升改变的不只是构建时间
传统上,团队会把编译优化理解为“CI 少等几分钟”。但现代 JavaScript 项目的反馈链条远不止一次构建:
- 开发服务器需要持续转换和更新模块;
- 编辑器会反复触发类型检查与 lint;
- 提交前钩子会运行格式化、检查和局部测试;
- CI 会在多个 Node.js 版本、操作系统或工作区中重复这些任务;
- AI Agent 每改一轮代码,都可能重新编译、检查并运行测试。
对人类开发者而言,较低延迟能减少上下文切换。对 Agent 而言,它直接决定固定时间或预算内可以完成多少次“修改—验证—修正”循环。因此,更快的编译器、linter 和测试工具并非相互孤立的优化,而是在共同降低整个软件交付循环的成本。
超过 80 次发布也传递了另一个信号:工具链性能往往不是靠一次大版本重写完成的。解析、转换、模块图、缓存、诊断输出和测试调度都可能存在瓶颈,需要通过短周期发布持续验证。
从 React 编译器到 Vite+:关注统一反馈链
摘要提到的两个代表性结果分别是速度提升达到 10 倍的 React 编译器,以及 Vite+ 1.0。由于不同项目的依赖图、插件和运行环境差异很大,不能把单项结果直接等同于所有项目都能获得相同提升;更有价值的做法是观察自己的完整工作流。
评估新工具时,建议同时记录四组指标:
- 冷启动:清空产物和工具缓存后的首次构建时间。
- 热更新:修改一个常用模块后,开发环境恢复可交互状态所需的时间。
- 全量验证:lint、类型检查和测试从开始到结束的总耗时。
- 资源成本:峰值内存、CPU 占用以及 CI 计费时长。
速度还必须与兼容性一起考察。某些插件依赖特定转换顺序、虚拟模块或框架约定;编译结果更快,但如果 source map、错误位置或边缘语法发生变化,迁移成本仍可能超过收益。
用可重复基准测试,而不是凭感觉升级
下面是一套可以直接改造的 Node.js 基准脚本。它不依赖第三方包,默认测试项目中的 build、lint 和 test 脚本。运行前请确认 package.json 已定义这些命令;如果名称不同,修改 tasks 数组即可。
// scripts/bench.mjs
import { spawnSync } from "node:child_process";
import { performance } from "node:perf_hooks";
const runs = Number(process.env.RUNS ?? 5);
const tasks = [
["build", ["run", "build"]],
["lint", ["run", "lint"]],
["test", ["test", "--", "--run"]],
];
const results = {};
for (const [name, args] of tasks) {
const samples = [];
for (let i = 0; i < runs; i++) {
const start = performance.now();
const result = spawnSync("npm", args, {
stdio: "inherit",
shell: process.platform === "win32",
env: { ...process.env, CI: "1" },
});
const elapsed = performance.now() - start;
if (result.status !== 0) {
console.error(`${name} failed on run ${i + 1}`);
process.exit(result.status ?? 1);
}
samples.push(elapsed);
}
samples.sort((a, b) => a - b);
results[name] = {
runs,
median_ms: Math.round(samples[Math.floor(samples.length / 2)]),
min_ms: Math.round(samples[0]),
max_ms: Math.round(samples.at(-1)),
};
}
console.log(JSON.stringify(results, null, 2));
在项目根目录运行:
mkdir -p scripts benchmarks
node scripts/bench.mjs | tee benchmarks/before.json
升级目标工具或在迁移分支中切换配置后,再执行一次:
npm ci
node scripts/bench.mjs | tee benchmarks/after.json
diff -u benchmarks/before.json benchmarks/after.json || true
这段脚本主要衡量热缓存条件下的重复执行。如果要测试冷构建,可以在每轮运行前删除 dist、框架缓存目录和目标工具缓存,但不要随意清理全局依赖缓存,否则网络波动会污染结果。更严谨的测试还应固定 Node.js 版本、锁文件、机器规格和并发任务数量。
可以把它接入 CI,在迁移分支与主分支使用相同环境:
# .github/workflows/toolchain-benchmark.yml
name: toolchain-benchmark
on:
workflow_dispatch:
pull_request:
paths:
- "package.json"
- "package-lock.json"
- "vite.config.*"
- "scripts/bench.mjs"
jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: mkdir -p benchmarks && node scripts/bench.mjs | tee benchmarks/result.json
- uses: actions/upload-artifact@v4
with:
name: toolchain-benchmark
path: benchmarks/result.json
共享 CI 机器会有噪声,因此不要因为一次结果快了几个百分点就下结论。应查看多次运行的中位数,并为显著回归设置合理阈值。
AI Agent 更需要快速且结构化的工具输出
AI Agent 不会像人一样抱怨等待,但等待仍会消耗执行时间、令牌预算和基础设施费用。工具链越快,Agent 越能采用小步提交策略:每次只修改少量文件,立即运行局部 lint 或测试,失败后根据诊断继续修正。
速度之外,输出格式也很关键。适合 Agent 的命令应具备这些性质:
- 在非交互环境中稳定退出,不等待 TTY 输入;
- 通过退出码明确表示成功或失败;
- 错误信息包含文件、行号和规则名称;
- 支持只检查变更文件或指定测试;
- 最好提供 JSON 等机器可读输出;
- 相同输入尽量产生确定性结果。
可以为 Agent 单独定义一个有边界的验证入口:
{
"scripts": {
"check:agent": "npm run lint && npm run test -- --run && npm run build"
}
}
这个例子假设测试框架接受 --run 参数;如果项目使用其他测试运行器,应替换为对应的非监听模式。统一入口的价值在于,开发者、CI 和 Agent 可以执行同一组最低验证,而不是各自维护一套隐含规则。
采用建议:先建立基线,再扩大迁移范围
VoidZero 与 Cloudflare 的进展说明,JavaScript 工具链仍有显著的性能优化空间。不过,落地时不宜只追逐“10 倍”这样的峰值数字。更稳妥的方式是:
- 选择一个依赖关系清晰的应用或包作为试点;
- 固定运行环境,记录升级前的冷启动、热更新和全量检查基线;
- 验证插件、source map、错误诊断与产物语义;
- 比较本地体验、CI 时间和内存,而不是只看单项 benchmark;
- 为 Agent 提供非交互、可重复、可解析的检查命令;
- 保留回退路径,并让新旧方案在一段时间内并行验证。
真正有价值的工具链升级,不只是让一次构建更快,而是让人和 Agent 都愿意更频繁地验证代码。只有当速度、正确性与可迁移性同时成立,性能改进才会转化为持续的工程产出。