跳至主内容

7.7.0 版本发布:错误恢复与 TypeScript 3.7 支持

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

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

今天我们正式发布 Babel 7.7.0!

本次更新包含多项解析器新特性,例如顶层 await(await x()Stage 3)和 Flow enum 声明(Flow 提案)。更重要的是,@babel/parser 现在已支持从特定语法错误中恢复解析!

我们还新增了对 TypeScript 3.7 的支持:Babel 现在能够解析并转换带类型注解的私有类字段、使用 declare 关键字定义的公共类字段注解、类型断言函数签名,以及在 enum 声明中使用模板字面量。

Babel 现已支持三种新配置文件:babel.config.jsonbabel.config.cjs.babelrc.cjs,其功能与 babel.config.js.babelrc.js 文件完全一致。

最后,Babel 7.7.0 的内存占用比 7.6.0 版本减少了 20%。

您可以在 GitHub 查看完整更新日志。


特别鸣谢首次贡献 PR 的开发者:Alejandro SánchezChris Garrett彭驰Daniel Arthur GallagherExE-BossEugene MyunsterGeorgii DolzhykovGeraldLinus UnnebäckMartin ForsgrenMatthew WhitworthMicah ZoltuMohammad Ahmadi 以及 Samuel Kwok

本次发布也得益于与其他开源项目的协作:感谢 Devon GovettParcel)实现了对 babel.config.json 文件的支持,以及 George ZaharievFlow)为 @babel/parser 添加了 Flow enum 声明功能!

特别致谢 Bloomberg 组织开源黑客马拉松活动,鼓励工程师回馈社区!尤其感谢 Robin RicardJaideep Bhoosreddy,他们正积极推动基于 Test262 测试套件 实现 Babel 转换的自动化测试。

如果您或您的公司希望支持 Babel 和 JavaScript 的演进但不确定具体方式,可以通过 OpenCollective 进行捐赠。更棒的是,直接参与 新 ECMAScript 提案 的实现工作!作为志愿者驱动的项目,我们依赖社区支持来资助维护广大 JavaScript 用户生态的工作,并对代码实现负责。如有意向合作,请通过 henry@babeljs.io 联系 Henry!

