跳至主内容

7.0 版本规划

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

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

如果你还不知道的话,我们计划很快发布 7.0 版本 🙌!实际上,相关工作早在二月份就启动了,当时我的目标只是发布一个停止支持 Node 0.10/0.12 并移除 babel-runtime 及其他代码的版本。自那以后,我们已经发布了直到 alpha.20 的多个版本。

我们将在整个 Beta 版本发布期间持续更新这篇文章

由于我们仍然是一个纯志愿者项目,团队大多数人很难在完成这些变更的同时保持专注和积极性——尤其是这个项目被众多公司、训练营和工具深度依赖的情况下。但在此期间我们仍取得了重大进展:每周会议记录、作为受邀嘉宾参加最近的 TC39 会议、协助组织 RGSoCGSoC 活动,并创建了 Open Collective

升级指南

对大多数项目而言,升级只需将 package.json 中的依赖更新至 7.0.0-beta.0。在整个 7.0 开发周期中,我们已在 Babel 自身(非常元)和我所在的 Behance 工作环境中持续使用该版本。

在正式版发布前,我们将把所有依赖固定为精确版本。

{
"devDependencies": {
"babel-cli": "7.0.0-beta.0"
}
}

具体包变更:

Details

babel packages in the monorepo should all be >= 7.0.0-beta.0
babel-preset-env should be at least 2.0.0-beta.0
babel-eslint can be >= 8.0.0
babel-loader should be >= 7.0.0 (out of beta since it uses babel-core as a peerDependency)
grunt-babel can be >= 7.0.0
gulp-babel can be >= 7.0.0
rollup-plugin-babel can be >= 3.0.2


请查阅我们的 升级指南 以及专门为 工具作者 准备的指南,我们将根据需要持续更新这些文档。

我想重点介绍首个 Beta 版本中的显著变更(在破坏性变更范围上,它仍比之前的 6.0 版本小得多)。

重申项目目标

在深入讨论前,请允许我再次阐明 Babel 的核心使命。

自 Babel 从 6to5 更名以来,浏览器已实现更多规范特性,用户也愈加习惯使用最新语法和构建工具。但 Babel 的核心目标始终未变。

我们的两大目标相辅相成:

  1. 帮助开发者将新语法转换为向后兼容的代码(抽象浏览器差异)

  2. 搭建桥梁:协助 TC39 获取 ECMAScript 新提案的反馈,让社区参与语言未来建设

因此,说 Babel 是 JavaScript 生态的关键组成部分绝非夸大(仅 babel-core 月下载量就达近千万次),它亟需社区的支持。(我所有演讲都围绕这一主题:JSConf EUReact RallyTC39)。最近我提出:"如果 Babel 消亡会怎样?" 当当前维护者感到倦怠/转向其他领域时(或这种情况已经发生),我们该如何应对?我不想只是呼吁大家帮助——作为项目使用者,你们本身就是我们的一部分。

那么,现在让我们具体聊聊这些变更!

停止对未维护 Node 版本的支持:0.10、0.12、5 (#4315)

开源项目的进步往往伴随着用户升级的成本。正因如此,我们一直对引入主版本升级/破坏性变更持谨慎态度。通过放弃对不再维护的 Node 版本的支持,我们不仅能改进代码库,还能升级依赖项和工具链(如 ESLint、Yarn、Jest、Lerna 等)。

👓 提案更新/规范合规性

又名:你们大多数人唯一关心的部分 😅

设计理念(提案:规范模式、宽松模式、默认行为)

我们创建了新仓库 babel/proposals 来追踪各项 TC39 提案 的进展。

我们新增了 关于新提案采纳标准 的说明。基本原则是:只要 TC39 提案负责人准备演示(阶段 0),我们就开始接受相关 PR。当规范变更时,我们会在社区协助下同步更新。

我们将默认尽可能遵循规范(在合理性能范围内),这意味着如果需要更快的构建速度或更小的体积,应使用 loose 选项主动忽略某些规范要求(如运行时检查等边界情况)。这种设计是刻意为之:进阶用户明确知晓风险,而普通用户应能无缝升级 babel-preset-env 使用原生语法,或在完全停用 Babel 时不受影响。

阶段 3:类属性(从阶段 2 升级)

babel-plugin-transform-class-properties:默认行为现已采用原 "spec" 选项模式,使用 Object.defineProperty 替代简单赋值。

当前变更导致 旧版装饰器插件(在 7.0 中更名为 "transform-decorators")无法装饰类属性。若需兼容当前装饰器转换实现,必须启用 loose 选项,直至我们发布阶段 2 装饰器插件。

私有字段功能开发中:#6120

输入

JavaScript
class Bork {
static a = 'foo';
x = 'bar';
}

输出(默认模式)

JavaScript
class Bork {
constructor() {
Object.defineProperty(this, "x", {
configurable: true,
enumerable: true,
writable: true,
value: 'bar'
});
}
};

Object.defineProperty(Bork, "a", {
configurable: true,
enumerable: true,
writable: true,
value: 'foo'
});

输出(宽松模式)

JavaScript
class Bork {
constructor() {
this.x = 'bar';
}
};
Bork.a = 'foo';

阶段 3:对象剩余展开(从阶段 2 升级)

babel-plugin-transform-object-rest-spread:该插件现已支持非字符串键(如 Number/Symbol 类型)。

输入

JavaScript
// Rest Properties
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }

