Skip to main content

Babel 7 发布

· 33 min read

历经 2 年,4k 多次提交,50 多个预发布版本以及大量社区援助,我们很高兴地宣布发布 Babel 7。自 Babel 6 发布以来,已经过了将近三年的时间!发布期间有许多要进行的迁移工作,因此请在发布第一周与我们联系。Babel 7 是更新巨大的版本:我们使它编译更快,并创建了升级工具,支持 JS 配置,支持配置 "overrides",更多 size/minification 的选项,支持 JSX 片段,支持 TypeScript,支持新提案等等!

如果你欣赏我们在 Babel 上所做的工作,你可以在 Open Collective 上赞助 Babel,也可在 Patreon 上支持我,或者也可加入到 Babel 的维护行列中。Babel 项目是集合 javascript 社区成员力量的集体成果,我们向他们表示衷心的感谢!

进行中!🎉

软件永远都不是完美的,但我们准备发布的版本已经在生产环境中实验了一段时间!@babel/core 已经达到每月 510 万次的下载量,因为它在类似于 Next.js 6vue-cli 3.0React Native 0.56,甚至是 WordPress 的前端 中都被广泛使用 🙂!

Babel 担任的角色

我想再次介绍下过去几年中 Babel 在 JavaScript 生态系统中所担任的角色,以此展开本文的叙述。

起初,JavaScript 与服务器语言不同,它没有办法保证对每个用户都有相同的支持,因为用户可能使用支持程度不同的浏览器(尤其是旧版本的 Internet Explorer)。如果开发人员想要使用新语法(例如 class A {}),旧浏览器上的用户只会因为 SyntaxError 的错误而出现屏幕空白的情况。

Babel provided a way for developers to use the latest JavaScript syntax while allowing them to not worry about how to make it backwards compatible for their users by translating it (class A {} to var A = function A() {}).

由于它能转译 JavaScript 代码,它还可用于实现新的功能:因此它已成为帮助 TC39(制订 JavaScript 语法的委员会)获得有关 JavaScript 提案意见反馈的桥梁,并让社区对语言的未来发展发表自己的见解。

Babel 如今已成为 JavaScript 开发的基础。GitHub 目前有超过 130 万个仓库依赖 Babel每月 npm 下载量达 1700 万次,还拥有数百个用户,其中包括许多主要框架(React,Vue,Ember,Polymer)以及著名公司(Facebook,Netflix,Airbnb)等。它已成为 JavaScript 开发的基础,许多人甚至不知道它正在被使用。即使你自己没有使用它,但你的依赖很可能正在使用 Babel。

核心开发

Babel 不仅影响着语言本身的未来,也对社区和生态系统产生巨大了影响。尽管承担着这么巨大的责任,可 Babel 也仅仅是一个由几名志愿者组成的社区驱动项目。

去年,团队里的一些伙伴才在线下相见:

尽管这只是一个公告帖,但我想借此机会告诉大家这个项目的状态。

我在 6.0 版本发布前几个月加入,当时 Babel 拥有很多争议和反对意见。那些争议导致现有维护人员精疲力竭(包括 Sebastian,Babel 的创作者),我们中的一些人选择了离开。

像许多维护者一样,我们偶然发现了 Babel 所担任这个角色。我们中的许多人没有对编译器的工作原理或如何维护开源项目进行过正式培训。更具讽刺的是,我甚至故意避免攻读大学的计算机科学专业,因为我不想上编译器或任何底层课程,因为它似乎显得无趣且非常困难。然而,我发现自己被工具,linter,Babel 和 JavaScript 语言所吸引。

我想鼓励大家研究一下你所依赖的开源项目并支持它们(如果可能的话,还有时间和资金方面的支持)。

许多维护者也并不是他们所从事工作方面的专家;并且工作中还有许多工作要进行。你必须拥有好奇心和谦虚心,这两者都是作为维护者的重要属性。我的愿望是项目拥有美好的未来,而不是我们所有人都在完成"任务"

