跳至主内容

Babel 7 正式发布

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

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

经过近 2 年开发、4,000 次提交、50 多个预发布版本以及无数社区支持,我们激动地宣布 Babel 7 正式发布!距离 Babel 6 发布已近三年!由于本次更新涉及大量模块,发布初期若有疏漏敬请谅解。Babel 7 是一次重大升级:我们提升了编译速度、开发了升级工具、支持 JS 配置文件、新增配置覆盖功能、优化体积压缩选项、实现 JSX Fragments 支持、增强 TypeScript 兼容性、支持多项新提案等等!

如果您认可 Babel 的工作,可通过 Open Collective 赞助 Babel,在 Patreon 支持我,或让您所在公司参与 Babel 开发。我们期待 JavaScript 社区共同维护这个至关重要的项目!

终于来了!🎉

软件虽无法尽善尽美,但我们已准备好发布这个久经生产环境考验的版本!@babel/core 月下载量已达 510 万次,广泛应用于 Next.js 6vue-cli 3.0React Native 0.56 甚至 WordPress.com 前端 等工具中 🙂!

Babel 的使命

本文开篇先回顾 Babel 近年来在 JavaScript 生态系统中的定位

核心问题在于:与服务端语言不同,无法保证所有用户的 JavaScript 支持度一致——用户可能使用支持程度各异的浏览器(尤其是旧版 Internet Explorer)。若开发者使用新语法(如 class A {}),旧浏览器用户将因 SyntaxError 而遭遇白屏。

Babel 让开发者能自由使用最新 JavaScript 语法,同时通过代码转换(class A {} 转为 var A = function A() {})自动解决向后兼容问题。

其代码转换能力还可用于实现新特性,这使其成为连接 TC39(JavaScript 语言标准委员会)与社区的桥梁:既帮助委员会收集提案反馈,也让社区能参与语言未来建设。

Babel 已成为现代 JavaScript 开发的基石。目前 GitHub 上有 130 万个依赖仓库,npm 月下载量达 1700 万次数百家用户涵盖主流框架(React、Vue、Ember、Polymer)和知名企业(Facebook、Netflix、Airbnb)。它已深深融入开发基础架构,许多人甚至意识不到它的存在——即使您未直接使用,您的依赖项极可能正在使用 Babel。

维护者也是普通人

Babel 不仅深刻影响着语言本身的发展方向,更塑造着整个社区生态。然而肩负如此重任的 Babel,仅是由几名志愿者驱动的社区项目。

直到去年,团队中的部分成员才有机会首次线下见面:

虽然这是一篇发布公告,但我想借此机会提醒大家这个项目的现状。

我本人在 6.0 版本发布前几个月加入,当时也经历了不少争议和反对。这些反响导致当时的维护者精疲力竭(包括 Babel 的创始人 Sebastian),我们剩下的几个人接过了重任。

和许多维护者一样,我们也是偶然进入这个角色的。我们中的大多数人没有接受过编译器工作原理或开源项目维护的正式培训。讽刺的是,我在大学甚至刻意避开了计算机科学专业,因为不想学习编译器或底层课程——当时觉得这些既枯燥又困难。然而我却发现自己被工具链、代码检查器、Babel 以及 JavaScript 这门语言所吸引。

我想鼓励大家关注你所依赖的开源项目,并尽可能给予支持(包括时间和资金)。

许多维护者并非天生就是所从事领域的专家;只要开始行动,就能完成很多事情。你会带着好奇心和谦逊的态度加入,这两者都是维护者应具备的优秀品质。我期望的是对项目愿景的追求,而不仅仅是我们所有人都在完成"任务"

Babel 不是公司,也不是像 Facebook 这样大公司里的开源团队。只有少数志愿者在为 Babel 工作,而我辞去工作成为目前为止唯一全职投入开源的人也不过几个月。但维护者可能来来去去——他们在这个"爱好"之外还有生活,需要养家糊口,转换方向,更换工作或寻找新机会。我们是否在共同维护这些支撑我们工作的基础?还是任由地基逐渐崩塌?如何让开源既保持开放包容又有明确边界?能否从其他维护者的经验中学习?

尽管开源已主导软件领域,但若忽视背后的,我们真能认为它处于健康状态吗?

#Babel赞助计划

当前的开源可持续性犹如传递募捐篮:开源项目为千万用户和企业创造的价值不言而喻,但这些价值却很少回馈给实际付出的维护者。虽然存在多种支持方式,但并非所有方法都适合每个项目或维护者。


