跳到主要内容

7.7.0 Released: Error recovery and TypeScript 3.7

· 阅读需 9 分钟

Today we are releasing Babel 7.7.0!

This release includes new parser features like top-level await (await x(), Stage 3) and Flow enum declarations (Flow proposal). And now, @babel/parser has the option of recovering from certain syntax errors!

We've also added support for TypeScript 3.7: Babel can parse and transform private class fields with type annotations, public class fields annotations defined using the declare keyword, type assertion function signatures and template literals in enum declarations.

Babel now understands three new configuration files: babel.config.json, babel.config.cjs and .babelrc.cjs, which behave the same as babel.config.js and .babelrc.js files.

Lastly, Babel 7.7.0 uses 20% less memory than 7.6.0.

You can read the whole changelog on GitHub.


Shoutout to Alejandro Sánchez, Chris Garrett, 彭驰, Daniel Arthur Gallagher, ExE-Boss, Eugene Myunster, Georgii Dolzhykov, Gerald, Linus Unnebäck, Martin Forsgren, Matthew Whitworth, Micah Zoltu, Mohammad Ahmadi and Samuel Kwok for their first PRs!

This release has also been made possible thanks to collaboration with teams of other open source projects: thanks to Devon Govett (Parcel) for implementing support for babel.config.json files, and to George Zahariev (Flow) for adding Flow enum declarations to @babel/parser!

Another special thanks goes to Bloomberg for organizing an Open Source Hackaton to encourage their engineers to give back to the community! In particular, Robin Ricard and Jaideep Bhoosreddy who are actively working on automating the testing of Babel transforms against the Test262 suite.

If you or your company want to support Babel and the evolution of JavaScript, but aren't sure how, you can donate to us on OpenCollective and, better yet, work with us on the implementation of new ECMAScript proposals directly! As a volunteer-driven project, we rely on the community's support to both fund our efforts in supporting the wide range of JavaScript users and taking ownership of the code. Reach out to Henry at henry@babeljs.io if you'd like to talk more!

