diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 471a7fa5111d..03b2f74382d6 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -54,7 +54,7 @@ module.exports = {
"guard-for-in": "error",
"import/extensions": ["error", { json: "always", cjs: "always" }],
},
- globals: { PACKAGE_JSON: "readonly" },
+ globals: { PACKAGE_JSON: "readonly", USE_ESM: "readonly" },
},
{
files: [
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 37eba6a9ba73..382fce0b8c10 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -65,6 +65,30 @@ jobs:
- name: Upload coverage report
uses: codecov/codecov-action@v1
+ test-esm:
+ name: Test ESM version
+ needs: prepare-yarn-cache
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ - name: Use Node.js latest
+ uses: actions/setup-node@v3
+ with:
+ node-version: "*"
+ cache: "yarn"
+ - name: Use ESM and build
+ run: make use-esm
+ env:
+ USE_ESM: true
+ - name: Test
+ # Hack: --color has supports-color@5 returned true for GitHub CI
+ # Remove once `chalk` is bumped to 4.0.
+ run: yarn jest --ci --color
+ env:
+ BABEL_ENV: test
+ USE_ESM: true
+
build:
name: Build Babel Artifacts
needs: prepare-yarn-cache
diff --git a/.github/workflows/e2e-tests-esm.yml b/.github/workflows/e2e-tests-esm.yml
new file mode 100644
index 000000000000..ee50612fa2c8
--- /dev/null
+++ b/.github/workflows/e2e-tests-esm.yml
@@ -0,0 +1,82 @@
+name: E2E tests (esm)
+
+on:
+ push:
+ pull_request:
+
+permissions:
+ contents: read
+
+jobs:
+ e2e-publish:
+ name: Publish to local Verdaccio registry
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Use Node.js latest
+ uses: actions/setup-node@v3
+ with:
+ node-version: "*"
+ cache: "yarn"
+ - name: Use ESM
+ run: make use-esm
+ - name: Publish
+ run: ./scripts/integration-tests/publish-local.sh
+ env:
+ USE_ESM: true
+ - uses: actions/upload-artifact@v3
+ with:
+ name: verdaccio-workspace
+ path: /tmp/verdaccio-workspace
+
+ e2e-tests:
+ name: Test
+ needs: e2e-publish
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ project:
+ - create-react-app
+ steps:
+ - name: Get yarn1 cache directory path
+ id: yarn1-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn cache dir)"
+ - name: Checkout code
+ uses: actions/checkout@v3
+ - name: Use Node.js latest
+ uses: actions/setup-node@v3
+ with:
+ node-version: "*"
+ - name: Get yarn3 cache directory path
+ id: yarn3-cache-dir-path
+ run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
+ - name: Use yarn1 cache
+ uses: actions/cache@v3
+ id: yarn1-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
+ with:
+ path: ${{ steps.yarn1-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn1-e2e-breaking-${{ matrix.project }}-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: ${{ runner.os }}-yarn1-e2e-breaking-${{ matrix.project }}-
+ - name: Use yarn3 cache
+ uses: actions/cache@v3
+ id: yarn3-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
+ with:
+ path: ${{ steps.yarn3-cache-dir-path.outputs.dir }}
+ key: ${{ runner.os }}-yarn3-e2e-breaking-${{ matrix.project }}-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: ${{ runner.os }}-yarn3-e2e-breaking-${{ matrix.project }}-
+ - name: Clean babel cache
+ run: |
+ rm -rf ${{ steps.yarn1-cache-dir-path.outputs.dir }}/*babel*
+ rm -rf ${{ steps.yarn3-cache-dir-path.outputs.dir }}/*babel*
+ - uses: actions/download-artifact@v3
+ with:
+ name: verdaccio-workspace
+ path: /tmp/verdaccio-workspace
+ - name: Test
+ run: ./scripts/integration-tests/e2e-${{ matrix.project }}.sh
+ env:
+ USE_ESM: true
diff --git a/.gitignore b/.gitignore
index 11d0f00a0a8a..d28514220d56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,3 +89,5 @@ packages/babel-standalone/babel.min.js
/test/runtime-integration/*/output.js
/test/runtime-integration/*/absolute-output.js
+
+.module-type
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b341efc5dc5b..d64fe3b599e2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -80,6 +80,21 @@ If you wish to build a copy of Babel for distribution, then run:
$ make build-dist
```
+## Develop compiling to CommonJS or to ECMAScript modules
+
+Babel can currently be compiled both to CJS and to ESM. You can toggle between those two
+modes by running one of the following commands:
+```sh
+make use-esm
+```
+```sh
+make use-cjs
+```
+
+Note that they need to recompile the whole monorepo, so please make sure to stop any running `make watch` process before running them.
+
+If you never run a `make use-*` (or if you delete the `.module-type` file that they generate), our build process defaults to CJS.
+
### Running linting/tests
#### Lint
@@ -121,16 +136,17 @@ $ TEST_ONLY=babel-cli make test
More options
TEST_ONLY
will also match substrings of the package name:
- ```sh
- # Run tests for the @babel/plugin-transform-classes package.
- $ TEST_ONLY=babel-plugin-transform-classes make test
- ```
+```sh
+# Run tests for the @babel/plugin-transform-classes package.
+$ TEST_ONLY=babel-plugin-transform-classes make test
+```
- Or you can use Yarn:
+Or you can use Yarn:
+
+```sh
+$ yarn jest babel-cli
+```
- ```sh
- $ yarn jest babel-cli
- ```
@@ -145,16 +161,19 @@ $ TEST_GREP=transformation make test
Substitute spaces for hyphens and forward slashes when targeting specific test names:
For example, for the following path:
+
```sh
packages/babel-plugin-transform-arrow-functions/test/fixtures/arrow-functions/destructuring-parameters
```
You can use:
+
```sh
$ TEST_GREP="arrow functions destructuring parameters" make test
```
Or you can directly use Yarn:
+
```sh
$ yarn jest -t "arrow functions destructuring parameters"
```
diff --git a/Gulpfile.mjs b/Gulpfile.mjs
index 21aa3020848a..454967b9d829 100644
--- a/Gulpfile.mjs
+++ b/Gulpfile.mjs
@@ -25,6 +25,14 @@ import { resolve as importMetaResolve } from "import-meta-resolve";
import rollupBabelSource from "./scripts/rollup-plugin-babel-source.js";
import formatCode from "./scripts/utils/formatCode.js";
+let USE_ESM = false;
+try {
+ const type = fs
+ .readFileSync(new URL(".module-type", import.meta.url), "utf-8")
+ .trim();
+ USE_ESM = type === "module";
+} catch {}
+
const require = createRequire(import.meta.url);
const monorepoRoot = path.dirname(fileURLToPath(import.meta.url));
@@ -480,10 +488,15 @@ const libBundles = [
"packages/babel-plugin-bugfix-safari-id-destructuring-collision-in-function-expression",
].map(src => ({
src,
- format: "cjs",
+ format: USE_ESM ? "esm" : "cjs",
dest: "lib",
}));
+const cjsBundles = [
+ // This is used by Prettier, @babel/register and @babel/eslint-parser
+ { src: "packages/babel-parser" },
+];
+
const dtsBundles = ["packages/babel-types"];
const standaloneBundle = [
@@ -581,6 +594,45 @@ ${fs.readFileSync(path.join(path.dirname(input), "license"), "utf8")}*/
);
});
+gulp.task("build-cjs-bundles", () => {
+ if (!USE_ESM) {
+ fancyLog(
+ chalk.yellow(
+ "Skipping CJS-compat bundles for ESM-based builds, because not compiling to ESM"
+ )
+ );
+ return Promise.resolve();
+ }
+ return Promise.all(
+ cjsBundles.map(async ({ src, external = [] }) => {
+ const input = `./${src}/lib/index.js`;
+ const output = `./${src}/lib/index.cjs`;
+
+ const bundle = await rollup({
+ input,
+ external,
+ onwarn(warning, warn) {
+ if (warning.code === "CIRCULAR_DEPENDENCY") return;
+ warn(warning);
+ },
+ plugins: [
+ rollupCommonJs({ defaultIsModuleExports: true }),
+ rollupNodeResolve({
+ extensions: [".js", ".mjs", ".cjs", ".json"],
+ preferBuiltins: true,
+ }),
+ ],
+ });
+
+ await bundle.write({
+ file: output,
+ format: "cjs",
+ sourcemap: false,
+ });
+ })
+ );
+});
+
gulp.task(
"build",
gulp.series(
@@ -590,7 +642,7 @@ gulp.task(
// rebuild @babel/types and @babel/helpers since
// type-helpers and generated helpers may be changed
"build-babel",
- "generate-standalone"
+ gulp.parallel("generate-standalone", "build-cjs-bundles")
)
);
@@ -612,7 +664,8 @@ gulp.task(
gulp.series(
"generate-type-helpers",
// rebuild @babel/types since type-helpers may be changed
- "build-no-bundle"
+ "build-no-bundle",
+ "build-cjs-bundles"
)
)
)
@@ -631,5 +684,11 @@ gulp.task(
"./packages/babel-helpers/src/helpers/*.js",
gulp.task("generate-runtime-helpers")
);
+ if (USE_ESM) {
+ gulp.watch(
+ cjsBundles.map(({ src }) => `./${src}/lib/**.js`),
+ gulp.task("build-cjs-bundles")
+ );
+ }
})
);
diff --git a/Makefile b/Makefile
index 0e6b0024a9d7..79d1be069148 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ YARN := yarn
NODE := $(YARN) node
-.PHONY: build build-dist watch lint fix clean test-clean test-only test test-ci publish bootstrap
+.PHONY: build build-dist watch lint fix clean test-clean test-only test test-ci publish bootstrap use-esm use-cjs
build: build-no-bundle
ifneq ("$(BABEL_COVERAGE)", "true")
@@ -24,6 +24,7 @@ ifneq ("$(BABEL_COVERAGE)", "true")
endif
build-bundle: clean clean-lib
+ node ./scripts/set-module-type.js
$(YARN) gulp build
$(MAKE) build-flow-typings
$(MAKE) build-dist
@@ -34,6 +35,7 @@ build-no-bundle-ci: bootstrap-only
$(MAKE) build-dist
build-no-bundle: clean clean-lib
+ node ./scripts/set-module-type.js
BABEL_ENV=development $(YARN) gulp build-dev
$(MAKE) build-flow-typings
$(MAKE) build-dist
@@ -186,6 +188,7 @@ prepublish:
$(MAKE) bootstrap-only
$(MAKE) prepublish-build
IS_PUBLISH=true $(MAKE) test
+ node ./scripts/set-module-type.js clean
new-version-checklist:
# @echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
@@ -221,6 +224,7 @@ ifneq ("$(I_AM_USING_VERDACCIO)", "I_AM_SURE")
endif
$(YARN) release-tool version $(VERSION) --all --yes --tag-version-prefix="version-e2e-test-"
$(MAKE) prepublish-build
+ node ./scripts/set-module-type.js clean
YARN_NPM_PUBLISH_REGISTRY=http://localhost:4873 $(YARN) release-tool publish --yes --tag-version-prefix="version-e2e-test-"
$(MAKE) clean
@@ -230,6 +234,14 @@ bootstrap-only: clean-all
bootstrap: bootstrap-only
$(MAKE) generate-tsconfig build
+use-cjs:
+ node ./scripts/set-module-type.js script
+ $(MAKE) bootstrap
+
+use-esm:
+ node ./scripts/set-module-type.js module
+ $(MAKE) bootstrap
+
clean-lib:
$(foreach source, $(SOURCES), \
$(call clean-source-lib, $(source)))
diff --git a/babel.config.js b/babel.config.js
index 434bbe4d8963..d57134718f4d 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -11,6 +11,14 @@ function normalize(src) {
module.exports = function (api) {
const env = api.env();
+ const outputType = api.cache.invalidate(() => {
+ try {
+ return fs.readFileSync(__dirname + "/.module-type", "utf-8").trim();
+ } catch (_) {
+ return "script";
+ }
+ });
+
const sources = ["packages/*/src", "codemods/*/src", "eslint/*/src"];
const includeCoverage = process.env.BABEL_COVERAGE === "true";
@@ -54,7 +62,8 @@ module.exports = function (api) {
};
let targets = {};
- let convertESM = true;
+ let convertESM = outputType === "script";
+ let addImportExtension = !convertESM;
let ignoreLib = true;
let includeRegeneratorRuntime = false;
let needsPolyfillsForOldNode = false;
@@ -84,6 +93,7 @@ module.exports = function (api) {
case "standalone":
includeRegeneratorRuntime = true;
convertESM = false;
+ addImportExtension = false;
ignoreLib = false;
// rollup-commonjs will converts node_modules to ESM
unambiguousSources.push(
@@ -95,6 +105,7 @@ module.exports = function (api) {
break;
case "rollup":
convertESM = false;
+ addImportExtension = false;
ignoreLib = false;
// rollup-commonjs will converts node_modules to ESM
unambiguousSources.push(
@@ -179,10 +190,6 @@ module.exports = function (api) {
pluginPackageJsonMacro,
- process.env.STRIP_BABEL_8_FLAG && [
- pluginToggleBabel8Breaking,
- { breaking: bool(process.env.BABEL_8_BREAKING) },
- ],
needsPolyfillsForOldNode && pluginPolyfillsOldNode,
].filter(Boolean),
overrides: [
@@ -216,7 +223,25 @@ module.exports = function (api) {
{
test: sources.map(normalize),
assumptions: sourceAssumptions,
- plugins: [transformNamedBabelTypesImportToDestructuring],
+ plugins: [
+ transformNamedBabelTypesImportToDestructuring,
+ addImportExtension ? pluginAddImportExtension : null,
+
+ [
+ pluginToggleBooleanFlag,
+ { name: "USE_ESM", value: outputType === "module" },
+ "flag-USE_ESM",
+ ],
+
+ process.env.STRIP_BABEL_8_FLAG && [
+ pluginToggleBooleanFlag,
+ {
+ name: "process.env.BABEL_8_BREAKING",
+ value: bool(process.env.BABEL_8_BREAKING),
+ },
+ "flag-BABEL_8_BREAKING",
+ ],
+ ].filter(Boolean),
},
convertESM && {
test: lazyRequireSources.map(normalize),
@@ -444,20 +469,19 @@ function pluginPolyfillsOldNode({ template, types: t }) {
},
};
}
-
-function pluginToggleBabel8Breaking({ types: t }, { breaking }) {
+function pluginToggleBooleanFlag({ types: t }, { name, value }) {
return {
visitor: {
"IfStatement|ConditionalExpression"(path) {
let test = path.get("test");
- let keepConsequent = breaking;
+ let keepConsequent = value;
if (test.isUnaryExpression({ operator: "!" })) {
test = test.get("argument");
keepConsequent = !keepConsequent;
}
- // yarn-plugin-conditions inject bool(process.env.BABEL_8_BREAKING)
+ // yarn-plugin-conditions injects bool(process.env.BABEL_8_BREAKING)
// tests, to properly cast the env variable to a boolean.
if (
test.isCallExpression() &&
@@ -467,7 +491,7 @@ function pluginToggleBabel8Breaking({ types: t }, { breaking }) {
test = test.get("arguments")[0];
}
- if (!test.matchesPattern("process.env.BABEL_8_BREAKING")) return;
+ if (!test.isIdentifier({ name }) && !test.matchesPattern(name)) return;
path.replaceWith(
keepConsequent
@@ -476,7 +500,12 @@ function pluginToggleBabel8Breaking({ types: t }, { breaking }) {
);
},
MemberExpression(path) {
- if (path.matchesPattern("process.env.BABEL_8_BREAKING")) {
+ if (path.matchesPattern(name)) {
+ throw path.buildCodeFrameError("This check could not be stripped.");
+ }
+ },
+ ReferencedIdentifier(path) {
+ if (path.node.name === name) {
throw path.buildCodeFrameError("This check could not be stripped.");
}
},
@@ -658,6 +687,56 @@ function pluginImportMetaUrl({ types: t, template }) {
};
}
+function pluginAddImportExtension() {
+ return {
+ visitor: {
+ "ImportDeclaration|ExportDeclaration"({ node }) {
+ const { source } = node;
+ if (!source) return;
+
+ if (source.value.startsWith(".") && !/\.[a-z]+$/.test(source.value)) {
+ const dir = pathUtils.dirname(this.filename);
+
+ try {
+ const pkg = JSON.parse(
+ fs.readFileSync(
+ pathUtils.join(dir, `${source.value}/package.json`)
+ ),
+ "utf8"
+ );
+
+ if (pkg.main) source.value = pathUtils.join(source.value, pkg.main);
+ } catch (_) {}
+
+ try {
+ if (fs.statSync(pathUtils.join(dir, source.value)).isFile()) return;
+ } catch (_) {}
+
+ for (const [src, lib = src] of [["ts", "js"], ["js"], ["cjs"]]) {
+ try {
+ fs.statSync(pathUtils.join(dir, `${source.value}.${src}`));
+ source.value += `.${lib}`;
+ return;
+ } catch (_) {}
+ }
+
+ source.value += "/index.js";
+ }
+ if (
+ source.value.startsWith("lodash/") ||
+ source.value.startsWith("core-js-compat/") ||
+ source.value === "babel-plugin-dynamic-import-node/utils"
+ ) {
+ source.value += ".js";
+ }
+ if (source.value.startsWith("@babel/preset-modules/")) {
+ source.value += "/index.js";
+ }
+ },
+ },
+ };
+}
+
const tokenTypesMapping = new Map();
const tokenTypeSourcePath = "./packages/babel-parser/src/tokenizer/types.js";
diff --git a/constraints.pro b/constraints.pro
index 8fdb6c7976d6..f557c2f4955c 100644
--- a/constraints.pro
+++ b/constraints.pro
@@ -81,8 +81,9 @@ gen_enforced_field(WorkspaceCwd, 'exports', '{ ".": "./lib/index.js", "./package
% Exclude packages with more complex `exports`
workspace_ident(WorkspaceCwd, WorkspaceIdent),
WorkspaceIdent \= '@babel/compat-data',
- WorkspaceIdent \= '@babel/plugin-transform-react-jsx', % TODO: Remove in Babel 8
WorkspaceIdent \= '@babel/helper-plugin-test-runner', % TODO: Remove in Babel 8
+ WorkspaceIdent \= '@babel/parser',
+ WorkspaceIdent \= '@babel/plugin-transform-react-jsx', % TODO: Remove in Babel 8
WorkspaceIdent \= '@babel/standalone',
\+ atom_concat('@babel/eslint-', _, WorkspaceIdent),
\+ atom_concat('@babel/runtime', _, WorkspaceIdent).
diff --git a/eslint/babel-eslint-parser/src/client.cjs b/eslint/babel-eslint-parser/src/client.cjs
index 7925d35b5c00..40d491af1ee0 100644
--- a/eslint/babel-eslint-parser/src/client.cjs
+++ b/eslint/babel-eslint-parser/src/client.cjs
@@ -90,7 +90,7 @@ exports.WorkerClient = class WorkerClient extends Client {
}
};
-if (!process.env.BABEL_8_BREAKING) {
+if (!USE_ESM) {
exports.LocalClient = class LocalClient extends Client {
static #handleMessage;
diff --git a/eslint/babel-eslint-parser/src/index.cjs b/eslint/babel-eslint-parser/src/index.cjs
index f713b839f20e..9725c52166f5 100644
--- a/eslint/babel-eslint-parser/src/index.cjs
+++ b/eslint/babel-eslint-parser/src/index.cjs
@@ -3,9 +3,7 @@ const analyzeScope = require("./analyze-scope.cjs");
const baseParse = require("./parse.cjs");
const { LocalClient, WorkerClient } = require("./client.cjs");
-const client = new (
- process.env.BABEL_8_BREAKING ? WorkerClient : LocalClient
-)();
+const client = new (USE_ESM ? WorkerClient : LocalClient)();
exports.parse = function (code, options = {}) {
return baseParse(code, normalizeESLintConfig(options), client);
diff --git a/eslint/babel-eslint-parser/src/worker/babel-core.cjs b/eslint/babel-eslint-parser/src/worker/babel-core.cjs
index 47486218db92..ebd71b5afd6d 100644
--- a/eslint/babel-eslint-parser/src/worker/babel-core.cjs
+++ b/eslint/babel-eslint-parser/src/worker/babel-core.cjs
@@ -10,8 +10,8 @@ function initialize(babel) {
exports.createConfigItem = babel.createConfigItem;
}
-if (process.env.BABEL_8_BREAKING) {
- exports.init = import("@babel/core").then(ns => initialize(ns.default));
+if (USE_ESM) {
+ exports.init = import("@babel/core").then(initialize);
} else {
initialize(require("@babel/core"));
}
diff --git a/eslint/babel-eslint-parser/src/worker/handle-message.cjs b/eslint/babel-eslint-parser/src/worker/handle-message.cjs
index 7a198a847ff8..f2a82028840d 100644
--- a/eslint/babel-eslint-parser/src/worker/handle-message.cjs
+++ b/eslint/babel-eslint-parser/src/worker/handle-message.cjs
@@ -24,7 +24,7 @@ module.exports = function handleMessage(action, payload) {
maybeParse(payload.code, options),
);
case "MAYBE_PARSE_SYNC":
- if (!process.env.BABEL_8_BREAKING) {
+ if (!USE_ESM) {
return maybeParse(
payload.code,
normalizeBabelParseConfigSync(payload.options),
diff --git a/eslint/babel-eslint-tests/test/integration/config-files.js b/eslint/babel-eslint-tests/test/integration/config-files.js
index 3ed1a87d257f..8ef6a5b1ad0c 100644
--- a/eslint/babel-eslint-tests/test/integration/config-files.js
+++ b/eslint/babel-eslint-tests/test/integration/config-files.js
@@ -1,11 +1,22 @@
import eslint from "eslint";
import path from "path";
import { fileURLToPath } from "url";
+import fs from "fs";
+
+let USE_ESM = false;
+try {
+ const type = fs
+ .readFileSync(new URL("../../../../.module-type", import.meta.url), "utf-8")
+ .trim();
+ USE_ESM = type === "module";
+} catch {}
describe("Babel config files", () => {
- const babel8 = process.env.BABEL_8_BREAKING ? it : it.skip;
+ const itESM = USE_ESM ? it : it.skip;
+ const itNode12upNoESM =
+ USE_ESM || parseInt(process.versions.node) < 12 ? it.skip : it;
- babel8("works with babel.config.mjs", () => {
+ itESM("works with babel.config.mjs", () => {
const engine = new eslint.CLIEngine({ ignore: false });
expect(
engine.executeOnFiles([
@@ -17,12 +28,7 @@ describe("Babel config files", () => {
).toMatchObject({ errorCount: 0 });
});
- const babel7node12 =
- process.env.BABEL_8_BREAKING || parseInt(process.versions.node) < 12
- ? it.skip
- : it;
-
- babel7node12("experimental worker works with babel.config.mjs", () => {
+ itNode12upNoESM("experimental worker works with babel.config.mjs", () => {
const engine = new eslint.CLIEngine({ ignore: false });
expect(
engine.executeOnFiles([
diff --git a/jest.config.js b/jest.config.js
index d03c48348873..984394e6f8e0 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,3 +1,4 @@
+const fs = require("fs");
const semver = require("semver");
const nodeVersion = process.versions.node;
const supportsESMAndJestLightRunner = semver.satisfies(
@@ -8,6 +9,12 @@ const supportsESMAndJestLightRunner = semver.satisfies(
);
const isPublishBundle = process.env.IS_PUBLISH;
+let LIB_USE_ESM = false;
+try {
+ const type = fs.readFileSync(`${__dirname}/.module-type`, "utf-8").trim();
+ LIB_USE_ESM = type === "module";
+} catch (_) {}
+
module.exports = {
runner: supportsESMAndJestLightRunner ? "jest-light-runner" : "jest-runner",
@@ -36,6 +43,9 @@ module.exports = {
// Some tests require internal files of bundled packages, which are not available
// in production builds. They are marked using the .skip-bundled.js extension.
...(isPublishBundle ? ["\\.skip-bundled\\.js$"] : []),
+ ...(LIB_USE_ESM
+ ? ["/babel-node/", "/babel-register/", "/babel-helpers/"]
+ : []),
],
testEnvironment: "node",
transformIgnorePatterns: [
diff --git a/packages/babel-core/cjs-proxy.cjs b/packages/babel-core/cjs-proxy.cjs
new file mode 100644
index 000000000000..4bf8b5cb9ced
--- /dev/null
+++ b/packages/babel-core/cjs-proxy.cjs
@@ -0,0 +1,29 @@
+"use strict";
+
+const babelP = import("./lib/index.js");
+
+const functionNames = [
+ "createConfigItem",
+ "loadPartialConfig",
+ "loadOptions",
+ "transform",
+ "transformFile",
+ "transformFromAst",
+ "parse",
+];
+
+for (const name of functionNames) {
+ exports[`${name}Sync`] = function () {
+ throw new Error(
+ `"${name}Sync" is not supported when loading @babel/core using require()`
+ );
+ };
+ exports[name] = function (...args) {
+ babelP.then(babel => {
+ babel[name](...args);
+ });
+ };
+ exports[`${name}Async`] = function (...args) {
+ return babelP.then(babel => babel[`${name}Async`](...args));
+ };
+}
diff --git a/packages/babel-core/package.json b/packages/babel-core/package.json
index 67a23cc21c55..c4e90b7f1cc2 100644
--- a/packages/babel-core/package.json
+++ b/packages/babel-core/package.json
@@ -50,7 +50,7 @@
"@babel/code-frame": "workspace:^",
"@babel/generator": "workspace:^",
"@babel/helper-compilation-targets": "workspace:^",
- "@babel/helper-module-transforms": "condition:BABEL_8_BREAKING ? : workspace:^7.18.6",
+ "@babel/helper-module-transforms": "workspace:^",
"@babel/helpers": "workspace:^",
"@babel/parser": "workspace:^",
"@babel/template": "workspace:^",
@@ -84,7 +84,14 @@
],
"USE_ESM": [
{
- "type": "module"
+ "type": "module",
+ "exports": {
+ ".": {
+ "require": "./cjs-proxy.cjs",
+ "default": "./lib/index.js"
+ },
+ "./package.json": "./package.json"
+ }
},
null
]
diff --git a/packages/babel-core/src/config/files/import-meta-resolve.ts b/packages/babel-core/src/config/files/import-meta-resolve.ts
index 591aec4d9de4..3807a0776bad 100644
--- a/packages/babel-core/src/config/files/import-meta-resolve.ts
+++ b/packages/babel-core/src/config/files/import-meta-resolve.ts
@@ -6,7 +6,7 @@ const require = createRequire(import.meta.url);
let import_;
try {
// Node < 13.3 doesn't support import() syntax.
- import_ = require("./import").default;
+ import_ = require("./import.cjs");
} catch {}
// import.meta.resolve is only available in ESM, but this file is compiled to CJS.
diff --git a/packages/babel-core/src/config/files/import.ts b/packages/babel-core/src/config/files/import.cjs
similarity index 77%
rename from packages/babel-core/src/config/files/import.ts
rename to packages/babel-core/src/config/files/import.cjs
index 460873bec19f..56793ef5bf77 100644
--- a/packages/babel-core/src/config/files/import.ts
+++ b/packages/babel-core/src/config/files/import.cjs
@@ -2,6 +2,6 @@
// import() isn't supported, we can try/catch around the require() call
// when loading this file.
-export default function import_(filepath: string) {
+module.exports = function import_(filepath: string) {
return import(filepath);
-}
+};
diff --git a/packages/babel-core/src/config/files/module-types.ts b/packages/babel-core/src/config/files/module-types.ts
index 7ece0a7a8906..389054d381bf 100644
--- a/packages/babel-core/src/config/files/module-types.ts
+++ b/packages/babel-core/src/config/files/module-types.ts
@@ -10,7 +10,7 @@ const require = createRequire(import.meta.url);
let import_: ((specifier: string | URL) => any) | undefined;
try {
// Old Node.js versions don't support import() syntax.
- import_ = require("./import").default;
+ import_ = require("./import.cjs");
} catch {}
export const supportsESM = semver.satisfies(
diff --git a/packages/babel-helper-remap-async-to-generator/package.json b/packages/babel-helper-remap-async-to-generator/package.json
index 4cb1715879c3..170b8dfc8367 100644
--- a/packages/babel-helper-remap-async-to-generator/package.json
+++ b/packages/babel-helper-remap-async-to-generator/package.json
@@ -20,6 +20,7 @@
"@babel/types": "workspace:^"
},
"devDependencies": {
+ "@babel/core": "workspace:^",
"@babel/traverse": "workspace:^"
},
"peerDependencies": {
diff --git a/packages/babel-helpers/scripts/generate-regenerator-runtime.js b/packages/babel-helpers/scripts/generate-regenerator-runtime.js
index 91694b2cd9f2..b6bacf65e40a 100644
--- a/packages/babel-helpers/scripts/generate-regenerator-runtime.js
+++ b/packages/babel-helpers/scripts/generate-regenerator-runtime.js
@@ -5,7 +5,7 @@ import { createRequire } from "module";
const [parse, generate] = await Promise.all([
import("@babel/parser").then(ns => ns.parse),
- import("@babel/generator").then(ns => ns.default.default),
+ import("@babel/generator").then(ns => ns.default.default || ns.default),
]).catch(error =>
Promise.reject(
new Error(
diff --git a/packages/babel-parser/index.cjs b/packages/babel-parser/index.cjs
new file mode 100644
index 000000000000..89863a9f3658
--- /dev/null
+++ b/packages/babel-parser/index.cjs
@@ -0,0 +1,5 @@
+try {
+ module.exports = require("./lib/index.cjs");
+} catch {
+ module.exports = require("./lib/index.js");
+}
diff --git a/packages/babel-parser/package.json b/packages/babel-parser/package.json
index e364f944ca73..7cb6c186bce9 100644
--- a/packages/babel-parser/package.json
+++ b/packages/babel-parser/package.json
@@ -27,7 +27,8 @@
"files": [
"bin",
"lib",
- "typings"
+ "typings",
+ "index.cjs"
],
"engines": {
"node": ">=6.0.0"
@@ -49,13 +50,23 @@
],
"USE_ESM": [
{
- "type": "module"
+ "type": "module",
+ "exports": {
+ ".": {
+ "require": "./lib/index.cjs",
+ "default": "./lib/index.js"
+ },
+ "./package.json": "./package.json"
+ }
},
null
]
},
"exports": {
- ".": "./lib/index.js",
+ ".": {
+ "require": "./index.cjs",
+ "default": "./lib/index.js"
+ },
"./package.json": "./package.json"
},
"type": "commonjs"
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-10339/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-10339/exec.js
index 1815a28ade50..3468fe3d5f77 100644
--- a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-10339/exec.js
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-10339/exec.js
@@ -15,7 +15,7 @@ let functionPath;
return transformAsync(code, {
configFile: false,
plugins: [
- __dirname + "/../../../../lib",
+ __dirname + "/../../../../lib/index.js",
{
post({ path }) {
programPath = path;
diff --git a/packages/babel-preset-env/test/index.spec.js b/packages/babel-preset-env/test/index.spec.js
index 67b44ca1d71a..33f2a8916768 100644
--- a/packages/babel-preset-env/test/index.spec.js
+++ b/packages/babel-preset-env/test/index.spec.js
@@ -1,7 +1,7 @@
// eslint-disable-next-line import/extensions
import compatData from "@babel/compat-data/plugins";
-import babelPresetEnv from "../lib/index.js";
+import * as babelPresetEnv from "../lib/index.js";
import _removeRegeneratorEntryPlugin from "../lib/polyfills/regenerator.js";
import _pluginLegacyBabelPolyfill from "../lib/polyfills/babel-polyfill.js";
@@ -14,12 +14,25 @@ const pluginLegacyBabelPolyfill =
const transformations = _transformations.default || _transformations;
const availablePlugins = _availablePlugins.default || _availablePlugins;
+// We need to load the correct plugins version (ESM or CJS),
+// because our tests rely on function identity.
+let pluginCoreJS2, pluginCoreJS3, pluginRegenerator;
+import _pluginCoreJS2_esm from "babel-plugin-polyfill-corejs2";
+import _pluginCoreJS3_esm from "babel-plugin-polyfill-corejs3";
+import _pluginRegenerator_esm from "babel-plugin-polyfill-regenerator";
import { createRequire } from "module";
-const require = createRequire(import.meta.url);
+// eslint-disable-next-line @babel/development-internal/require-default-import-fallback
+if (/* commonjs */ _transformations.default) {
+ const require = createRequire(import.meta.url);
-const pluginCoreJS2 = require("babel-plugin-polyfill-corejs2").default;
-const pluginCoreJS3 = require("babel-plugin-polyfill-corejs3").default;
-const pluginRegenerator = require("babel-plugin-polyfill-regenerator").default;
+ pluginCoreJS2 = require("babel-plugin-polyfill-corejs2").default;
+ pluginCoreJS3 = require("babel-plugin-polyfill-corejs3").default;
+ pluginRegenerator = require("babel-plugin-polyfill-regenerator").default;
+} else {
+ pluginCoreJS2 = _pluginCoreJS2_esm;
+ pluginCoreJS3 = _pluginCoreJS3_esm;
+ pluginRegenerator = _pluginRegenerator_esm;
+}
describe("babel-preset-env", () => {
describe("transformIncludesAndExcludes", () => {
diff --git a/scripts/set-module-type.js b/scripts/set-module-type.js
new file mode 100755
index 000000000000..a8062dc4533a
--- /dev/null
+++ b/scripts/set-module-type.js
@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+
+import fs from "fs";
+
+const root = rel => new URL(`../${rel}`, import.meta.url).pathname;
+
+// prettier-ignore
+let moduleType;
+if (process.argv.length >= 3) {
+ moduleType = process.argv[2];
+} else if (fs.existsSync(root(".module-type"))) {
+ moduleType = fs.readFileSync(root(".module-type"), "utf-8").trim();
+} else {
+ moduleType = "script";
+}
+
+if (moduleType === "clean") {
+ try {
+ fs.unlinkSync(root(".module-type"));
+ } catch {}
+} else if (moduleType === "module" || moduleType === "script") {
+ fs.writeFileSync(root(".module-type"), moduleType);
+} else {
+ throw new Error(`Unknown module type: ${moduleType}`);
+}
+
+["eslint", "codemods", "packages"]
+ .flatMap(dir => fs.readdirSync(root(dir)).map(file => root(`${dir}/${file}`)))
+ .filter(
+ dir =>
+ fs.statSync(dir).isDirectory() && fs.existsSync(`${dir}/package.json`)
+ )
+ .forEach(dir => {
+ if (moduleType === "clean") {
+ try {
+ fs.unlinkSync(`${dir}/lib/package.json`);
+ } catch {}
+ } else {
+ fs.mkdirSync(`${dir}/lib`, { recursive: true });
+ fs.writeFileSync(
+ `${dir}/lib/package.json`,
+ `{ "type": "${moduleType}" }`
+ );
+ }
+ });
diff --git a/yarn.lock b/yarn.lock
index 39f7c56e9623..02f6fd57fdda 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -327,7 +327,7 @@ __metadata:
"@babel/code-frame": "workspace:^"
"@babel/generator": "workspace:^"
"@babel/helper-compilation-targets": "workspace:^"
- "@babel/helper-module-transforms": "condition:BABEL_8_BREAKING ? : workspace:^7.18.6"
+ "@babel/helper-module-transforms": "workspace:^"
"@babel/helper-transform-fixture-test-runner": "workspace:^"
"@babel/helpers": "workspace:^"
"@babel/parser": "workspace:^"
@@ -749,30 +749,6 @@ __metadata:
languageName: unknown
linkType: soft
-"@babel/helper-module-transforms-BABEL_8_BREAKING-false@npm:@babel/helper-module-transforms@workspace:^7.18.6, @babel/helper-module-transforms@workspace:^, @babel/helper-module-transforms@workspace:packages/babel-helper-module-transforms":
- version: 0.0.0-use.local
- resolution: "@babel/helper-module-transforms@workspace:packages/babel-helper-module-transforms"
- dependencies:
- "@babel/helper-environment-visitor": "workspace:^"
- "@babel/helper-module-imports": "workspace:^"
- "@babel/helper-simple-access": "workspace:^"
- "@babel/helper-split-export-declaration": "workspace:^"
- "@babel/helper-validator-identifier": "workspace:^"
- "@babel/template": "workspace:^"
- "@babel/traverse": "workspace:^"
- "@babel/types": "workspace:^"
- languageName: unknown
- linkType: soft
-
-"@babel/helper-module-transforms@condition:BABEL_8_BREAKING ? : workspace:^7.18.6":
- version: 0.0.0-condition-893c47
- resolution: "@babel/helper-module-transforms@condition:BABEL_8_BREAKING?:workspace:^7.18.6#893c47"
- dependencies:
- "@babel/helper-module-transforms-BABEL_8_BREAKING-false": "npm:@babel/helper-module-transforms@workspace:^7.18.6"
- checksum: 7d7ccf481c03d5ba93f2c2fd3d736c67ad544fe53dddd5175790b1b5e0de4130b70566514c073fb51ec56a2ace0fdaa9102ac3cdd0790cc5ac5321142407321c
- languageName: node
- linkType: hard
-
"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.18.0":
version: 7.18.0
resolution: "@babel/helper-module-transforms@npm:7.18.0"
@@ -789,6 +765,21 @@ __metadata:
languageName: node
linkType: hard
+"@babel/helper-module-transforms@workspace:^, @babel/helper-module-transforms@workspace:packages/babel-helper-module-transforms":
+ version: 0.0.0-use.local
+ resolution: "@babel/helper-module-transforms@workspace:packages/babel-helper-module-transforms"
+ dependencies:
+ "@babel/helper-environment-visitor": "workspace:^"
+ "@babel/helper-module-imports": "workspace:^"
+ "@babel/helper-simple-access": "workspace:^"
+ "@babel/helper-split-export-declaration": "workspace:^"
+ "@babel/helper-validator-identifier": "workspace:^"
+ "@babel/template": "workspace:^"
+ "@babel/traverse": "workspace:^"
+ "@babel/types": "workspace:^"
+ languageName: unknown
+ linkType: soft
+
"@babel/helper-optimise-call-expression@npm:^7.16.7":
version: 7.16.7
resolution: "@babel/helper-optimise-call-expression@npm:7.16.7"
@@ -844,6 +835,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@babel/helper-remap-async-to-generator@workspace:packages/babel-helper-remap-async-to-generator"
dependencies:
+ "@babel/core": "workspace:^"
"@babel/helper-annotate-as-pure": "workspace:^"
"@babel/helper-environment-visitor": "workspace:^"
"@babel/helper-wrap-function": "workspace:^"