Babel 不是一家公司,也不像 Facebook 这样级别大公司的开源团队。只有少数志愿者在 Babel 工作,而我也只是最近几个月离职后,并且成为迄今为止唯一一位全职的开源工作者。但人们可以来去匆匆,生活在这种“爱好”之外,抚养家庭,做不同的事,换不同的工作或寻找不同的工作等。我们是否竭尽所能来维持那些对我们工作来说至关重要的事,还是说我们让基金会慢慢感到失望?我们如何保证开源项目的受欢迎且应用广泛,并且有明确的界限?我们可以学习其他维护者的经验

虽然软件已明确开源,但我们真的可以认为它处于健康状态而不考虑其背后的维护者吗?

#BabelSponsorsEverything

Open Source sustainability feels like giving out an offering basket at the moment. It's not difficult to state the value that projects provide to the thousands of people and companies using open source, but yet we don't see that value being shown to the few that are willing to put in the work. There can be so many ways to support open source and yet not all approaches work for each project or people.


现在让我们来看看 Babel 的变化!!

重大突破性变化

We are documenting these in our Migration Guide. Many of these changes can be done automatically with our new babel-upgrade tool, or can be added in the future.

  • 删除对不进行维护的 Node 版本的支持:0.10,0.12,4,5(详情)
  • 通过使用 "scoped" 包将 babel 移动到 @babel 的命名空间中(详情)。这有助于区分官方依赖包,因此 babel-core 变为 @babel/core
  • 删除(并停止发布)任何年度 preset(preset-es2015 等)(详情)。 @babel/preset-env 取代了对这些内容的需求,因为它包含了所有年度所添加内容以及针对特定浏览器集兼容的能力。
  • 同时删除 "Stage" 等 presets (@babel/preset-stage-0 等) 转而选择使用单独的提案插件。同样,默认情况下从 @babel/polyfill 删除提案。(详情)请阅读整篇文章以获取更多相关信息。
  • 些许包进行重命名:任意 TC39 提案的插件现在都是 -proposal ,而不是 -transform (详情)。 因此,@babel/plugin-transform-class-properties 变为 @babel/plugin-proposal-class-properties
  • 为某些面向用户的依赖包引入 @babel/corepeerDependency (例如 babel-loader@babel/cli 等) (详情)

babel-upgrade

babel-upgrade 是我们开始尝试自动进行升级更新的新工具:目前只适用于 package.json.babelrc 的配置文件。

我们建议直接在 git 的仓库中运行 npx babel-upgrade,或者你可以使用 npm i babel-upgrade -g 进行全局安装。

如果你想修改该文件,你可以传递 --write--install 进行。

Shell
npx babel-upgrade --write --install

你可以通过编写 issues 或提交 PRs 来帮助每个人过渡到 Babel 7!未来的规划是,我们会将此工具用于所有重大的版本更新,并会为项目中的 PR 创建机器人。

JavaScript 配置文件

我们正在引进babel.config.js。它不是必须的也不是 .babelrc 的替代品,但是在一些特殊情况会很有用处。

*.js 配置文件在 JavaScript 生态系统中相当常见,ESLint 和 Webpack 分别允许在 .eslintrc.jswebpack.config.js 中进行配置。

以下是在生产环境中使用插件进行编译的情况(你可以在 .babelrc 文件中的 "env" 选项中进行操作):

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

.babelrc 相比,babel.config.js 有着不同的配置解决。它始终会解析该文件中的配置,而不是最初 Babel 从每个文件向上查找,直到找到配置为止。这样就可以利用发布的下一个功能,overrides

选择性的配置 overrides

最近我发表了一篇关于发布 ES2015+ packages 以及 consuming/compiling 的帖子

其中有一节介绍在 Babel 的配置中新的关键字 overrides ,它允许为每个 glob 指定不同的配置。

JavaScript
module.exports = {
presets: [
// 默认配置...
],
overrides: [
{
test: ["./node_modules"],
presets: [
// 配置 node_modules
],
},
{
test: ["./tests"],
presets: [
// 配置测试
],
},
],
};

这允许应用程序对其测试,客户端代码及服务器代码进行不同编译配置,不需要再为每个文件夹创建一个新的 .babelrc 文件。

速度 🏎