Top-level await parsing (#10449)

The top-level await proposal allows you to await promises in modules as if they were wrapped in a big async function. This is useful, for example, to conditionally load a dependency or to perform app initialization:

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

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

@babel/parser has supported using await outside of async functions via the allowAwaitOutsideFunction option since version 7.0.0.

Version 7.7.0 introduces a new topLevelAwait parser plugin, which has a few key differences:

  • It only allows top-level await inside modules and not inside scripts, as the proposal mandates. This is needed because synchronous script-based module systems (like CommonJS) cannot support an async dependency.
  • It allows to detect the correct sourceType when sourceType: "unambiguous" is used. Note that, since await is a valid identifier in scripts, many constructs which may seem unambiguously modules are actually ambiguous and Babel will parse them as scripts. For example, await -1 could either be an await expression which waits for -1, or a difference between await and 1.

If you are using @babel/parser directly, you can enable the topLevelAwait plugin:

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

We also created the @babel/plugin-syntax-top-level-await package, which you can add to your Babel configuration:


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

Please note that usage of top-level await assumes support within your module bundler. Babel itself isn't doing transformations: if you are using Rollup you can enable the experimentalTopLevelAwait option, and webpack 5 supports the experiments.topLevelAwait option.

Starting from this release, @babel/preset-env will automatically enable @babel/plugin-syntax-top-level-await if the caller supports it. Note: babel-loader and rollup-plugin-babel don't yet tell Babel that they support this syntax, but we are working on it with the respective maintainers.

Parser error recovery (#10363)

Like many other JavaScript parsers, @babel/parser throws an error whenever some invalid syntax is encountered. This behavior works well for Babel, since in order to transform a JavaScript program to another program we must first be sure that the input is valid.

Given Babel's popularity, there are many other tools relying on @babel/parser: above all babel-eslint and Prettier. For both these tools, a parser which bails out on the first error is suboptimal.

Consider this code, which is invalid because of the duplicated __proto__ property:

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

let a = 2;

The current workflow with ESLint and Prettier is the following:

  1. Prettier can't format the file
  2. ESLint reports an Redefinition of __proto__ property parser error
  3. You remove the second __proto__ property
  4. Prettier can't format the file
  5. ESLint reports an Identifier 'a' has already been declared error
  6. You remove the second let keyword
  7. Prettier formats the file

Wouldn't it better if it was more like this?

  1. Prettier formats the file
  2. ESLint reports two errors: Redefinition of __proto__ property and Identifier 'a' has already been declared
  3. You remove the second __proto__ property and the second let keyword

In this release, we are adding a new option to @babel/parser: errorRecovery. When it is set to true, the resulting AST will have an errors property containing all the errors which @babel/parser was able to recover from:

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 can still throw as not every error is currently recoverable. We'll continue to improve these cases!

New configuration file extensions (#10501, #10599)

Babel 6 only supported a single configuration file: .babelrc, whose contents must be specified using JSON.

Babel 7 changed the meaning of .babelrcs and introduced two new configuration files: babel.config.js and .babelrc.js (you can read about the difference between them in the docs). We added configuration files with JavaScript to allow defining your own logic when enabling/disabling plugins/options.

However a big benefit of JSON files is easier cacheability. The same JavaScript file can produce different values when called two times, while a JSON file is guaranteed to always evaluate to the same object. Also, JSON configurations are easily serializable, while it's not possible to serialize JavaScript values like functions or JavaScript objects with implicit data or relationships.

Note that Babel also caches transforms when using JavaScript-based configurations, but the config file must be evaluated (in order to know if the cache is still valid) and the cache manually configured.

For these reasons, Babel 7.7.0 introduces support for a new configuration file: babel.config.json, whose behavior is the same as babel.config.js.

We also added support for two different configuration files: babel.config.cjs and .babelrc.cjs, which must be used when using node's "type": "module" option in package.json (because Babel doesn't support ECMAScript modules in config files). Apart from this "type": "module" difference, they behave exactly like babel.config.js and .babelrc.js.

TypeScript 3.7 (#10543, #10545)

TypeScript 3.7 RC includes support for optional chaining, nullish coalescing operator, assertion functions, type-only field declarations and many more type-related features.

Optional chaining (a?.b) and nullish coalescing (a ?? b) have been supported in Babel since 7.0.0 via @babel/plugin-proposal-optional-chaining and @babel/plugin-proposal-nullish-coalescing-operator.

In Babel 7.7.0, you can now use assertion functions and declare in class fields:

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;
}

To avoid breaking changes, we introduced support for declare in class fields behind a flag: "allowDeclareFields", supported by both @babel/plugin-transform-typescript and @babel/preset-typescript. This will likely become default behavior, so it is recommended that you migrate your config to use it:

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

Use object spread in compiled JSX (#10572)

When using spread properties in JSX elements, Babel injects a runtime helper by default:

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));

In 2016, as support for native ES6 improved, we added the useBuiltIns option to @babel/plugin-transform-react-jsx which allowed the compiled output to directly use Object.assign and removed excess code:

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

// 🡇 🡇 🡇

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

However given the native support for object spread, it allows us to produce even more optimized code:

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

// 🡇 🡇 🡇

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

You can enable it using the useSpread option with either @babel/preset-react or @babel/plugin-transform-react-jsx:

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

Memory usage improvements (#10480)

Since the beginning, we have been making efforts (#433, #3475, #7028, etc.) to improve performance. Babel 7.7.0 now uses 20% less memory, and transforms large files 8% faster when compared to 7.6.0.

In order to achieve these results, we optimized different operations done during the lifetime NodePath objects (used to wrap every AST node):

  1. We now avoid initializing some rarely used object properties until they are needed, allowing us to avoid an Object.create(null) allocation for almost every AST node.

  2. We reduced bookkeeping workload for every single node visit, by replacing a few uncommon properties with getters so that @babel/traverse can skip updating them.

  3. We optimized memory usage by compressing several boolean properties used to represent the status of a node traversal (i.e. skipped, stopped or removed) into a bit array.

All these improvements add up to the following difference in transform performance and memory usage:

PerformanceMemory usage

You can also checkout the raw data of the charts above. If you want to read more about this topic, you can read Jùnliàng's detailed write-up about the changes he made to get those improvements!