跳至主内容

移除 Babel 的阶段预设

· 1 分钟阅读
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

随着 v7 版本的推进,我们决定停止在 Babel 中发布阶段预设(例如 @babel/preset-stage-0)。

这个决定并非轻率之举,我们希望解释 TC39、Babel 和社区之间互动的背景。

发展历程

Babel 预设是可共享的插件集合。

官方 Babel 阶段预设 追踪了 TC39 针对 JavaScript 新语法提案标准化流程

每个预设(如 stage-3stage-2 等)包含特定阶段及更高阶段的所有插件。例如 stage-2 包含 stage-3 的插件,依此类推。


这让需要实验性语法的用户只需添加预设,无需单独配置/安装每个插件。

实际上,我们在 Babel v6 发布后不久就添加了阶段预设(v5 中曾是配置标志)。下方是 Babel v6 的旧示例:

配置中常见以下写法:

JavaScript
{
"presets": ["es2015", "react", "stage-0"]
}

babel-preset-stage-0 的原始代码:

babel.config.js
module.exports = {
presets: [
require("babel-preset-stage-1")
],
plugins: [
require("babel-plugin-transform-do-expressions"),
require("babel-plugin-transform-function-bind")
]
};

问题

这些预设便捷地实现了我们共同的需求:使用崭新、前沿但"尚未确定"的 JavaScript 未来特性。

回顾过去,这种方式效果显著!(也许过于显著了?)

是否过于成功?

CoffeeScript 等语言和 Traceur 等工具奠定了编译 JavaScript 的理念。Babel 则进一步简化了新/未来语法的使用,并与现有工具链集成。开发者态度从怀疑担忧转向全面拥抱实验性特性。

若没有 Babel 等编译器的广泛采用,我们不可能取得今日成就:它加速了 ES2015 在大规模受众中的使用(和教学)。React 的发展更推动了这一趋势,其 JSX 语法、类属性和对象展开运算符使更多人开始了解这些语法提案。

Babel 成为人们"一次性配置后无需再虑"的底层设施,隐藏在其他工具背后,直到出现 SyntaxError、依赖问题或集成故障。用户只需简单配置 stage-0

这固然令人欣喜——意味着这些理念正在真实环境甚至生产系统中得到验证。但也意味着当提案发生重大变更(或直接被废弃)时,许多公司、工具和个人都会遭遇困扰。

反复讨论

数年来,我们通过 #4914#4955#7770 等议题多次讨论阶段预设的去留。我甚至在早前关于 Babel 7.0 的文章中说过不会移除它们 😅。

但我们发现保留阶段预设甚至会给 Babel 自身带来问题:

  • 经常有人提出类似问题:“使用异步函数需要哪些预设?”。用户往往不清楚 stage-0 的具体含义,很少有人会查看它的 package.json 或源码。

  • 当提案处于 Stage 3 阶段时(一旦进入 Stage 4),移除对应的插件实际上是破坏性变更。当你尝试使用 @babel/preset-env 避免编译原生支持的提案时,这个问题会更加严重。

“ES7 Decorators”的命名问题

部分问题恰恰源于命名本身——正如我们常听到的,命名是件困难的事。

ES6 本身就有多种命名:Harmony、ES Next、ES6、ES2015。当人们听说新特性时,很自然地会直接采用最新的版本号来命名。

因此,通过 Twitter 搜索 相关话题 很容易发现,推文/博客/演讲中普遍使用“ES7 Decorators”这一名称,它已成为约定俗成的叫法。

这种现象完全可以理解,但持续使用这种命名会给语言发展进程设定错误预期。这无需感到愧疚——作为社区,我们始终在相互学习并提醒彼此 JavaScript 的运行机制。

Jay Phelps 曾就此撰写了篇好文。他建议最好按其当前“阶段”命名:“Stage 2 Decorators”,或直接称为“Decorators 提案”。

原因在于“ES7 Decorators”的表述暗示该特性必定会进入 ES7。我在关于编译 node_modules 的上篇博客中提过,进入特定阶段并不代表任何保证:提案可能停滞、倒退甚至被完全废弃。

因此我们决定将提案插件名称从 @babel/plugin-transform- 改为 @babel/plugin-proposal-,正是为了强调这一事实

关于“BabelScript”

过早为提案提供预设,可能暗示这些提案已确定会推进或具有稳定实现。