// Spread Properties
let n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }

同时禁止以下用法

JavaScript
var { ...{ x } } = obj;
var { ...[ y ] } = obj;

阶段 3:可选 catch 绑定(新增)

babel-plugin-transform-optional-catch-binding:允许开发者使用 try/catch 时无需创建未使用的绑定变量。

输入

JavaScript
try {
throw 0;
} catch {
doSomethingWhichDoesNotCareAboutTheValueThrown();
}

输出

JavaScript
try {
throw 0;
} catch (_unused) {
doSomethingWhichDoesNotCareAboutTheValueThrown();
}

阶段 3:Unicode 属性正则(新增)

babel-plugin-transform-unicode-property-regex:将 Unicode 属性转义符(\p{…}\P{…})编译为适用于当前环境的 ES5/ES6 正则表达式。

输入

JavaScript
var regex = /\p{ASCII_Hex_Digit}/u;

输出

JavaScript
var regex = /[0-9A-Fa-f]/;

阶段 3:BigInt(新增,未完成)

babel-plugin-transform-bigint: #6015。 该插件不会包含在 Stage 预设中,因为包装所有操作符会导致性能下降。

输入

JavaScript
50000n + 60n;

输出

JavaScript
import babelCheckBinaryExpressions from "babel-check-binary-expressions";
babelCheckBinaryExpressions(new BigInt("50000"), new BigInt("60"), "+");

Stage 3:动态导入(由 Stage 2 升级)

babel-plugin-syntax-dynamic-import:你只需解析语法,因为像 Webpack 这样的工具可以代替 Babel 处理转换 另有一个适用于 Node 的插件

输入

JavaScript
const testModule = import('test-module');

Stage 2:import.meta(仅语法支持)

该元属性仅在模块中语法有效,用于表示由宿主环境提供的当前运行模块的元信息

输入

JavaScript
const size = import.meta.scriptElement.dataset.size || 300;

Stage 2:数值分隔符(新增)

babel-plugin-transform-numeric-separator:通过在数字分组之间创建视觉分隔符(_)使数值字面量更易读

输入

JavaScript
1_000_000_000
0b1010_0001_1000_0101
0xA0_B0_C0

输出

JavaScript
1000000000
0b1010000110000101
0xA0B0C0

Stage 2:装饰器(由 Stage 1 升级),仍在开发中

babel-plugin-transform-decorators#6107

禁止的

JavaScript
// no computed decorator keys
@dec[foo]
class A {}

// no parameter decorators (a separate proposal)
class Foo {
constructor(@foo x) {}
}

// no decorators on object methods
var o = {
@baz
foo() {}
}

// decorator cannot be attached to the export
@foo
export default class {}

有效的

JavaScript
// decorators with a call expression
@foo('bar')
class A {
// decorators on computed methods
@autobind
[method](arg) {}
// decorators on generator functions
@deco
*gen() {}
// decorators with a member expression
@a.b.c(e, f)
m() {}
}

// exported decorator classes
export default @foo class {}

尚未支持(开发中)

JavaScript
// decorated class properties
class A {
@dec name = 0
}

Stage 2:function.sent(新增)

babel-plugin-transform-function-sent:编译 function.sent 元属性,该属性在生成器函数内部使用

输入

JavaScript
function* generator() {
console.log("Sent", function.sent);
console.log("Yield", yield);
}

const iterator = generator();
iterator.next(1); // Logs "Sent 1"
iterator.next(2); // Logs "Yield 2"

输出

JavaScript
let generator = _skipFirstGeneratorNext(function* () {
const _functionSent = yield;
console.log("Sent", _functionSent);
console.log("Yield", yield);
});

Stage 2:export-ns-from