顶层 await 解析 (#10449)

顶层 await 提案 允许你在模块中像被包裹在大型异步函数中那样 await Promise。这在条件加载依赖项或执行应用初始化等场景中非常实用:

JavaScript
// Dynamic dependency path
const strings = await import(`./i18n/${navigator.language}.mjs`);

// Resource initialization
const connection = await dbConnector();

自 7.0.0 版本起,@babel/parser 就已通过 allowAwaitOutsideFunction 选项支持在异步函数之外使用 await

7.7.0 版本引入了全新的 topLevelAwait 解析器插件,具有以下关键区别:

  • 根据提案要求,它仅允许在模块内部而非脚本内部使用顶层 await。这是必要的,因为基于脚本的同步模块系统(如 CommonJS)无法支持异步依赖。

  • 当使用 sourceType: "unambiguous" 时,它能正确检测 sourceType。请注意,由于 await 在脚本中是有效标识符,许多看似明确是模块的结构实际存在歧义,Babel 会将其解析为脚本。 例如,await -1 可能是等待 -1 的 await 表达式,也可能是 await1 的减法运算。

若直接使用 @babel/parser,可启用 topLevelAwait 插件:

JavaScript
parser.parse(inputCode, {
plugins: ["topLevelAwait"]
});

我们还创建了 @babel/plugin-syntax-top-level-await 包,可将其加入 Babel 配置:


module.exports = {
plugins: [
"@babel/plugin-syntax-top-level-await"
]
}

请注意,顶层 await 的使用需要模块打包器的支持。Babel 本身不进行转换:使用 Rollup 时可启用 experimentalTopLevelAwait 选项,Webpack 5 则支持 experiments.topLevelAwait 选项。

自本版本起,若 caller 支持,@babel/preset-env 将自动启用 @babel/plugin-syntax-top-level-await注意babel-loaderrollup-plugin-babel 暂未告知 Babel 其支持此语法,我们正与相应维护者协作解决。

解析器错误恢复 (#10363)

与多数 JavaScript 解析器类似,@babel/parser 遇到无效语法时会抛出错误。这对 Babel 很适用——因为要将 JavaScript 程序转换为另一程序,必须先确保输入有效。

鉴于 Babel 的流行,许多工具依赖 @babel/parser:尤其是 babel-eslint 和 Prettier。对这些工具而言,首个错误即中止的解析器并非最优方案。

请看这段因重复 __proto__ 属性而无效的代码:

JavaScript
let a = {
__proto__: x,
__proto__: y
}

let a = 2;

当前 ESLint 和 Prettier 的工作流程如下:

  1. Prettier 仍无法格式化文件

  2. ESLint 报告 Redefinition of __proto__ property 解析错误

  3. 你移除第二个 __proto__ 属性

  4. Prettier 仍无法格式化文件

  5. ESLint 报告 Identifier 'a' has already been declared 错误

  6. 你移除第二个 let 关键字

  7. Prettier 完成文件格式化

这样的流程是否更优?

  1. Prettier 完成文件格式化

  2. ESLint 报告两个错误:Redefinition of __proto__ propertyIdentifier 'a' has already been declared

  3. 移除第二个 __proto__ 属性和第二个 let 关键字

在此版本中,我们为 @babel/parser 新增了一个选项:errorRecovery。当设置为 true 时,生成的 AST 将包含 errors 属性,其中记录了所有 @babel/parser 能够恢复的错误:

JavaScript
const input = `
let a = {
__proto__: x,
__proto__: y
}

let a = 2;
`;

parser.parse(input); // Throws "Redefinition of __proto__ property"

const ast = parser.parse(input, { errorRecovery: true });
ast.errors == [
SyntaxError: "Redefinition of __proto__ property",
SyntaxError: "Identifier 'a' has already been declared",
];

@babel/parser 仍可能抛出异常,因为并非所有错误目前都可恢复。我们将持续改进这些情况!

新的配置文件扩展名 (#10501, #10599)

Babel 6 仅支持单一的配置文件:.babelrc,其内容必须使用 JSON 格式指定。

Babel 7 重新定义了 .babelrc 的含义,并引入了两个新配置文件:babel.config.js.babelrc.js(关于它们的区别请参阅文档)。我们添加 JavaScript 配置文件是为了允许在启用/禁用插件/选项时定义自定义逻辑。

但 JSON 文件的一大优势是更易_缓存_。同一个 JavaScript 文件在多次调用时可能产生不同值,而 JSON 文件始终保证解析为相同对象。此外,JSON 配置易于序列化,而函数或具有隐式数据/关系的 JavaScript 对象等值无法序列化。

请注意,Babel 在使用基于 JavaScript 的配置时也会缓存转换结果,但配置文件必须被求值(以确定缓存是否有效),且缓存需要手动配置

因此,Babel 7.7.0 引入了对新配置文件 babel.config.json 的支持,其行为与 babel.config.js 完全相同。

我们还添加了对两种不同配置文件的支持:babel.config.cjs.babelrc.cjs,当在 package.json 中使用 node 的 "type": "module" 选项时必须使用它们(因为 Babel 不支持配置文件中的 ECMAScript 模块)。除了 "type": "module" 差异外,它们的行为与 babel.config.js.babelrc.js 完全相同。

TypeScript 3.7 (#10543, #10545)

TypeScript 3.7 RC 包含对可选链、空值合并运算符、断言函数、仅类型字段声明等类型相关功能的支持。

自 7.0.0 版本起,Babel 已通过 @babel/plugin-proposal-optional-chaining@babel/plugin-proposal-nullish-coalescing-operator 支持可选链(a?.b)和空值合并(a ?? b)。

在 Babel 7.7.0 中,你现在可以在类字段中使用断言函数和 declare

function assertString(x): assert x is string {
if (typeof x !== "string") throw new Error("It must be a string!");
}

class Developer extends Person {
declare usingBabel: boolean;
}

为避免破坏性变更,我们通过标志 "allowDeclareFields" 引入了对类字段中 declare 的支持,该标志由 @babel/plugin-transform-typescript@babel/preset-typescript 共同支持。这很可能成为默认行为,因此建议迁移配置以使用它:

{
"presets": [
["@babel/preset-typescript", {
"allowDeclareFields": true
}]
]
}

在编译后的 JSX 中使用对象展开 (#10572)

在 JSX 元素中使用展开属性时,Babel 默认会注入运行时辅助函数:

JSX
<a x {...y} />

// 🡇 🡇 🡇

function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

React.createElement("a", _extends({
x: true
}, y));

2016 年,随着对原生 ES6 支持改进,我们为 @babel/plugin-transform-react-jsx 添加了 useBuiltIns 选项,使编译输出能直接使用 Object.assign 并减少冗余代码:

JSX
<a x {...y} />

// 🡇 🡇 🡇

React.createElement("a", Object.assign({
x: true
}, y));

考虑到对象展开语法的原生支持,我们现在可以生成更加优化的代码:

JSX
<a x {...y} />

// 🡇 🡇 🡇

React.createElement("a", { x: true, ...y });

你可以通过 @babel/preset-react@babel/plugin-transform-react-jsxuseSpread 选项启用该功能:

{
presets: [
["@babel/react", { useSpread: true }]
]
}

内存使用优化 (#10480)

自项目启动以来,我们一直致力于性能优化(#433#3475#7028 等)。与 7.6.0 相比,Babel 7.7.0 现在内存占用减少 20%,大文件转换速度提升 8%。

为了实现这些优化,我们改进了 NodePath 对象(用于包装每个 AST 节点)生命周期中的各项操作:

  1. 延迟初始化部分低频使用的对象属性,避免了为几乎每个 AST 节点分配 Object.create(null) 的开销

  2. 通过将非常用属性替换为 getter 方法,减少了节点遍历时的簿记工作,使 @babel/traverse 能跳过相关更新

  3. 将表示节点遍历状态(跳过/停止/移除)的多个布尔属性压缩为位数组,优化内存使用

这些改进共同带来了以下编译性能和内存占用的显著变化:

PerformanceMemory usage

你可以查看上述图表的原始数据。若想深入了解该主题,请阅读 Jùnliàng 的详细说明,其中阐述了他实现这些优化的技术细节!