Babel 自身更快所以它应该花费更少的时间进行构建!我们做了大量更改对代码进行优化并接受 来自v8团队的补丁。我们很高兴与许多其他优秀的 JavaScript 工具一起成为Web Tooling Benchmark的一部分。

Output 选项

Babel 一段时间以来一直支持 preset 和 plugin 的选项。回顾一下,你能够将插件放在一个数组中,并将一个 options 对象传递给该插件:

{
"plugins": [
- "pluginA",
+ ["pluginA", {
+ // 配置选项...
+ }],
]
}

我们对一些插件的 loose 选项进行了一些更改,并为其他插件添加了一些新选项!注意,在使用这些选项时,你应该清楚你在做什么,因为这是一个非规范行为。当你关掉编译去使用原生语法时,这可能会成为一个问题。如果使用的话,这些选项最好在一个库中进行使用。

  • 对于类, class A {} 将不包括 classCallCheck helper.
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);
}
  • loose 模式下移除了 preset-env 中的 transform-typeof-symbol 插件。#6831

我们发现很多库已经这样做了,所以我们决定设为默认行为。

请注意,默认行为是尽可能符合规范,以便关闭 Babel 时或使用 preset-env 时是无缝衔接的,而允许较小的输出仅仅是为了节省字节空间(每个项目都可以进行权衡)。我们计划开发更好的文档和工具,以使其易于使用。

"Pure" 注入支持

#6209之后,转换后的 ES6 类将使用 /*#__PURE__*/ 进行注释。这允许给像 Uglifybabel-minify 这样的 minfiers 在删除无用代码时提供提示。这些注释也会被添加到其他辅助函数中。

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

如此得知,minifier 的提示和优化可能会有更多的机会!

语法

TC39 提案 支持

我想再次重申下我们已经移除了 Stage presets,转而要求用户明确地选择性的加入低于 Stage 4 的提案。

令人担忧的是,我们会自动选择还未定案或预期不会改变的语法。但事实并非如此,特别是对于 Stage 0 和 Stage 1 的提案。这篇文章解释了一些新想法背后的思考。

以下是一个关于 Babel 支持新语法的小清单(请记住,此功能集是一个可能会增加/删除/停止的变更项)以及在 v7 中添加的功能:

任何人都很难跟踪所有提案,因此我们尝试在 babel/proposals 中做到这一点。

TypeScript 支持 (@babel/preset-typescript)

我们与 TypeScript 团队合作,让 Babel 使用 @babel/preset-typescript 解析/转换类型语法,类似于我们使用 @babel/preset-flow 处理Flow的方式。

有关更多详细信息,请查看 TypeScript 团队的这篇文章!

使用前 (带有 types):

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

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

使用后 (移除 types):

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

Flow 和 Typescript 都是使 JavaScript 用户能够利用渐进类型的工具,我们希望在 Babel 中同时启用它们。我们计划继续与 FB 和 Microsoft 的团队(不包括整个社区)密切合作,以保持兼容性并支持新功能。

这种集成是相当超前的,因此可能不完全支持某些语法。我们非常期待您的帮助,为其提出 issues 并发起 PR(如果可能的话)!

JSX 片段支持 (<>)

正如 React 博客中所提到的,从 Beta.31 开始,JSX 片段已支持。

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

// 输出 👇

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-corejs2(PR)。前者仅包含 Babel 的辅助函数,后者包含前者和所有 polyfill 函数(例如:SymbolPromise)。

Babel 可能需要将某些函数注入到可重用的代码中。我们只是将这些"辅助函数"称为模块之间共享的函数。

An example of this is with compiling a class (without loose mode on):

从规范的角度来说你需要通过 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's helpers 的独立文章。

自动 Polyfill (试验)

对于不支持某些功能(例如:PromiseSymbol)的环境中,为了启用这些功能添加 Polyfills 是必要的。在区分 Babel 到底是作为编译器(转换语法)还是 polyfill (实现内置 functions/objects)时,这同样很重要的。

只需引入涵盖所有的 @babel/polyfill 就足够了:

JavaScript
import "@babel/polyfill";

但是它包含完整的 polyfill,如果某些功能浏览器已经支持,你可能不需要全部引入。这与 @babel/preset-env 试图用语法解决的问题相同,所以我们将它应用于 polyfill 中。选项 useBuiltins: "entry" 通过将原始 import 拆分成按需 import 来解决该问题。