TC39 强烈建议谨慎使用 Stage 2 及以下阶段的提案,因为这可能导致社区无意间施压要求保持现有实现(例如装饰器用 # 替代 @ 符号),阻碍改进进程,唯恐破坏现有代码或造成生态分裂。

有人戏称使用 Babel 的开发者其实在用“BabelScript”而非 JavaScript,暗示一旦某个特性有了 Babel 插件,就等于被“固化”或已成为语言正式部分(实际并非如此)。当人们看到新语法/概念(Stage "-1")时,第一反应往往是“有没有对应的 Babel 插件”。

设定合理预期

随着 Babel 等编译器使编写 ES2015 成为常态,开发者自然想尝试更前沿的实验性“特性”。在 Babel 中,早期版本通过 stage 标志实现,后来则通过 stage-x 预设。

作为启用任何新特性最便捷的方式,Stage 预设迅速成为用户配置 Babel 时的默认选择(尽管在 Babel v6 中,默认不进行任何转换的设计曾引发大量抱怨)。

"stage-0" 在各类库、样板文件、技术演讲、推文和幻灯片中随处可见。

多年前就有过大量深入讨论,但当时难以找到完美解决方案:我们既不愿通过 console.warn 警告惩罚那些理解风险的用户,又觉得完全移除该选项不够合理。

无论是否默认启用,盲目选择 Stage 0 都显得危险;但完全拒绝使用任何提案又过于保守。理想情况下,开发者应根据特性合理性自主决策,不受提案阶段限制。Mike Pennisi这篇精彩文章中深入探讨了这些担忧。

我们的初衷并非威胁、催促或强行推动特定技术,而是忠实维护新特性的实现与讨论生态。

顾虑

其他考量

我们曾考虑过替代方案:

  • 重命名预设以明确稳定性(但无法解决版本问题)

  • 优化版本策略:独立控制预设版本并及时更新,可能采用 0.x 版本号

  • 对过时预设版本发出警告/错误提示

但若保留 Stage 预设,用户仍需查询提案所处阶段来决定使用范围。

为何选择现在?

为何不更早移除?Stage 预设伴随 Babel 多年,我们曾顾虑移除会增加使用复杂度——大量工具链、文档、技术文章都围绕其构建。早期我们认为应由官方维护这些预设,否则社区必然会(也将继续)自行创建类似方案。

我们在寻求反馈平衡点:若仅由委员会决定语言特性,可能产生规范完善但非必要的功能;若社区将实验性提案视为可稳定用于生产环境,又会引发其他问题。我们需在谨慎与进取间找到平衡:Babel 适合进行技术实验,但明确边界至关重要。

移除预设实际是种"功能强化"——它强制用户为每个提案做出明确决策,这对具有不同稳定性、实用性和复杂度的提案而言完全合理。

我们预期这将引发初期争议,但坚信长期来看移除 Stage 预设是更优选择。这绝不意味我们忽视易用性、新用户体验或文档建设。我们将持续维护项目稳定,提供更优工具链,并完善知识沉淀。

迁移方案

如需自动化迁移,我们已更新 babel-upgrade 工具(执行 npx babel-upgrade 即可)。

简而言之,我们决定移除 Stage 预设。这意味着开发者需要主动选择并了解正在使用的提案类型,而非默认使用预设配置——尤其考虑到某些提案本身的不稳定性。如果你使用其他预设或工具链(例如 create-react-app),此变更可能不会对你产生直接影响。

7.0.0-beta.52 起,我们已正式弃用 Stage 预设。若你暂不调整配置,建议将版本锁定在 beta.54 直至可升级;beta.54 之后版本将抛出包含迁移指南的错误提示。另请注意:预发布阶段需确保所有依赖版本保持一致。

替代方案是自由创建包含相同插件的自定义预设,并按需升级插件。未来我们可能开发 babel-init 工具,以交互方式配置插件;或增强 babel-upgrade 使其能列出并添加当前 Stage 插件。或许 Babel 应专注底层能力,而将此类选择权交给高层级框架工具(如 create-react-app)。

防止提案锁定

James DiGioia 近期撰文探讨了管道操作符 (|>) 的变更

该文核心指出:提案本身仍处变动期且存在多种探索方向。为同时获取规范反馈与用户反馈,我们计划将当前所有三种可能性实现为 Babel 插件——这要求插件使用方式同步革新。这对 TC39 和 Babel 而言都是全新尝试!

此前开发者仅需在配置中添加提案插件。如今我们移除默认行为,要求用户通过标志显式选择提案,并明确告知当前无固定(甚至倾向性)方案。

{
"plugins": [
- "@babel/plugin-proposal-pipeline-operator"
+ ["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }]
]
}

我们将持续推进此模式,以此强调这些提案始终开放变更与社区反馈。移除 Stage 预设进一步简化了流程:此前即使未使用特定语法,我们也需向下传递这些选项。

生态维护负担

语言的"语法预算"不仅关乎自身复杂度,更会波及工具链。每项新语法都会给 JavaScript 生态维护者带来新的负担

新语法提案需要同步更新:解析器 (babylon)、语法高亮 (language-babel)、代码检查 (babel-eslint)、测试框架 (jest/ava)、格式化工具 (prettier)、覆盖率统计 (istanbul)、代码压缩 (babel-minify) 等。

acorneslintjshinttypescript 等项目频现支持 Stage 0 提案的诉求——只因它们已存在于 Babel。鲜有项目能承担"全量支持提案"的维护重负。考虑到持续变更带来的挑战,Babel 自身能处理此事已属不易。

谁在承担这些工作?确保全链路兼容是我们的责任吗?这些(主要由志愿者维护的)项目普遍人力匮乏,却持续承受全方位抱怨。作为社区,我们该如何共同承担基础设施的责任?——这与整个开源生态面临的挑战别无二致。

Babel 承担了支持这些实验性特性的不寻常负担;同时,其他项目采取更保守的策略也是合理的。如果您希望看到新的语言特性在整个生态系统中得到支持,请为 TC39 做贡献并参与本项目,将这些提案推进到第 4 阶段。

未来规划

本项目的目的是作为 TC39 流程的一部分:既作为实现较新提案(第 0-2 阶段)的资源,又用于接收用户反馈,同时支持旧版 JavaScript。我们希望本文能进一步阐明我们如何尽最大努力让本项目更好地融入 JavaScript 生态系统。我们将很快发布 v7 的 RC 版本!


如果您认可本文以及我们在 Babel 上的工作,您可以在 Patreon 上支持我,请贵公司在 Open Collective 上赞助我们,或者更好的做法是让贵公司将参与 Babel 作为工作的一部分。我们珍视这种集体所有权。

感谢所有审阅者!欢迎在 Twitter 上提供反馈。