当一间充满开发者的房间里,所有人都在等待构建(build)完成时,会产生一种奇特的寂静。目光开始飘向副屏,手指在手机上滑动,有人起身去买一杯其实并不想喝的咖啡。如果你曾在现代 JavaScript 代码库中浸淫过,你一定体会过这种停顿。这不仅仅是休息,更是思维的断层。

我们经常讨论框架。React、Vue、Svelte 以及下周可能推出的任何新框架都占据了所有的注意力。会议因为发布新框架而门票售罄,博客文章在剖析各种语法糖。但在所有这些面向用户的喧嚣之下,底层正在发生着足以改变你编写代码方式的变革。这场革命并非来自前端框架,而是发生在工具层(tooling layer),并且正由 Rust 和 Go 编写。

多年来,JavaScript 工具一直是使用 JavaScript 构建的。这很合理。Babel 教会了一代人如何用今天的手段编写明天的语法。Webpack 将我们拆分的代码打包成浏览器可以处理的形式。ESLint 在我们提交代码之前捕捉 Bug。这些工具是为规模较小的 Web 设计的。它们假设的是几百个模块,而不是上万个;假设的是单一仓库(single repos),而不是单体仓库(monorepos)——在单体仓库中,共享 UI 包的一个改动会波及到十几款应用程序。

随后,应用规模增长了。代码库膨胀成了庞大的仓库。工具却停留在原地,延迟也随之悄然滋生。原本只需两秒的热重载(hot reload)变成了十二秒,然后是三十秒。午饭前跑完完整的测试套件变成了一种幻想。Linter 在检查过上千次的文件上再次踉跄。在纸面上,每一次延迟似乎都很微小,但在实践中,这些停顿会摧毁专注力。它们训练你习惯批量处理工作,训练你在检查修复是否生效前犹豫不决,训练你因为反馈成本太高而避免尝试新事物。

下一代工具通过不再让 JavaScript 成为性能瓶颈,来正面解决这种延迟。

新的引擎室

看看特定的任务是如何被重新夺回的。

转换(Transformation) 过去意味着 Babel。它是通用的预处理器,将 JSX 和 stage-3 提案转换为纯粹的 ES5。它仍然是一款令人印象深刻的软件,但它本质上是单线程的 JavaScript 在解析 JavaScript。现在,OXC 登场了,这是一个基于 Rust 的工具链。它处理着与 Babel 相同的任务,但基准测试显示,它的速度大约快 40 倍,同时内存消耗减少了 70%。这不仅仅是渐进式的改进,这是“让你察觉到它的存在”与“让你忘记它正在运行”之间的区别。

打包(Bundling) 是痛点最集中的地方。Webpack 统治了十年,但其内部架构是为另一种规模设计的。作为其 Rust 继任者的 Turbopack 不仅仅是重新编译得更快。它依靠激进的记忆化(memoization)来精确理解哪些部分发生了变化,并仅重新构建该部分。在一个大型应用中,修改单个组件不应该让你付出遍历整个依赖图的代价。有了 Turbopack,构建趋于瞬时完成。进度条消失了,因为根本没有什么需要等待。

测试(Testing) 也有其特有的拖累。Jest 重新定义了 JavaScript 测试,但在监听模式(watch mode)下,它给人的感觉就像是在每一次按键时都在重新学习你的代码库。Vitest 采用了不同的架构方法。因为它复用了 Vite 的模块图,而不是从头开始构建自己的依赖树,因此在监听模式下的报告速度比 Jest 快约 8.5 倍。这里的胜利不仅在于原始速度,更在于一致性。你的测试运行器和开发服务器终于在“项目长什么样”这一点上达成了一致。

代码检查(Linting) 也面临类似的开销。ESLint 的灵活性是它的杀手锏;它的规则本质上是在 AST 上运行的 JavaScript 函数。这种灵活性是以消耗计算周期为代价的。用 Rust 编写的 Oxlint 则将范围缩小到常见情况,从而实现了极速运行。它的运行速度比 ESLint 快 50 到 100 倍。其实际效果是:在你编辑器的保存动画结束之前,代码检查就已经完成了。你不再需要忍受那些在你修复问题后依然停留数秒之久的红色波浪线。

