7.0 版本规划
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
如果你还不知道的话,我们计划很快发布 7.0 版本 🙌!实际上,相关工作早在二月份就启动了,当时我的目标只是发布一个停止支持 Node 0.10/0.12 并移除 babel-runtime 及其他代码的版本。自那以后,我们已经发布了直到 alpha.20 的多个版本。
我们将在整个 Beta 版本发布期间持续更新这篇文章
由于我们仍然是一个纯志愿者项目,团队大多数人很难在完成这些变更的同时保持专注和积极性——尤其是这个项目被众多公司、训练营和工具深度依赖的情况下。但在此期间我们仍取得了重大进展:每周会议记录、作为受邀嘉宾参加最近的 TC39 会议、协助组织 RGSoC 和 GSoC 活动,并创建了 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 的核心目标始终未变。
我们的两大目标相辅相成:
-
帮助开发者将新语法转换为向后兼容的代码(抽象浏览器差异)
-
搭建桥梁:协助 TC39 获取 ECMAScript 新提案的反馈,让社区参与语言未来建设
因此,说 Babel 是 JavaScript 生态的关键组成部分绝非夸大(仅 babel-core 月下载量就达近千万次),它亟需社区的支持。(我所有演讲都围绕这一主题:JSConf EU、React Rally、TC39)。最近我提出:"如果 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
输入
class Bork {
static a = 'foo';
x = 'bar';
}
输出(默认模式)
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'
});
输出(宽松模式)
class Bork {
constructor() {
this.x = 'bar';
}
};
Bork.a = 'foo';
阶段 3:对象剩余展开(从阶段 2 升级)
babel-plugin-transform-object-rest-spread:该插件现已支持非字符串键(如 Number/Symbol 类型)。
输入
// 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 }
同时禁止以下用法
var { ...{ x } } = obj;
var { ...[ y ] } = obj;
阶段 3:可选 catch 绑定(新增)
babel-plugin-transform-optional-catch-binding:允许开发者使用 try/catch 时无需创建未使用的绑定变量。
输入
try {
throw 0;
} catch {
doSomethingWhichDoesNotCareAboutTheValueThrown();
}
输出
try {
throw 0;
} catch (_unused) {
doSomethingWhichDoesNotCareAboutTheValueThrown();
}
阶段 3:Unicode 属性正则(新增)
babel-plugin-transform-unicode-property-regex:将 Unicode 属性转义符(\p{…}和\P{…})编译为适用于当前环境的 ES5/ES6 正则表达式。
输入
var regex = /\p{ASCII_Hex_Digit}/u;
输出
var regex = /[0-9A-Fa-f]/;
阶段 3:BigInt(新增,未完成)
babel-plugin-transform-bigint: #6015。 该插件不会包含在 Stage 预设中,因为包装所有操作符会导致性能下降。
输入
50000n + 60n;
输出
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 的插件
输入
const testModule = import('test-module');
Stage 2:import.meta(仅语法支持)
该元属性仅在模块中语法有效,用于表示由宿主环境提供的当前运行模块的元信息
输入
const size = import.meta.scriptElement.dataset.size || 300;
Stage 2:数值分隔符(新增)
babel-plugin-transform-numeric-separator:通过在数字分组之间创建视觉分隔符(_)使数值字面量更易读
输入
1_000_000_000
0b1010_0001_1000_0101
0xA0_B0_C0
输出
1000000000
0b1010000110000101
0xA0B0C0
Stage 2:装饰器(由 Stage 1 升级),仍在开发中
禁止的
// 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 {}
有效的
// 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 {}
尚未支持(开发中)
// decorated class properties
class A {
@dec name = 0
}
Stage 2:function.sent(新增)
babel-plugin-transform-function-sent:编译function.sent元属性,该属性在生成器函数内部使用
输入
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"
输出
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插件拆分而来(该插件曾合并此提案与另一个提案)
输入
export * as ns from "mod";
输出
import * as ns from "mod";
export {ns};
Stage 1:export-default-from
babel-plugin-transform-export-default:导入/重新导出内容的简写形式,从旧的transform-export-extensions插件拆分而来(该插件曾合并此提案与另一个提案)
输入
export v from "mod";
输出
import _v from "module";
export { _v as v };
Stage 1:可选链(新增)
babel-plugin-transform-optional-chaining:操作符 (?.) 允许安全访问深层嵌套对象属性,无需担心中间层为 undefined
输入
a?.b = 42;
输出
var _a;
(_a = a) == null ? void 0 : _a.b = 42;
ES2015:new.target
babel-plugin-transform-new-target:此前未实现new.target支持,现提供独立插件并包含在 ES2015/env 预设中
示例
// 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
输入
class Foo {
constructor() {
new.target;
}
test() {
new.target;
}
}
输出
class Foo {
constructor() {
this.constructor;
}
test() {
void 0;
}
}
🚀 新特性
.babelrc.js
*.js 配置文件在 JavaScript 生态中相当常见,ESLint 和 Webpack 分别支持 .eslintrc.js 和 webpack.config.js 配置形式
使用 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 };
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!
{
"presets": ["typescript"]
}
我们正在与 TypeScript 团队合作编写设置指南,该指南将在 7.0 正式版发布前完成。简而言之,你需要用 --noEmit 配置 TypeScript 或在编辑器/监视模式下使用它,这样就能继续使用 preset-env 和其他 Babel 插件。
针对代码压缩工具的特定转换中的"Pure"注解
在 #6209 之后,转译后的 ES6 类将包含 /*#__PURE__*/ 注释,Uglify 和 babel-minify 等代码压缩工具可利用此标记进行死代码消除。未来这些注解可能也会应用于我们的辅助函数。
输入
class C {
m(x) {
return 'a';
}
}
输出
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-babel、gulp-babel、rollup-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
虽然使用 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。
{
"presets": ["env"],
"plugins": ["transform-object-rest-spread"]
}
如果某个实验性提案的规范发生变化,我们将自由地对该插件进行破坏性变更并升级其主版本号。由于这仅影响该插件本身,用户可以在适当时候自主更新。我们旨在确保用户尽可能更新到实验性提案的最新版本,并在合理范围内提供自动化更新工具。
💀 可能的弃用项
弃用 .babelrc 中的 "env" 选项
.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)
Thoughts on @babeljs using npm scoped packages for 7.0?
— Henry Zhu (@left_pad) January 18, 2017
看起来理解作用域包概念的人大多表示支持?
优点
- 无需担心特定包名被占用(这正是最初提出此方案的原因)
很多包名已被占用(preset-es2016、preset-es2017、2020、2040 等)。虽然可以申请转移,但操作不便且容易让用户因命名误认某些包是官方的
缺点
-
需要迁移到新语法
-
某些非 npm 工具仍不支持(存在锁定风险)
-
除非为旧名称设置别名,否则无法统计下载量
看来我们可能暂缓此方案,至少名称变更不会构成破坏性更新
external-helpers、transform-runtime、babel-polyfill
编辑说明:我们已将 transform-runtime 使用的 @babel/runtime 和 core-js 分离
"regeneratorRuntime 未定义"——这个问题被反复报告
本质上我们需要更好的解决方案来处理内置函数/polyfill
-
开发者不知道 regenerator-runtime 是什么,他们只想用生成器/异步函数
-
很多开发者困惑为何需要运行时,或为何 Babel 不编译
Promise、Object.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 的会议记录/讨论,并参与进来!
致谢!
我们有望尽快发布正式版,但必须强调:开源项目依靠的是日复一日的持续维护,而非昙花一现的发布周期——我们不会抛下社区只顾前进。因此修复漏洞和升级生态可能需要更长时间,敬请理解。