7.7.0 Released: Error recovery and TypeScript 3.7
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 [email protected] 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:
// 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
whensourceType: "unambiguous"
is used. Note that, sinceawait
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 betweenawait
and1
.
If you are using @babel/parser
directly, you can enable the topLevelAwait
plugin:
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:
let a = {
__proto__: x,
__proto__: y
}
let a = 2;
The current workflow with ESLint and Prettier is the following:
- Prettier can't format the file
- ESLint reports an
Redefinition of __proto__ property
parser error - You remove the second
__proto__
property - Prettier can't format the file
- ESLint reports an
Identifier 'a' has already been declared
error - You remove the second
let
keyword - Prettier formats the file
Wouldn't it better if it was more like this?
- Prettier formats the file
- ESLint reports two errors:
Redefinition of __proto__ property
andIdentifier 'a' has already been declared
- You remove the second
__proto__
property and the secondlet
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:
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 .babelrc
s 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:
<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:
<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:
<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):
-
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. -
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. -
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:
Performance | Memory 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!