babel-plugin-transform-export-namespace:导入/重新导出一个命名空间的简写形式,从旧的 transform-export-extensions 插件拆分而来(该插件曾合并此提案与另一个提案)

输入

JavaScript
export * as ns from "mod";

输出

JavaScript
import * as ns from "mod";
export {ns};

Stage 1:export-default-from

babel-plugin-transform-export-default:导入/重新导出内容的简写形式,从旧的 transform-export-extensions 插件拆分而来(该插件曾合并此提案与另一个提案)

输入

JavaScript
export v from "mod";

输出

JavaScript
import _v from "module";
export { _v as v };

Stage 1:可选链(新增)

babel-plugin-transform-optional-chaining:操作符 (?.) 允许安全访问深层嵌套对象属性,无需担心中间层为 undefined

输入

JavaScript
a?.b = 42;

输出

JavaScript
var _a;
(_a = a) == null ? void 0 : _a.b = 42;

ES2015:new.target

babel-plugin-transform-new-target:此前未实现 new.target 支持,现提供独立插件并包含在 ES2015/env 预设中

示例

JavaScript
// with a function
function Foo() {
console.log(new.target);
}

Foo(); // => undefined
new Foo(); // => Foo

// with classes
class Foo {
constructor() {
console.log(new.target);
}
}

class Bar extends Foo {
}

new Foo(); // => Foo
new Bar(); // => Bar

输入

JavaScript
class Foo {
constructor() {
new.target;
}

test() {
new.target;
}
}

输出

JavaScript
class Foo {
constructor() {
this.constructor;
}

test() {
void 0;
}
}

🚀 新特性

.babelrc.js

babel/babel#4630

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

使用 JavaScript 编写配置文件支持动态配置,可创建自适应不同环境的单一配置文件

JavaScript
var env = process.env.BABEL_ENV || process.env.NODE_ENV;
var plugins = [];
if (env === 'production') {
plugins.push.apply(plugins, ["a-super-cool-babel-plugin"]);
}
module.exports = { plugins };
JavaScript
var env = process.env.BABEL_ENV || process.env.NODE_ENV;
module.exports = {
plugins: [
env === 'production' && "another-super-cool-babel-plugin"
].filter(Boolean)
};

此前通过 env 配置选项实现,现该选项已弃用,详见下文

TypeScript

你现在可以使用 babel-preset-typescript 让 Babel 剥离类型,其工作方式类似于 babel-preset-flow

babel.config.json
{
"presets": ["typescript"]
}

我们正在与 TypeScript 团队合作编写设置指南,该指南将在 7.0 正式版发布前完成。简而言之,你需要用 --noEmit 配置 TypeScript 或在编辑器/监视模式下使用它,这样就能继续使用 preset-env 和其他 Babel 插件。

针对代码压缩工具的特定转换中的"Pure"注解

#6209 之后,转译后的 ES6 类将包含 /*#__PURE__*/ 注释,Uglify 和 babel-minify 等代码压缩工具可利用此标记进行死代码消除。未来这些注解可能也会应用于我们的辅助函数。

输入

JavaScript
class C {
m(x) {
return 'a';
}
}

输出

JavaScript
var C = /*#__PURE__*/ function () {
function C() {
_classCallCheck(this, C)
}
C.prototype.m = function m(x) {
return 'a';
};
return C;
}();

😎 其他破坏性变更

babel-preset-react 中移除 babel-preset-flow

这个变更是必要的,因为我们收到许多未使用类型/Flow 的用户的反馈:他们在使用 react 预设时编写了无效 JS 代码,但由于预设包含 Flow 支持,这些错误未能被语法检查捕获。

此外,现在我们有了 TypeScript 预设,继续在 react 预设中捆绑 flow 已不合时宜。

集成方案调整

grunt-babelgulp-babelrollup-plugin-babel 等集成包过去都将 babel-core 列为直接依赖项。

v7 之后,我们计划将 babel-core 改为 peerDependency(如同 babel-loader 的做法)。这样当 babel-core API 未变更时,这些集成包无需更新主版本号。因此它们现已发布为 7.0.0 版本,预计后续不会再有改动。

项目元信息

