Skip to main content

Upgrade to Babel 8

This document lists the breaking changes introduced in Babel 8.0.0, and how to adapt your usage of Babel to them when upgrading from Babel 7.0.0.

If you are working directly with Babel's API (because, for example, you maintain a custom Babel plugin), please also check the migration guide for integration.

If you are upgrading from Babel 6, please check the Babel 7 migration guide first.

All of Babel

Node.js support

All Babel 8 packages require Node.js ^20.19.0 || >=22.12.0.

We highly encourage you to use a newer version of Node.js (LTS v22) since the previous versions are not maintained. See nodejs/Release for more information.

This just means Babel itself won't run on older versions of Node. It can still output code that runs on old Node versions.

ESM only

Babel is now shipped as native ECMAScript modules (#11701, #17265).

Peer dependency requirements

  • All presets and plugins require @babel/core@^8.0.0 as peer dependency. Some Babel 7 plugins and presets might work with @babel/core@8, and some Babel 8 plugins and presets might work with @babel/core@7, but we do not provide any official support for that.
  • @babel/eslint-parser and @babel/eslint-plugin require eslint@^8.9.0 as peer dependency. (#15563)

Renamed Packages

The following packages has been renamed from ...-proposal-... to ...-transform-..., as they have reached Stage 4 (#15614). The rename process has been landed in Babel 7.22 so you can start the migration prior to the upgrade.

Babel 7Babel 8
@babel/plugin-proposal-async-generator-functions@babel/plugin-transform-async-generator-functions
@babel/plugin-proposal-class-properties@babel/plugin-transform-class-properties
@babel/plugin-proposal-class-static-block@babel/plugin-transform-class-static-block
@babel/plugin-proposal-duplicate-named-capturing-groups-regex@babel/plugin-transform-duplicate-named-capturing-groups-regex
@babel/plugin-proposal-dynamic-import@babel/plugin-transform-dynamic-import
@babel/plugin-proposal-export-namespace-from@babel/plugin-transform-export-namespace-from
@babel/plugin-proposal-json-strings@babel/plugin-transform-json-strings
@babel/plugin-proposal-logical-assignment-operators@babel/plugin-transform-logical-assignment-operators
@babel/plugin-proposal-nullish-coalescing-operator@babel/plugin-transform-nullish-coalescing-operator
@babel/plugin-proposal-numeric-separator@babel/plugin-transform-numeric-separator
@babel/plugin-proposal-object-rest-spread@babel/plugin-transform-object-rest-spread
@babel/plugin-proposal-optional-catch-binding@babel/plugin-transform-optional-catch-binding
@babel/plugin-proposal-optional-chaining@babel/plugin-transform-optional-chaining
@babel/plugin-proposal-private-methods@babel/plugin-transform-private-methods
@babel/plugin-proposal-private-property-in-object@babel/plugin-transform-private-property-in-object
@babel/plugin-proposal-unicode-property-regex@babel/plugin-transform-unicode-property-regex

Discontinued Packages

@babel/runtime-corejs2

core-js@2 has not been maintained for years, and you should switch to core-js@3 instead.

Please upgrade to @babel/runtime-corejs3 (#11751). After you install the new runtime, please set the corejs version to the core-js version that you are using.

babel.config.json
{
"plugins": ["@babel/transform-runtime", {
- corejs: 2
+ corejs: "3.42.0"
}]
}

@babel/plugin-syntax-import-assertions

The proposal evolved into import attributes, which now Babel supports parsing by default. You can remove @babel/plugin-syntax-import-assertions from your config, and replace the following patterns in your codebase:

input.js
- import value from "module" assert { type: "json" };
+ import value from "module" with { type: "json" };

@babel/plugin-proposal-record-and-tuple

The Records and Tuples proposal has been withdrawn in TC39, which means that the syntax is not on track anymore to be included in the language.

You will need to migrate away from the Records and Tuples syntax, and you can directly use the @bloomberg/record-tuple-polyfill polyfill as if it was a standalone library.

input.mjs
+ import { Record, Tuple } from "@bloomberg/record-tuple-polyfill"

// syntaxType: "hash"

- #{ p: "value" }
+ Record({ p: "value" })

- #[0, 1, 2]
+ Tuple(0, 1, 2)

// syntaxType: "bar"
- {| p: "value" |}
+ Record({ p: "value" })

- [|0, 1, 2|]
+ Tuple(0, 1, 2)

If you have to do a large scale migration. You can run Babel 7 with only the @babel/plugin-proposal-record-and-tuple plugin to transform the code base:

babel.record-tuple-migration.config.json
{
"babelrc": false,
"configFile": false,
"generateOpts": {
"experimental_preserveFormat": true,
"retainLines": true,
"tokens": true
},
"parserOpts": {
"createParenthesizedExpressions": true
},
"plugins": [
"@babel/plugin-syntax-jsx",
"@babel/plugin-syntax-typescript",

// or syntaxType: "bar" if you are using `{||}` or `[||]`
["@babel/plugin-proposal-record-and-tuple", {
"syntaxType": "hash", "importPolyfill": true
}]
]
}

And then run @babel/cli@7 to transform the input source src, this command will output the transformed source to src-mod:

npx @babel/cli@7 --config-file ./babel.record-tuple-migration.config.json src --out-lib src-mod

Please manually check whether src-mod is correct. If everything looks good, overwrite src with contents in src-mod.

Syntax plugins

The following syntax plugins are no longer needed, you can safely remove them from your configuration and dependencies:

  • @babel/plugin-syntax-async-functions
  • @babel/plugin-syntax-async-generators
  • @babel/plugin-syntax-bigint
  • @babel/plugin-syntax-class-properties
  • @babel/plugin-syntax-class-static-block
  • @babel/plugin-syntax-dynamic-import
  • @babel/plugin-syntax-exponentiation-operator
  • @babel/plugin-syntax-export-extensions
  • @babel/plugin-syntax-export-namespace-from
  • @babel/plugin-syntax-import-meta
  • @babel/plugin-syntax-json-strings
  • @babel/plugin-syntax-logical-assignment-operators
  • @babel/plugin-syntax-module-string-names
  • @babel/plugin-syntax-nullish-coalescing-operator
  • @babel/plugin-syntax-numeric-separator
  • @babel/plugin-syntax-object-rest-spread
  • @babel/plugin-syntax-optional-catch-binding
  • @babel/plugin-syntax-optional-chaining
  • @babel/plugin-syntax-private-property-in-object
  • @babel/plugin-syntax-top-level-await
  • @babel/plugin-syntax-trailing-function-commas
  • @babel/plugin-syntax-unicode-sets-regex

Per-package breaking changes

@babel/core

high

medium

  • The root AMD/UMD/SystemJS options, namely moduleIds, getModuleId, moduleRoot, moduleId and filenameRelative are moved to plugin options (#5473, #12724).

    Migration: Move these options to the plugin you are using to transform modules. For example, if you are using @babel/plugin-transform-modules-systemjs:

    babel.config.js
    module.exports = {
    plugins: [
    ['@babel/plugin-transform-modules-systemjs', {
    moduleIds: true,
    moduleRoot: 'myApp',
    getModuleId (name) {
    return name + "suffix";
    },
    }],
    ],
    };

    Adapt the example above if you are using @babel/plugin-transform-modules-amd or @babel/plugin-transform-modules-umd. You can start the migration prior to Babel 8.0.

    If you are using @babel/cli and passing --module-ids, --module-root and --module-id from command line, please create a Babel config babel.config.js and specify options there.

    If you are transforming ESM through @babel/preset-env, you will need to explicitly use one of the @babel/plugin-transform-modules-... plugins.

  • targets.esmodules: true option now behaves as same as targets.esmodules: "intersect" (#17188)

    Migration: In Babel 7, specifying esmodules: true will override the browsers target or browserslist's targets, while specifying "intersect" will intersect with such targets.

    If your app targets modern browsers released after 2019, you can safely remove the esmodules option as they all support ES modules.

    If your app targets legacy browsers such as IE, you can also remove the esmodules option as IE requires more transforms than any other browser.

    If your app targets browsers released before 2019 and you want to preserve the Babel 7 esmodules: true behavior, remove the esmodules option and set the following browsers target:

    babel.config.json
    {
    "targets": {
    "browsers": "chrome 61, firefox 60, safari 10.1, node 13.2"
    }
    }
  • Remove uglify target (#12594)

    Migration: The uglify target had been deprecated since 7.0.0. If you are using it to force @babel/preset-env to transpile down to ES5 and you still need to do so, you can use its forceAllTransforms option.

@babel/parser

medium

  • Remove the estree parser plugin's classFeatures option (#13752)

    Migration: Remove the option from your config, since it's now enabled by default. Previously the classFeatures plugin enables @babel/parser to produce class properties AST compatible with ESLint 8, following the ESTree specification. In Babel 8 the eslint-parser only works with ESLint 8 and above.

  • Remove the decimal parser plugin (#16741)

    Migration: Migrate your project to the latest proposal and remove the plugin from your config since the proposal doesn't include syntax anymore.

    example.js
    - 1.03m
    + new Decimal("1.03")
    - decimal1 + decimal2
    + decimal1.add(decimal2)
  • Remove the importAssertions parser plugin (#16770)

    This plugin was for an old version of the import attributes proposal, using the assert keyword instead of with. The proposal moved ahead without the assert keyword.

    Migration: If possible, replace assert with with in your code. If doing so is not possible, you can temporarily use the deprecatedImportAssert parser plugin.

  • Remove the importReflection parser plugin (#16808)

    The "import reflection" proposal does not exist anymore, and it was superseeded by the "source phase imports" proposal, which uses the source modifier for imports instead of module.

    Migration: Replace the plugin with sourcePhaseImports, and migrate your code to use source instead of module in import declarations.

    example.js
    - import module x from "foo";
    + import source x from "foo";

low

  • Disallow sequence expressions inside JSX attributes (#12447)

    Migration: Find and replace the following code patterns. You can start the migration prior to Babel 8:

    input.jsx
    - <p key={foo, bar}></p> // Invalid
    + <p key={(foo, bar)}></p> // Valid
  • Disallow {, }, < and > in JSX text (#12451)

    Migration: Use {'{'}, {'}'}, {'<'} and {'>'} instead. Find and replace the following code patterns. You can start the migration prior to Babel 8:

    input.jsx
    - <p>">" is greater than.</p>
    + <p>"{'>'}" is greater than.</p>

    Notes: This is technically a spec compliance fix because the JSX specification already forbids them. However, we have chosen to postpone it until Babel 8 because it could break someone's code.

@babel/generator

medium

  • Remove the jsonCompatibleStrings option (#9943, #12477)

    Migration: @babel/generator allows to specify options for jsesc, a library used to escape printed values. If you are using the jsonCompatibleStrings option, you can replace it with jsescOption: { json: true }.

low

  • Output non-ASCII characters as-is in string literals (#12675)

    If you are using any one of @babel/cli, webpack, Rollup, or other Node.js powered bundlers, the transformed code is always encoded with utf-8 and your app will not be affected. The issue only affects if you are manually calling the babel.transform API and your web server is not serving js files in the utf8 encoding.

    Migration: Ensure your server is always serving js files in the utf8 encoding. If you can not control the server output, specify the charset attribute of the script tag in the html files.

    <script charset="utf-8" src="your-app.js"></script>

    You can also restore to the Babel 7 behaviour by setting

    babel.config.js
    {
    generatorOpts: {
    jsescOption: {
    minimal: false
    }
    }
    }

@babel/preset-env

note

@babel/preset-env's targets option inherits its behavior from the top-level targets option, so make sure to check the @babel/core section.

high

medium

  • includes and excludes respect renamed package names (#15576)

    Migration: If includes or excludes contain any plugins mentioned in the Packages Renames section, change it to the new name. For example,

    babel.config.json
    {
    "presets": [[
    "@babel/preset-env",
    {
    - "includes": ["proposal-optional-chaining"]
    + "includes": ["transform-optional-chaining"]
    }
    ]]
    }

low

  • Enable the bugfixes option by default (#13866)

    Migration: You will probably be fine with the new behaviour as Babel now tries to compile the broken syntax to the closest non-broken modern syntax supported by your target browsers. If anyhow you want to restore the Babel 7 behaviour, you can specify bugfixes: false.

  • Removed syntax plugins can not be used in includes and excludes (#15810)

    Migration: You can safely remove them if you are using any of syntax plugins listed above in the includes and excludes options.

@babel/preset-react and @babel/plugin-transform-react-jsx

note

@babel/parser also includes some JSX-related breaking changes, so make sure to check the @babel/parser section.

high

  • Use the new JSX implementation by default (#12630)

    Migration: If you are using a modern version of React or Preact, it should work without any configuration changes. Otherwise, you can pass the runtime: "classic" option to @babel/preset-react or @babel/plugin-transform-react-jsx to be explicit about your usage of createElement (or the equivalent function in other libraries).

  • Make the development option default to the configured environment (#16927)

    Note that Babel's environment, set through envName option, defaults to process.env.BABEL_ENV || process.env.NODE_ENV || "development": if you don't specify neither the envName option nor the BABEL_ENV or NODE_ENV environment variables, it will default to development.

    Migration: In production builds, set the BABEL_ENV or NODE_ENV environment variables, or the envName Babel option, to "production". If you want to run only this preset in production mode, then you can explicitly set the development option to false.

medium

  • Remove useSpread and useBuiltIns options (#12593)

    Migration: Babel 8 always compiles JSX spread elements to object spread:

    input.jsx
    <div {...props}></div>
    // transforms to
    jsx("div", { ...props })

    If your app targets to modern browsers released after 2019, you can safely remove these options as object spread has less code footprint.

    If your code needs to run in an environment which doesn't support object spread, you can either use @babel/preset-env (recommended) or @babel/plugin-transform-object-rest-spread. If you want to transpile Object.assign down, you also need to enable @babel/plugin-transform-object-assign.

    In Babel 7.7.0, you can opt into the Babel 8 behavior by using the useSpread: true option.

  • Type check input options (#12460)

    Migration: The preset will also report invalid option names. Refer to the docs and ensure valid usage.

low

  • Disallow filter option in automatic runtime (#15068)

    Migration: The filter option can only be used with the classic runtime. If you have switched to automatic runtime, you can safely remove this option. Otherwise please specify runtime: "classic".

@babel/preset-typescript

note

Make sure to also check the @babel/plugin-transform-typescript changes changes, since it's what this preset uses under the hood.

high

  • Remove isTSX and allExtensions options (#14955)

    Migration:

    • isTSX: true and allExtensions: true

      If you are already using @babel/preset-react, @babel/plugin-transform-react-jsx or any other third-party jsx presets such as @vue/babel-preset-jsx, and you want to transpile .tsx files, you can safely remove these two options. Babel 8 will automatically handle .tsx files using this preset and the other JSX plugin.

      babel.config.json
      {
      "presets": [
      ["@babel/preset-react", { "runtime": "automatic" }],
      - ["@babel/preset-typescript", { "allExtensions": true, "isTSX": true }]
      + ["@babel/preset-typescript"]
      ]
      }

      If you want to transpile files other than .tsx, such as .vue, use ignoreExtensions: true:

      babel.config.js
      {
      overrides: [{
      include: /\.vue$/,
      presets: [
      ['@babel/preset-typescript', {
      - allExtensions: true, isTSX: true
      + ignoreExtensions: true
      }]
      ]
      }]
      }

      If you want to preserve the JSX format but transpile the TypeScript part, use ignoreExtensions: true and enable the @babel/plugin-syntax-jsx plugin.

    • isTSX: false and allExtensions: true

      Use ignoreExtensions: true, see the example above.

    • isTSX: false and allExtensions: false

      You can safely remove them.

medium

  • Remove allowDeclareFields option (#12461)

    Migration: Remove the option from your config, since it's now enabled by default.

  • Type check input options (#12460)

    Migration: The preset will also report invalid option names. Refer to the docs and ensure valid usage. For example, runtime is not a valid preset-typescript option and thus should be removed.

@babel/plugin-transform-typescript

high

  • Preserve uninitialized class fields (#12461)

    Migration: If you don't want fields to be initialized to undefined use the declare syntax, introduced in TypeScript 3.7:

    input.ts
    class A {
    foo: string | void; // initialized to undefined
    declare bar: number; // type-only, will be removed
    }

medium

  • Remove allowDeclareFields option (#12461)

    Migration: Remove the option from your config, since it's now enabled by default.

@babel/plugin-syntax-typescript

high

  • Remove isTSX option (#14955)

    Migration: If you are using isTSX: true, remove this option and enable the @babel/plugin-syntax-jsx plugin:

    {
    "plugins": [
    - ["@babel/plugin-syntax-typescript", { "isTSX": true }]
    + "@babel/plugin-syntax-typescript",
    + "@babel/plugin-syntax-jsx"
    ]
    }

    If you are using isTSX: false, you can safely remove it.

@babel/preset-flow

note

Make sure to also check the @babel/plugin-transform-flow-strip-types changes, since it's what this preset uses under the hood.

medium

  • Type check input options (#12460)

    Migration: The preset will also report invalid option names. Refer to the docs and ensure valid usage.

  • Remove the allowDeclareFields and enums options (#12457, #16792)

    Migration: Remove these options from your config, since they are now enabled by default.

@babel/plugin-transform-flow-strip-types

high

  • Preserve uninitialized class fields (#12457)

    Migration: Use the new declare syntax, introduced in Flow 0.120, if you don't want fields to be initialized to undefined:

    input.js
    class A {
    foo: string | void; // initialized to undefined
    declare bar: number; // type-only, will be removed
    }

medium

  • Remove the allowDeclareFields and enums options (#12457, #16792)

    Migration: Remove these options from your config, since they are now enabled by default.

@babel/plugin-transform-runtime

high

low

  • The useESModules option has been removed (#16141)

    Migration: Delete it from your configuration. @babel/runtime will now automatically expose ES modules when needed, using package.json#exports.

  • The runtime and helpers options have been removed (#16311)

    Migration: Delete them from your configuration: @babel/runtime will now always import helpers. If you don't want to inject imports to helpers, remove @babel/plugin-transform-runtime from your config.

@babel/plugin-transform-modules-systemjs

medium

  • Require @babel/plugin-transform-dynamic-import when transforming import() to SystemJS (#12700)

    Migration: Add @babel/plugin-transform-dynamic-import to your config: you can already do it in Babel 7. If you are using @babel/preset-env, you don't need to do anything.

    babel.config.js.diff
    {
    "plugins": [
    + "@babel/plugin-transform-dynamic-import",
    "@babel/plugin-transform-modules-systemjs",
    ]
    }

    Notes: All the other plugins which support dynamic import (transform-modules-commonjs and transform-modules-amd) require the separate plugin since it was introduced. We couldn't change it for transform-modules-systemjs because that package did already support dynamic import.

@babel/plugin-transform-regenerator

low

  • Do not replace global regeneratorRuntime references (#17237)

    Babel 7 used to replace references to a regeneratorRuntime global with Babel's regeneratorRuntime helper:

    input.js
    console.log(regeneratorRuntime.isGeneratorFunction(fn));
    output.js
    import _regeneratorRuntime from "@babel/runtime/regenerator";
    console.log(_regeneratorRuntime.isGeneratorFunction(fn));

    This doesn't happen anymore in Babel 8.

    Migration: The recommended approach is to update your code to not rely on a non-existent regeneratorRuntime global. If that's not possible, you can either import the unmaintained regenerator-runtime package in your application entrypoint, which will define the regeneratorRuntime global, or use babel-plugin-polyfill-regenerator to automatically inject that import for you.

@babel/plugin-proposal-decorators

medium

  • Only support the legacy and 2023-11 versions of the proposal. In addition to that, the plugin now requires a version option (#12712, #15676)

    Migration: You should migrate to the latest version of the proposal, "2023-11".

    babel.config.json
    {
    "plugins": [
    ["@babel/plugin-proposal-decorators", {
    - "decoratorsBeforeExport": true,
    - "version": "2018-09",
    + "version": "2023-11"
    }]
    ]
    }

    The syntax is the same, but you will need to rewrite your decorator functions. The spec repo provides comparison between the latest version and the 2018-09 version. You can already migrate since Babel 7.22.0, using the "version": "2023-11" option of @babel/plugin-proposal-decorators.

    Although Babel 8 still supports the legacy version, it is advisable to migrate to the 2023-11 version regardless: both Babel 8 and TypeScript 5.0 support the 2023-11 version, while there are a few behaviour differences in the legacy version between Babel and tsc's implementation. Browsers will only implement the latest version of the proposal.

@babel/eslint-parser

medium

  • Remove allowImportExportEverywhere option (#13921)

    Migration: Use babelOptions.parserOpts.allowImportExportEverywhere instead.

    .eslintrc
    {
    "parser": "@babel/eslint-parser",
    "parserOptions": {
    - "allowImportExportEverywhere": true,
    + "babelOptions": {
    + "parserOpts": {
    + "allowImportExportEverywhere": true
    + }
    + }
    }
    }

low

  • parserOpts.allowSuperOutsideMethod defaults to false (#13921)

    Migration: If you want to restore to Babel 7 behaviour, set babelOptions.parserOpts.allowSuperOutsideMethod to true.

  • allowReturnOutsideFunction is inferred from ecmaFeatures.globalReturn (#13921)

    Migration: If you want to enable allowReturnOutsideFunction, set ecmaFeatures.globalReturn to true.

    .eslintrc
    {
    "parser": "@babel/eslint-parser",
    "parserOptions": {
    "ecmaFeatures": {
    "globalReturn": true
    }
    }
    }

@babel/node

low

  • The -gc and -d command-line flags have been removed (#15956) Migration: Use the --expose-gc and --inspect Node.js flags respectively. Note that although -d was short for --debug, the latter has been deprecated since Node.js 7.7.0.

  • Command-line flags for Node.js and Babel must now be passed before the filename, while flags for the script itself must be passed after. (#16706):

    babel-node --flag-for-node --flag-for-babel script.js --flag-for-script