现在让我们进入正题!

重大破坏性变更

我们已在迁移指南中记录这些变更,其中许多可通过新的 babel-upgrade 工具自动完成,部分功能将在未来加入。

  • 停止支持不再维护的 Node 版本:0.10、0.12、4、5(详情

  • 将我们迁移到 @babel 命名空间,改为使用"作用域包"(详情)。这有助于区分官方包,因此 babel-core 变成了 @babel/core(同时避免了命名抢占问题)

  • 移除(并停止发布)所有年度预设(如 preset-es2015 等)(详情)。@babel/preset-env 取代了这些预设,因为它既包含所有年度新增特性,又能针对特定浏览器集合进行配置

  • 同时弃用"Stage"预设(如 @babel/preset-stage-0 等),改为支持按需启用单个提案。类似地,默认从 @babel/polyfill 中移除了提案支持(详情)。建议阅读完整说明文章了解更多解释

  • 部分包进行了重命名:所有 TC39 提案插件现在统一使用 -proposal 后缀替代 -transform详情)。例如 @babel/plugin-transform-class-properties 更名为 @babel/plugin-proposal-class-properties

  • 为面向用户的特定包(如 babel-loader, @babel/cli 等)引入对 @babel/corepeerDependency 依赖(详情

babel-upgrade

babel-upgrade 是我们开发的新工具,旨在自动完成升级操作:目前支持处理 package.json 中的依赖项和 .babelrc 配置文件

建议直接在 git 仓库中运行 npx babel-upgrade,也可通过 npm i babel-upgrade -g 全局安装

如需修改文件,可同时传递 --write--install 参数

Shell
npx babel-upgrade --write --install

欢迎通过提交 issue 或 PR 参与贡献,帮助大家顺利过渡到 Babel 7!我们期望未来能复用此工具处理所有破坏性变更,并创建机器人自动向项目提交更新 PR

JavaScript 配置文件

我们引入了 babel.config.js。它并非必需项,也不是 .babelrc 的替代品,但在某些特定场景中非常有用

*.js 配置文件在 JavaScript 生态中相当常见,ESLint 和 Webpack 分别支持 .eslintrc.jswebpack.config.js 配置形式

以下是仅在 "production" 环境下启用插件编译的示例(在 .babelrc 中已可通过 "env" 选项实现):

JavaScript
var env = process.env.NODE_ENV;
module.exports = {
plugins: [
env === "production" && "babel-plugin-that-is-cool"
].filter(Boolean)
};

babel.config.js 的配置解析机制与 .babelrc 不同:它始终基于该文件解析配置,而之前 Babel 会从当前文件向上递归查找配置。这种机制使得我们可以充分利用下文介绍的 overrides 特性

使用 overrides 进行选择性配置

近期我发表了文章,探讨了发布 ES2015+ 包以及使用/编译这些包的实践方案

我们有一篇专门章节详细介绍了如何在 Babel 配置中使用名为 overrides 的新配置项,它允许你针对不同的通配符模式指定不同的编译配置。

JavaScript
module.exports = {
presets: [
// default config...
],
overrides: [{
test: ["./node_modules"],
presets: [
// config for node_modules
],
}, {
test: ["./tests"],
presets: [
// config for tests
],
}]
};

这使得需要为测试代码、客户端代码和服务端代码分别配置不同编译选项的应用程序,无需再为每个文件夹单独创建 .babelrc 文件。

速度优化 🏎

Babel 本身的编译速度更快,构建耗时大幅减少!我们进行了大量代码优化,同时采纳v8 团队的性能补丁。我们很荣幸能与其他优秀 JavaScript 工具共同入选 Web Tooling Benchmark 测试集。

输出选项

Babel 长期支持预设和插件的配置选项。简单回顾下:你可以将插件包装在数组中,并向插件传递配置对象:

{
"plugins": [
- "pluginA",
+ ["pluginA", {
+ // options here
+ }],
]
}

我们对部分插件的 loose 选项进行了调整,并为其他插件新增了配置项!请注意:启用这些选项意味着你选择了不符合 JavaScript 规范的行为,务必清楚其影响;当未来不再需要编译而直接使用原生语法时,这可能导致问题。这类选项最适合在库代码中使用(如果确实需要使用的话)。

  • 对于类语法,class A {} 现在不再包含 classCallCheck 辅助函数
JavaScript
class A {}
var A = function A() {
- _classCallCheck(this, A);
};
  • 新增针对纯数组遍历的优化选项(使用 for-of 循环时):["transform-for-of", { "assumeArray": true }]
JavaScript
let elm;
for (elm of array) {
console.log(elm);
}
JavaScript
let elm;

for (let _i = 0, _array = array; _i < _array.length; _i++) {
elm = _array[_i];
console.log(elm);
}
  • preset-envloose 模式下,我们默认排除了 transform-typeof-symbol 插件 #6831

我们发现许多库已经采用这种实践,因此决定将其设为默认行为。

请注意默认行为是尽可能符合规范,这样在停用 Babel 或使用 preset-env 时能实现无缝切换,而不必为了节省字节数牺牲规范性(各项目可自行权衡)。我们计划提供更完善的文档和工具来简化这一过程。

"Pure"标注支持

通过 #6209 的改进,转译后的 ES6 类现在会添加 /*#__PURE__*/ 标注,为 Uglifybabel-minify 等代码压缩工具提供死代码消除提示。这些标注同样被添加到其他辅助函数中。

JavaScript
class C {
m() {}
}
JavaScript
var C =
/*#__PURE__*/
function () {
// ...
}();

可能还存在更多为压缩工具提供优化提示的机会,欢迎向我们反馈!

语法支持

TC39 提案支持

需要再次重申:我们已移除了 Stage 预设,改为要求用户显式启用 Stage 4 以下的提案。

这是因为我们意识到:自动让用户使用尚未定案、可能发生变更的语法存在隐患。尤其对于 Stage 0 或 Stage 1 阶段的提案更是如此。这篇文章阐述了我们对新语法提案的思考逻辑。

以下是 Babel 支持的部分新语法特性列表(请注意这些特性处于动态变化中,可能被添加/移除/暂停开发)以及 v7 中新增的功能:

任何人都很难跟踪所有提案进展,我们在 babel/proposals 持续维护这些信息。

TypeScript 支持(@babel/preset-typescript

我们与 TypeScript 团队合作,通过 @babel/preset-typescript 让 Babel 能够解析/转换类型语法,就像我们通过 @babel/preset-flow 处理 Flow 的方式一样。

更多详情请查看 TypeScript 团队的这篇文章

转换前(含类型):

interface Person {
firstName: string;
lastName: string;
}

function greeter(person : Person) {
return "Hello, " + person.firstName + " " + person.lastName;
}

转换后(类型已移除):

function greeter(person) {
return "Hello, " + person.firstName + " " + person.lastName;
}

Flow 和 TypeScript 都是让 JavaScript 用户利用渐进式类型化的工具,我们希望 Babel 能同时支持两者。我们计划继续与 Facebook 和微软的团队(以及整个社区)紧密合作,保持兼容性并支持新特性。

该集成相对较新,部分语法可能尚未完全支持。非常欢迎您报告问题或提交 PR

JSX Fragment 支持 (<>)

正如 React 博客所述,JSX Fragment 支持自 beta.31 版本起已可用。

JSX
render() {
return (
<>
<ChildA />
<ChildB />
</>
);
}

// output 👇

render() {
return React.createElement(
React.Fragment,
null,
React.createElement(ChildA, null),
React.createElement(ChildB, null)
);
}

Babel 辅助函数变更

babel-upgrade PR 正在进行中

@babel/runtime 已拆分为 @babel/runtime@babel/runtime-corejs2PR)。前者仅包含 Babel 的辅助函数,后者则额外包含垫片函数(如 Symbol, Promise)。

Babel 可能需要向代码中注入某些可复用的函数,我们称之为"辅助函数",类似于模块间共享的函数。

以编译 class 为例(未开启 loose 模式):

规范要求必须使用 new Person() 调用类,但若编译为函数,技术上可直接调用 Person(),因此我们加入了运行时检查机制。

JavaScript
class Person {}
JavaScript
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Person = function Person() {
_classCallCheck(this, Person);
};

通过 @babel/plugin-transform-runtime@babel/runtime(作为依赖),Babel 可提取独立函数并按需引入模块化函数,从而显著减小输出体积:

JavaScript
var _classCallCheck = require("@babel/runtime/helpers/classCallCheck");

var Person = function Person() {
_classCallCheck(this, Person);
};

external-helpersrollup-plugin-babel 也能实现相同效果。我们正在研究未来自动化该流程的方案,敬请期待关于 Babel 辅助函数的专题文章。

自动垫片(实验性)

在不支持 Promise, Symbol 等特性的环境中,垫片必不可少。这有助于明确区分 Babel 的编译器角色(转换语法)和垫片功能(实现内置函数/对象)。

直接导入全量垫片(如 @babel/polyfill)很简单:

JavaScript
import "@babel/polyfill";

但这会包含整个垫片库,而现代浏览器已支持的特性无需重复导入。@babel/preset-env 解决语法兼容的思路同样适用于此:通过 useBuiltins: "entry" 选项,将原始导入拆解为仅必要的模块。

更优方案是仅导入代码库实际使用的垫片。"useBuiltIns: "usage" 选项正是我们实现该目标的首次尝试(文档)。

它会遍历每个文件,如果检测到代码中"使用"了该内置功能,就会在文件顶部自动注入对应的导入语句。例如:

JavaScript
import "core-js/modules/es6.promise";
var a = new Promise();

这种推断机制并非完美,可能会出现误判情况。

JavaScript
import "core-js/modules/es7.array.includes";
a.includes // assume a is an []

该领域的其他解决方案包括使用 polyfill.io(如果您能接受依赖外部服务),或者参考 Kent C. Dodds 在 PayPal 构建托管服务的实践案例

其他改进

Babel 宏 🎣

Babel 最出色的特性之一是其可插拔架构。多年来,Babel 已从单纯的 "6to5" 编译器演进为代码转换平台,为用户和开发者带来了卓越的优化体验。针对特定库和用例开发的Babel 插件层出不穷,它们通过增强库 API 的性能和能力实现了原本不可能的效果(某些"库"甚至会被完全转译消除,实现零运行时开销)。

遗憾的是,在代码库中添加这些插件需要修改配置(这在 create-react-app 等工具链中不被允许),同时也增加了代码复杂性——开发者必须了解作用于文件的 Babel 插件才能预判代码转换结果。这些问题已被 Kent C. Dodds 开发的 babel-plugin-macros 完美解决!

安装 babel-plugin-macros添加到配置后(create-react-app v2 已内置),您无需调整配置即可使用任何宏。此外,该工具让您能更轻松地编写自定义转换逻辑,满足特定应用或代码模块的需求。

请在我们的原创文章《零配置实现代码转换:babel-plugin-macros》中深入了解 babel-plugin-macros

模块目标定位

Babel 始终致力于平衡代码转换带来的体积影响与开发者能力拓展。在 Babel 7 中,配置支持日益流行的module/nomodule 模式变得更加便捷。

值得注意的是,多个主流 Web 框架的 CLI 工具(VuePreact)已利用此特性,使经 Babel 转译的应用向终端用户分发的 JavaScript 体积缩减约 20%。

调用方元数据与更优默认值

我们在 @babel/core 中添加了 caller 选项,允许工具链向预设/插件传递元数据。例如:babel-loader 可添加类似配置,使 preset-env 能自动禁用模块转换(rollup 同理):

JavaScript
babel.transform("code;", {
filename,
presets: ["@babel/preset-env"],
caller: {
name: "babel-loader",
supportsStaticESM: true,
},
});

这项改进令人振奋,它让工具链能提供更合理的默认值并减少配置!对于 webpack/rollup 场景:我们可以自动使用其内置的模块转换机制替代 Babel 的转换(对 import("a") 同样适用)。期待未来更多工具利用此特性!

class C extends HTMLElement {}

我们最古老的问题之一终于有了专属标题(详情

Babel 长期存在无法原生支持扩展内置对象(如 ArrayError 等)的限制,现在这个问题终于解决了!我们收到过大量相关 issue;🎉 您应该像 Andrea 那样欢呼庆祝!

此变更是通过类插件实现的,因此如果您正在使用 preset-env,该功能将自动启用。

网站更新 🌏

我们已将网站从 Jekyll 迁移至 Docusaurus

我们正在通过 Crowdin 搭建多语言翻译体系。随着 Babel 7 的发布,我们将更好地推进这项工作。

REPL

我们已将 REPL 重写为 React 组件,并与 Ives 合作优化了与 CodeSandbox 的集成。现在您可以在 REPL 中直接安装 npm 上的任意插件或预设,并能实时获取 CodeSandbox 的所有更新。

我们再次参与了 Rails Girls Summer of Code!本次活动中,GyujinSujin 正全力将 Boopathi 开发的 babel-time-travel 集成到 REPL 中——您现在就可以体验!

这里蕴藏着无数参与改进 Babel、AST 及其他工具的宝贵机会!

我们拥有主题曲 🎶

哈利路亚——致敬 Babel

某天,Angus 慷慨地为我们创作了一首歌曲,其精妙绝伦让我们决定将其定为"官方主题曲"。而 Shawn 更制作了惊艳的翻唱版本在此

您可在代码库的 SONG.md 文件中找到它。期待更多项目延续这个创意传统!

未来规划

  • Babel 本质上与其编译对象 JavaScript 紧密相连。只要存在新的语法提案需要推进,我们的工作就不会停止。这包括在语法变得"稳定"前就投入时间精力实现和维护的工作。我们关注整个生态:升级路径、新特性教育、标准/语言设计教学、易用性以及与其他项目的集成。

    • 相关进展:在 Nicolò 的努力下,我们即将完成新装饰器提案的实现。这是一段漫长旅程(耗时超过一年!),因为新提案与旧版完全不同且更加强大,但现在已经接近完成 🎉。预计它将在后续的次要版本中发布,同时我们还会发布详解变更的博客文章。
  • Boopathi 始终在勤奋维护 babel-minify,接下来我们将为其推出 1.0 正式版!

  • 大量新功能正在开发中:插件排序优化、更完善的验证/错误提示、性能提升、重新设计宽松/严格规范选项、缓存机制、异步使用 Babel、基于 CI 的自构建、冒烟测试、test262 测试运行等。更多构想请查阅这份路线图文档!

我们没有秘密计划:我们正竭尽所能,用现有资源服务这个社区。

开源是一面镜子

若能拥有足够时间和资源完善这些构想当然理想,但正如本次发布所证实的,实际进展总比预期漫长得多!

为何发布周期如此漫长?是因为我们和用户都不断要求新功能?还是因为难以在众多待添加/修复项中确定优先级?是纠结于解决"低垂果实"还是根本性问题?或是被 GitHub/Slack/Twitter 上的协助请求"分心"?又或者我们作为志愿者,本就不擅长预估时间、理解问题复杂度、避免过度承诺?

还是我们对自己期望过高,或迫于他人压力而牺牲自我去满足需求?每当看到用户询问为何未发布某功能,或为何未修复某缺陷时,那种恐惧感难以言表。我总想仓促发布以作交代,却又渴望认真对待每个细节。

我在上周 React Rally 的演讲穿过(开源的)镜子中尝试表达了这些思考与挣扎,希望大家抽空聆听。我不断自问:面对维护者倦怠、持续焦虑和不切实际期望的恶性循环,我能做些什么?

如同生活常态,我们的行为映照品性,揭示真实自我。行动本身就在改变我们——或好或坏。若要严肃对待工作,就该在那些深植文化的习惯中彼此督促:追求即时满足、用指标定义成功、索取心态取代感恩、以及以过度工作为荣。

但尽管如此,为这次发布付出的所有努力都意义非凡。

致谢

这确实是激动人心的发布——不仅因回望成就与赋能,更因知晓过去一年凝聚了多少心血。沿途的机遇与经历令人难以置信:协助全球企业、所到城市皆遇知己、坦诚讲述这个团队共同经历的非凡旅程。

就我个人而言,我从未在任何如此规模的项目上投入如此多的心力,在此衷心感谢所有在此过程中给予我们支持的人们。特别要感谢Logan Smyth,他投入了无数时间重构核心架构,在Slack上始终耐心提供帮助,同时兼顾自由职业工作;还要感谢Brian Ng,他以极大热忱持续维护Babel,并审阅我的所有博文和技术演讲。Daniel TschinderSven SauleauNicolò RibaudoJustin Ridgewell都为这个版本的顺利发布做出了不可或缺的贡献,让整个开发过程充满乐趣。

同样感谢Slack、Twitter和GitHub上所有社区成员的理解与支持,正是你们的协作让我们能更好地服务终端用户。特别鸣谢Behance/Adobe的伙伴们,在我决定全职投入前赞助我近一年的半职开发工作(期间还协助在生产环境全面测试Babel 7)。衷心感谢所有Open Collective赞助商,尤其是TrivagoHandshake。最后,感谢亲友们长久以来的关爱与支持。

此刻我们虽已精疲力竭,但能以这种方式服务社区实属荣幸。衷心希望这个版本能为您带来价值!