我们可以仅 import 代码中所用到的 polyfill,这样实现会更佳。选项 "useBuiltIns: "usage" 是我们第一次尝试这样做(相关文档)。

它将遍历每个文件,如果"用到"了某个 polyfill,它会在文件的顶部注入一个 import 。 例如:

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

推论并不完美,因此可能存在误报。

JavaScript
import "core-js/modules/es7.array.includes";
a.includes; // 假设 a 是一个 []

关于这方面的其他想法是,如果你可以依赖服务端(或者阅读 Kent C. Dodds 如何在在 Paypal 上构建服务)的话,可以尝试使用polyfill.io

其他

Babel 宏(Macros) 🎣

Babel 最好的部分之一是该工具的可插拔性。多年来,Babel 从仅仅是一个 "6to5" 的编译器,变为代码转译平台,为用户和开发者提供了一系列出色的优化方案。Babel 已经为特定的库和用例开发了大量的 Babel 插件以提高库的 API 的性能和能力,不然它们将无法实现(某些库完全被删除,导致根本没有运行时)。

遗憾的是,将这些插件添加到代码库需要更改配置(某些工具如 create-react-app 不允许这样做)这增加了代码的复杂性,因为开发人员必须知道 Babel 插件在文件上做了什么。这些问题已被 Kent C. Dodds 解决!(babel-plugin-macros)

一旦安装了 babel-plugin-macros 并将其添加到你的配置中create-react-app v2 中已经包含),你就不需要操心在你的配置中使用任何 Macros。此外,为特定的应用程序或部分代码编写自定义的转换也会更加容易。

在我们的文章"使用 babel-plugin-macros 零配置代码转换"中了解更多有关 babel-plugin-macros 信息。

模块定位

Babel 一直试图平衡转译后代码体积的大小及兼容性问题,并提供给 JavaScript 的作者。在 Babel 7 中,配置 Babel 以支持模块/非模块模式会变得更加容易。

值得注意的是,部分流行的 Web 框架 (12) 的 CLI 工具已经在利用这些支持,这使得 Babel 转译后的应用程序向用户发送的 JavaScript 减少了大约 20% 。

调用者的元数据和更好的默认值

我们在 @babel/core 中添加了一个 caller 选项,因此我们的工具可以将元数据传递给 presets/plugins 。 例如: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 {}

我们最古老的 issues 之一就是本节的标题 (详情)

Babel 总是会警告说它不支持扩展原生对象(Array, Error 等),但是现在它可以了!在这方面我们遇到了很多的问题;🎉 你应该像 Andrea 一样庆祝!

这个更改时针对 class 插件进行的,如果你使用了 preset-env ,那么则默认开启了该特性。

网站变更 🌏

我们的网站从 Jekyll 搬到了 Docusaurus !

我们还使用 Crowdin 为 Babel 官网建立了翻译功能,而 Babel 7 的翻译,将成为我们更好的起点。

REPL

我们将 REPL 重写为 React 组件,并且已经与 Ives 合作,更好地与 CodeSandbox 集成。这允许在 REPL 中可以使用 npm 安装任何插件或 preset ,并且能从 CodeSandbox 获得的任何更新。

我们再次参加了 Rails Girls Summer of Code!这次,GyujinSujin  长期以来一直在做整合 Boopathibabel-time-travel 到 REPL 中的工作,现在已可以试用!

在这里有很多机会让 Babel,AST 和其他工具更好地协同工作!

我们有一首歌 🎶

哈利路亚 — 赞美 Babel

有一天 Angus 给我们传了一首我觉得很棒的,为什么不把它作为我们的 "官方" 歌?Shawn这里 做了一个精彩的封面。

你可以在我们库中的 SONG.md 中找到它。我们希望看到其他项目也能跟进此事!