移除 Babel 自身对 babel-runtime 的依赖 (#5218)

Babel 本身外部依赖不多,但在 6.x 中每个包都依赖 babel-runtime,以便无需 polyfill 即可使用 Symbol、Map、Set 等内置对象。通过将最低 Node 支持版本改为 v4(这些内置对象已原生支持),我们可以彻底移除此依赖。

这在 npm 2(我们不建议将其用于 Babel 6)和旧版 yarn 中会造成问题,但 npm 3 因其依赖去重机制不受影响。

Create React App 中,当 babel-runtime 被提升时 node_modules 文件夹大小变化显著:

  • npm 3 的 node_modules:约 120MB

  • Yarn (≤0.21.0) 的 node_modules:约 518MB

  • node_modules(用于 Yarn(≤0.21.0)并提升 babel-runtime):约 157MB

  • node_modules(Yarn + PR #2676):约 149MB(推文

虽然使用 npm ≥3 或新版 Yarn 已在上游修复此问题,但我们仍可通过移除自身对 babel-runtime 的依赖贡献力量。

实验性包的独立发布 (#5224)

我在 Babel 现状报告Versioning 章节提到过此机制。GitHub Issue

你可能还记得,在 Babel 6 之后,Babel 演变成了一组 npm 包,形成了自己的自定义预设(preset)和插件(plugin)生态系统。

但自那时起,我们始终采用"固定/同步"的版本管理策略(确保没有包单独升级到 v7.0 或更高)。当我们发布新版本(例如 v6.23.0)时,只有源码发生变更的包会更新版本号,其余包则保持不变。这种机制在实践中基本可行,因为我们在包管理中使用了 ^ 符号。

遗憾的是,这种策略要求一旦有某个包需要主版本升级,所有包都必须同步升级主版本。这要么导致频繁的小范围破坏性变更(非必要),要么迫使我们将大量破坏性变更堆积到单次发布中。因此我们计划区分实验性包(Stage 0 等)和其他包(如 es2015)。

这意味着当规范变更时,我们将直接对实验性提案插件进行主版本升级,而无需等待整个 Babel 更新。因此任何低于 Stage 4 阶段的提案都可能通过主版本升级引入破坏性变更,阶段预设(Stage presets)本身若不彻底弃用也将遵循此规则。

例如:

假设你正在使用 preset-env(它会持续更新,目前包含 es2015、es2016、es2017 的所有特性)和一个实验性插件,同时还因为实用而采用了 object-rest-spread。

babel.config.json
{
"presets": ["env"],
"plugins": ["transform-object-rest-spread"]
}

如果某个实验性提案的规范发生变化,我们将自由地对该插件进行破坏性变更并升级其主版本号。由于这仅影响该插件本身,用户可以在适当时候自主更新。我们旨在确保用户尽可能更新到实验性提案的最新版本,并在合理范围内提供自动化更新工具。

💀 可能的弃用项

弃用 .babelrc 中的 "env" 选项

babel/babel#5276 编辑:我们已将该选项的行为改为更直观的方式,因此并未移除。

"env" 配置选项(请勿与 babel-preset-env 混淆)一直是用户困惑的根源,这一点从大量问题反馈中可见一斑。

当前行为是将配置值合并到顶层配置中,这种机制不够直观,导致开发者最终不在顶层放置配置,而是在不同环境(env)下重复所有预设/插件。

为消除困惑(并服务高级用户),我们考虑完全弃用 env 配置选项,建议用户改用下文将介绍的 JS 配置格式。

弃用 ES20xx 预设(已完成)

我们早已弃用 preset-latest,并较早前弃用了 ES2016/ES2017 预设 维护年度预设相当繁琐(额外包/依赖,可能遭遇 npm 包抢注问题,除非采用作用域包)

开发者本就不该纠结该用哪个年度预设?若弃用这些预设,所有人都可转用 babel-preset-env,它会随规范变化自动更新。

🤔 问题

弃用/重命名/移除 Stage X 预设(已完成)

编辑:我们已完成此项变更,并撰写了专门文章说明

社区中许多人(包括 TC39)都对 Stage X 预设表达了担忧。我认为当初添加它们主要是为了提供从 Babel 5 迁移到 Babel 6 的便捷路径(之前曾有个"stage"选项)。

虽然我们希望工具简单易用,但事实证明许多公司/开发者长期在生产环境中使用这些"尚非正式 JavaScript"的预设。"Stage 0"这个名称给人的感觉,与babel-preset-dont-use-this-stage-0这样的警示性命名完全不同。

Ariya 最近做了个很棒的投票调查,正好说明了我讨论的问题

开发者们实际上并不清楚哪些特性属于哪个 JavaScript 版本(他们也不该需要知道)。但当我们都开始误以为那些尚处于提案阶段的"特性"已是正式规范时,问题就出现了。

许多开源项目(包括 Babel 自己 😝)、教程、会议演讲等都使用stage-0。React 推广了 JSX、类属性(现为 Stage 3)、对象剩余/展开(现为 Stage 3)的使用,而我们全都认为这就是 JavaScript,因为 Babel 为我们编译了这些语法。也许去除这种抽象层能帮助人们更清楚现状,理解选择 Stage X 插件时的权衡取舍。

维护自己的预设似乎也比更新 Stage 预设要简单得多。

我常看到人们说"我需要对象剩余属性,它在 Stage 2,所以我启用了 stage-2"。结果他们启用了大量其他实验性特性,这些他们可能不了解也不需要。 而且随着提案阶段变化,未使用 shrinkwrap 或 yarn 的用户可能不知不觉就获得新特性。如果某个特性被废弃,他们甚至可能发现功能突然消失。@glenjamin

使用 npm 作用域包(已完成,@babel/x

看起来理解作用域包概念的人大多表示支持?

优点

  • 无需担心特定包名被占用(这正是最初提出此方案的原因)

很多包名已被占用(preset-es2016、preset-es2017、2020、2040 等)。虽然可以申请转移,但操作不便且容易让用户因命名误认某些包是官方的

缺点

  • 需要迁移到新语法

  • 某些非 npm 工具仍不支持(存在锁定风险)

  • 除非为旧名称设置别名,否则无法统计下载量

看来我们可能暂缓此方案,至少名称变更不会构成破坏性更新

external-helperstransform-runtimebabel-polyfill

编辑说明:我们已将 transform-runtime 使用的 @babel/runtimecore-js 分离

"regeneratorRuntime 未定义"——这个问题被反复报告

本质上我们需要更好的解决方案来处理内置函数/polyfill

  • 开发者不知道 regenerator-runtime 是什么,他们只想用生成器/异步函数

  • 很多开发者困惑为何需要运行时,或为何 Babel 不编译 PromiseObject.assign 等内置函数

  • 开发者分不清 Babel 插件 transform-runtime 和运行时本身 babel-runtime

  • 抱怨生成代码体积大,因为 babel-polyfill 包含全部 polyfill(虽然现在有useBuiltIns),且少有人知晓 external-helpers

我们能否整合/替换这些包,提供更简单的默认体验?

后续计划

我们希望社区能升级版本并提供反馈/问题报告。初期可能会有大量活动令人应接不暇,恳请大家保持耐心。我们亟需协助进行问题分类、编写文档/升级指南/技巧说明,以及开发代码修改工具(codemods)来帮助他人更顺畅地升级。由于 Babel 影响着整个 JavaScript 生态链,升级可能不只是更新单个包那么简单——它可能依赖于 npm 上的其他社区 Babel 插件。我们不能坐等一个月指望用户自行升级,要让整个社区明年不再停留在 6.x 版本,还需要大量工作。我不希望任何项目(和个人)被落下。请告知我们能如何协助,同时也希望大家能互相扶持,共同推动社区发展。

项目可持续性

特别感谢我在 Behance 的团队允许我在工作中兼职维护 Babel;目前我们仍是唯一以工作时间为 Babel 提供支持的公司。能在工作时间内支持这个项目(而非仅靠业余时间),我深感庆幸,也希望未来能有更多维护者获得这样的机会。(希望我们已为企业如何支持其使用而非"拥有"的开源项目树立了良好范例)。

我们在 Open Collective 的资金仍不足以支持全职开发者:目前收到的最高单笔捐赠来自 Webflow 的 $750,最高月捐是多家公司/个人的 $100。因此我们需要加大募捐力度,或争取更多像 AMP/Google 这样的企业支持(最近加入团队的 @jridgewell 也能投入工作时间,这已带来显著改变)。

请询问贵司是否愿意通过 Open Collective 赞助,并告知我们缺失的支持、您能参与的方式。参与甚至不需要特定理由——如果贵团队关心项目的长期可持续发展,请建立连接并参与贡献。

未来规划

7.0 发布后,我们有许多潜在方向值得探索(这些多年前就已提出):将遍历逻辑与插件分离(异步访问器?)、不可变 AST、语法扩展?基础设施方面:集成 test262 与冒烟测试、优化从提案到转换的 GitHub 工作流、构建自动化升级的代码修改工具基础设施等。

请关注我们在 babel/notes 的会议记录/讨论,并参与进来!

致谢!

我们有望尽快发布正式版,但必须强调:开源项目依靠的是日复一日的持续维护,而非昙花一现的发布周期——我们不会抛下社区只顾前进。因此修复漏洞和升级生态可能需要更长时间,敬请理解。