或许最具有象征意义的转变正在类型检查领域发生。Microsoft 目前正在用 Go 语言重写 TypeScript 编译器。早期的基准测试结果令人震惊:使用新实现后,VS Code 的加载速度提升了约 8 倍,而类型检查本身的速度则提升了约 10 倍。想想这意味着什么。TypeScript 是 JavaScript 的成功典范。它是一种编译为 JavaScript 的语言,用于对 JavaScript 生态系统进行类型检查,而现在它自己的编译器正在转向一种原生系统语言,因为 JavaScript 无法提供该生态系统所要求的性能。这个工具正在通过自我演进来换取速度。

这一切并不会取代 React。它不会终结 Next.js,也不会让 TypeScript 过时。框架仍然定义着你的组件模型和路由。这些新工具只是让底层的运行变得更快。它们是路,而不是车。

当速度改变行为时

关于工具链的讨论往往容易陷入基准测试图表的泥潭。数字很容易比较,但真正的冲击力在于人类的行为。

当反馈从秒级降至毫秒级时,你不仅仅是完成任务的速度变快了,你的完成方式也会随之改变。你不再囤积改动。你写下一行代码,立即看到结果,然后进行调整。你运行测试是因为它们是即时的,而不是因为你的 pull request 要求这样做。你会尝试那些可能行不通的重构,因为撤销它的成本几乎为零。你能沉浸在问题之中,而不是等待机器让你重新进入状态。

这就是心理学家所说的“心流”(flow)。它需要在行动与后果之间建立一个紧密的闭环。如果音箱延迟了每一个音符,吉他手就无法演奏;如果画笔的更新延迟了半秒,画家就无法调色。开发者也不例外。延迟不仅仅是一种烦恼,它更是对思维征收的一种税。

因此,生产力的提升不仅仅是技术层面的,更是习惯层面的。快速的工具训练你进行实验;缓慢的工具训练你变得犹豫。在长达一年的时间里,这种差异会产生复利效应,最终造就完全不同的软件。拥有即时反馈的团队交付产品时更加自信。他们将工作拆解成更小的部分,因为尝试的成本为零。他们的代码审查(code review)变得更高效,因为 Bug 会在发生的瞬间被发现,而不是在 20 分钟后的 CI 中才被揪出来。

无形的工作

这就是为什么新闻头条往往具有误导性。框架很容易被拿来讨论,因为它们有 Logo、有 API、有 Twitter 上的话题度。而基础设施的设计初衷就是隐形的。你不会因为要配置一个打包工具(bundler)而感到兴奋,你只希望它能消失。但“消失”恰恰是优秀基础设施的职责所在。它承担了重量,好让可见层保持轻盈。

如果你正在领导一个团队或维护一个遗留代码库,这一点应该影响你的优先级。从 React 迁移到 Vue 可能会重塑你的组件树;但从 Webpack 迁移到 Turbopack,或者从 Babel 迁移到 OXC,可能会重塑你的整个工作日。后者很难向管理层推销,因为没有全新的首页演示可以看。你只能看到一个不再对着构建终端叹气的团队。

去审计一下究竟是什么在拖慢你的速度。如果你在用一套构建于 2015 年的工具链运行现代的 monorepo,你并不是在保持保守,你是在支付每日的“摩擦税”。解决办法不是学习新的前端范式,而是更换引擎。

框架会层出不穷,它们会持续占据推文和会议的主题演讲。但 JavaScript 编写体验的真正变革,正在底层发生——在那些将你的时间视为昂贵资源的编译型语言中。这才是真正的革命。它不是一种新的列表渲染方式,而是一套足够快、快到不会阻碍你的思考,从而让你专注于思考的工具链。