8.1.0 Released: bytes and text imports, source map range mappings, and ESLint configs
Babel 8.1.0 is out!
This is the first minor release since Babel 8.0.0. It includes transform support for the Import Bytes and Import Text proposals and for filtered namespaces from the deferred re-exports proposal, and experimental support for generating source maps with range mappings. @babel/parser now supports multiple new TC39 proposals: deferred re-exports, import.sync, and non-shared structs.
We also added recommended and all configs to @babel/eslint-plugin, for parity with ESLint's built-in presets.
You can read the whole changelog on GitHub.
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 our Open Collective 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 fund our efforts in supporting the wide range of JavaScript users: you can read more about our current situation in the Babel 8.0.0 release post. Reach out at team@babeljs.io if you'd like to discuss more!
Highlightsโ
Import bytes and text (#17817)โ
The Import Bytes (Stage 2.7) and Import Text (Stage 3) proposals build on top of import attributes, similarly to JSON modules. They allow importing any file either as raw bytes, or as a string:
import logo from "./logo.png" with { type: "bytes" };
import shader from "./shader.glsl" with { type: "text" };
logo; // An Uint8Array, backed by an immutable ArrayBuffer when supported
shader; // A string
Like @babel/plugin-transform-json-modules, the new @babel/plugin-proposal-bytes-modules and @babel/plugin-proposal-text-modules plugins transform these imports to the appropriate way of loading files in your target platform(s), according to your targets option. For example, when targeting browsers Babel compiles import logo from "./logo.png" with { type: "bytes" } to:
const logo = await fetch(import.meta.resolve("./logo.png"))
.then(r => r.bytes())
.then(_immutableUint8Array);
while when compiling to CommonJS for Node.js it will generate:
const logo = _immutableUint8Array(
require("fs").readFileSync(require.resolve("./logo.png"))
);
You can enable them by adding the plugins to your Babel config:
{
"targets": ["chrome 120", "firefox 120", "node 22"],
"plugins": [
"@babel/plugin-proposal-bytes-modules",
"@babel/plugin-proposal-text-modules"
]
}
Filtered namespaces (#18311, #18316)โ
The deferred re-exports Stage 2.7 proposal, in addition to export defer, introduces a new syntax to create namespace objects that only contain some of the exports of a module. Unlike with import * as ns, tools know exactly which exports can be accessed through the namespace object, so bundlers can safely remove the unused ones:
import { parse, format } as myLib from "my-library";
export { parse } as parser from "my-library";
// Dynamic imports accept the same filter, through the new `exports` option
const { format } = await import("my-library", { exports: ["format"] });
The new @babel/plugin-proposal-filtered-namespaces plugin compiles filtered namespaces to named imports, and creates a proxy-like object that behaves like a module namespace object. For example, import { parse, format } as myLib from "my-library" becomes:
const myLib = Object.freeze(Object.defineProperty({
__proto__: null,
get parse() {
return _parse;
},
get format() {
return _format;
}
}, Symbol.toStringTag, {
value: "Module"
}));
import { parse as _parse, format as _format } from "my-library";
The output still uses ES modules, so you can combine this plugin with any of Babel's modules transforms, such as @babel/plugin-transform-modules-commonjs. You can enable it by adding it to your Babel config:
{
"plugins": ["@babel/plugin-proposal-filtered-namespaces"]
}
Babel cannot transform filtered namespaces that are combined with import defer or export defer yet, such as import defer { parse } as myLib from "my-library".
Source map range mappings (#18006)โ
Source maps have recently started evolving again, under TC39's TG4 group. The range mappings Stage 3 proposal is one of the new additions, and aims at increasing source map quality while reducing their size.
Source maps encode a list of points: each mapping says that a given position in the generated code corresponds to a given position in the original code. Any character that is not mapped by a specific source map location is assumed to map to the same point that the previous mapping was pointing to: tools that generate JavaScript thus have to emit a lot of mappings, even for lightly modified code. This makes source maps large.
Range mappings allow marking some mappings as covering a whole range of code, allowing representing multiple point-to-point mapping as if they were a single one.
For example, given this input where a plugin renames z to a:
let x, y, z, w;
x + y + z + w;
Babel would usually generate the following mappings:
AAAA,IAAI,CAAC,EAAE,CAAC,EAAEA,CAAC,EAAE,CAAC;AACd,CAAC,GAAG,CAAC,GAAGA,CAAC,GAAK,CAAC
while with range mappings it generates:
AAAA,QAAQ,EAAEA,CAAC;KACN,GAAGA,CAAC,GAAK,CAAC
together with an additional "rangeMappings": "AD;D" field.
You can enable range mappings using the new sourceMapRanges generator option:
{
"sourceMaps": true,
"generatorOpts": {
"sourceMapRanges": true
}
}
Only enable this option if all the tools that run after Babel and consume its source maps, including your browser's devtools, support range mappings. The option is currently ignored when also using inputSourceMap.
Parsing support for lots of new proposalsโ
This Babel release also include parsing support for multiple new proposals. Note that it doesn't support transforming them yet: you can start playing around with them if you have other tools, such as bundlers, that can handle the new syntax.
Deferred re-exports (#18310)โ
The deferred re-exports Stage 2.7 proposal extends import defer to export ... from declarations. When a module re-exports bindings using export defer, the re-exported module is only loaded and evaluated if one of its bindings is actually imported:
export defer { parse } from "./parser.js";
export defer { format } from "./formatter.js";
// ./formatter.js will never be loaded nor executed
import { parse } from "my-library";
You can enable it using the deferredReexports parser plugin, together with deferredImportEvaluation.
import.sync (#17980)โ
The import.sync Stage 2 proposal allows loading and evaluating a module synchronously, when the runtime that the code runs in is able to do so:
const { readFile } = import.sync("node:fs");
You can enable it using the importSync parser plugin.
Structs (#17624)โ
The structs Stage 2 proposal introduces a new kind of class-like declaration, whose instances have a fixed layout: all their fields are defined when they are created, and they cannot be extended with new properties:
struct Point {
x = 0;
y = 0;
get length() {
return Math.hypot(this.x, this.y);
}
}
You can enable it using the structs parser plugin. Babel only supports non-shared structs, and not shared struct declarations.
@babel/eslint-plugin configs (#17956, #18009)โ
@babel/eslint-plugin provides versions of some ESLint core rules that are compatible with experimental syntax supported by Babel. It now exposes two configs, configs.recommended and configs.all, which are meant to be used with the corresponding configs from @eslint/js.
They replace some of ESLint's rules with the equivalent Babel ones so that they work with new syntax, and they enable some new Babel-specific rules that would likely be in those ESLint presets if the syntax that they lint was already part of standard JavaScript.
import js from "@eslint/js";
import babelParser from "@babel/eslint-parser";
import babelPlugin from "@babel/eslint-plugin";
export default [
js.configs.recommended,
{
plugins: { babel: babelPlugin },
languageOptions: { parser: babelParser },
...babelPlugin.configs.recommended,
},
];
Note that the configs do not register the plugin and the parser for you: you still need to list it them in plugins and languageOptions.parser.