未来?

  • Babel 本质上与它编译的内容——JavaScript 联系在一起。 只要有新的提案/工作,那就有事情可做。这包括在语法变得"稳定"之前花费时间/精力实现和维护该语法。我们关心整个过程:升级路径,新功能的教育,标准/语言设计的教学,易用性以及与其他项目的集成。
    • 相关:在 Nicolò 的帮助下,我们几乎完成了在 Babel 中实施新装饰器的提案。这是一个漫长的旅程(它花了一年多的时间!),因为新的提案与之前完全不同,而且要比旧的更强大,已基本完成 🎉。你可以期待它在下一个次要版本中发布,同时还会有一篇文章将详细解释这些变化。
    • Boopathi 一直在努力维护 babel-minify,所以我们将为此做一个 1.0!
    • 整体工作中有许多新功能:插件排序,更好的验证/错误机制,提升速度,重新思考 loose/spec 选项,缓存,异步使用 Babel,从 CI 构建自身,冒烟测试(smoke tests)以及运行 test262 等。查阅此 roadmap 文档以获取更多可能的想法!

我们没有秘密计划:我们正竭尽全力为这个社区服务。

开源是一面镜子

如果我们有足够的时间和资源来完成所有这些想法并且做得很好,我会很高兴。但正如当前版本中所展示的那样,事情需要比预期更久的时间!

为什么这些版本需要这么长时间呢?是来自我们自身和用户的功能蠕变吗?还是因为我们不理解如何在所有可能的事情中添加或修复的事务中确定优先级吗?还是说在决定结束之前解决了大量容易的问题或者基本的问题?又或者说在 GitHub,Slack,Twitter 上帮助别人从而导致分心?我们只估计我们自己的时间,了解这些问题的复杂性,但作为过度使用的志愿者是不是显得很糟糕?

Or are we just setting too high of an expectation on ourselves, or feel so pressured by others to perform and fit to their needs at the harm of ourselves? I can only describe it as dread when seeing a message from someone online wondering why something hasn't been released while another asks why this bug isn't fixed yet. I want to just rush it out and be done with it but I also have a desire to take this seriously.

我上周在 React Rally 的演讲中尝试表达了一些这方面的想法和挣扎:看着镜(开源)中的自己,希望你借此机会倾听。我问自己的问题:对于维护者倦怠,持续焦虑和不切实际的期望造成的不可避免的循环,我能为此做些什么?

像生活中的大部分情况一样,我们所做的事情反映了我们的品格,并向我们展示了我们真实的面貌。我们采取行动本身就可以改变我们,无论好坏。如果我们要认真对待我们的工作,我们就应该保持彼此对这些习惯的责任感,这些习惯似乎深深植根于我们的文化中:即时满足,指标、权利与感恩方面的成功以及过度工作产生的自豪感。

尽管如此,努力实现这一版本还是非常值得的。

感谢

这真的是一个真正令人兴奋的版本,不仅通过回顾我们已经完成和启用的东西,而且更多的是了解在过去的一年中为了实现它花费了多少时间和心血。很难相信在此过程中发生的机遇和经历:与来自世界各地的公司互动和帮助,在我访问过的任何城市都能找到朋友,并坦诚地讲述团队共同努力过程中令人难以置信的经历。

就我个人而言,我从来没有真正把我的精力投入到像如此规模的任何事情中,我要感谢这么多人一路上帮助我们。特别是 Logan Smyth,他花了很长时间来改变核心的工作方式,并且总是业余时间在 Slack 中解决问题,同时也是自由职业者,还有 Brian Ng ,他也很努力地维护 Babel 以及审查我的所有博客帖子和会谈。Daniel TschinderSven SauleauNicolò RibaudoJustin Ridgewell 都在帮助这个版本成为可能和愉快工作气氛方面都发挥了重要作用。

向 Slack,Twitter 以及 GitHub 上的所有项目中的所有社区成员致敬,他们还必须了解我们正在为用户所做的工作。我要衷心地感谢我在 Behance/Adobe 的朋友们,在我决定全职工作之前,为我提供了将近一年半的时间让我为 Babel 兼职(以及在我在那里的整个过程中帮助测试生产模式下的 Babel 7)。非常感谢我们的 Open Collective 的所有赞助商,特别是 TrivagoHandshake。感谢我们的朋友和家人的所有爱和支持。

我们都很辛苦,但以这种方式服务确实是一种荣幸,所以我们希望你喜欢这一次发布!