Repository: babel/babel (TypeScript)

Issue:
[Bug]: @babel/core version 7.22.11 breaks the build
### 💻

### How are you using Babel?

@rollup/plugin-babel

### Input code

I have a simple repro in this repo https://github.com/arthirm/babel-core-issue1. Downloading and doing `yarn install` and `yarn build` should break the build with the following error

```
[!] (plugin babel) SyntaxError: /Users/aravisha/babel-core-issue1/packages/test-plugin/index.ts: Unexpected token, expected "{" (11:7)

   9 |   }
  10 |
> 11 |   run(): void {
     |        ^
  12 |     console.log("Running test plugin");
  13 |   }
  14 | }
packages/test-plugin/index.ts (11:7)
    at constructor (/Users/aravisha/babel-core-issue1/node_modules/@babel/parser/lib/index.js:345:19)
    at Parser.raise (/Users/aravisha/babel-core-issue1/node_modules/@babel/parser/lib/index.js:3199:19)
```

### Current and expected behavior

The build should work fine without breaking.

### Additional context

Note:  Without any other changes if we downgrade the @babel/core changes to 1 patch below (7.22.10) the build works fine.

I also get the below error sometimes especially the first time and the next time I get `Unexpected token, expected "{"` 


```
[!] (plugin babel) SyntaxError: /Users/aravisha/babel-core-issue1/packages/core/index.ts: Support for the experimental syntax 'flow' isn't currently enabled (1:8):
> 1 | export type { BaseConfig } from './config.js';
    |        ^
Add @babel/preset-flow (https://github.com/babel/babel/tree/main/packages/babel-preset-flow) to the 'presets' section of your Babel config to enable transformation.
If you want to leave it as-is, add @babel/plugin-syntax-flow (https://github.com/babel/babel/tree/main/packages/babel-plugin-syntax-flow) to the 'plugins' section to enable parsing.
packages/core/index.ts (1:7)`
```

Also there is also some caching issues. Sometimes I need to delete the node_modules and should do `yarn cache clean` to revert to my original state. Just changing the code is not reflecting the change.


Hints:
Hey @arthirm! We really appreciate you taking the time to report an issue. The collaborators on this project attempt to help as many people as possible, but we're a limited number of volunteers, so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant [Slack community](https://babeljs.slack.com) that typically always has someone willing to help. You can sign-up [here](https://slack.babeljs.io/) for an invite.
Babel itself doesn't do any persistent caching, so that's probably caused by roll-up. It looks like, for some reason, your Babel config is not being applied 🤔

Probably caused by https://github.com/babel/babel/commit/7aa23aa68fb449042ae04abddee35a462ed62b67, I'll investigate.
Oh, so I'm not the only one. I've been trying to investigate this issue for a few days and my guess was that somehow the config was not loaded anymore. Seems like I wasn't far off. I'm using webpack as a bundler. Happy to provide a reproduction repo if necessary.

Patch:
diff --git a/packages/babel-core/src/config/files/configuration.ts b/packages/babel-core/src/config/files/configuration.ts
--- a/packages/babel-core/src/config/files/configuration.ts
+++ b/packages/babel-core/src/config/files/configuration.ts
@@ -41,8 +41,6 @@ const RELATIVE_CONFIG_FILENAMES = [
 
 const BABELIGNORE_FILENAME = ".babelignore";
 
-const LOADING_CONFIGS = new Set();
-
 type ConfigCacheData = {
   envName: string;
   caller: CallerMetadata | undefined;
@@ -72,25 +70,11 @@ function* readConfigCode(
 ): Handler<ConfigFile | null> {
   if (!nodeFs.existsSync(filepath)) return null;
 
-  // The `require()` call below can make this code reentrant if a require hook like @babel/register has been
-  // loaded into the system. That would cause Babel to attempt to compile the `.babelrc.js` file as it loads
-  // below. To cover this case, we auto-ignore re-entrant config processing.
-  if (LOADING_CONFIGS.has(filepath)) {
-    debug("Auto-ignoring usage of config %o.", filepath);
-    return buildConfigFileObject({}, filepath);
-  }
-
-  let options: unknown;
-  try {
-    LOADING_CONFIGS.add(filepath);
-    options = yield* loadCodeDefault(
-      filepath,
-      "You appear to be using a native ECMAScript module configuration " +
-        "file, which is only supported when running Babel asynchronously.",
-    );
-  } finally {
-    LOADING_CONFIGS.delete(filepath);
-  }
+  let options = yield* loadCodeDefault(
+    filepath,
+    "You appear to be using a native ECMAScript module configuration " +
+      "file, which is only supported when running Babel asynchronously.",
+  );
 
   let cacheNeedsConfiguration = false;
   if (typeof options === "function") {
diff --git a/packages/babel-core/src/config/files/module-types.ts b/packages/babel-core/src/config/files/module-types.ts
--- a/packages/babel-core/src/config/files/module-types.ts
+++ b/packages/babel-core/src/config/files/module-types.ts
@@ -4,6 +4,7 @@ import path from "path";
 import { pathToFileURL } from "url";
 import { createRequire } from "module";
 import semver from "semver";
+import buildDebug from "debug";
 
 import { endHiddenCallStack } from "../../errors/rewrite-stack-trace.ts";
 import ConfigError from "../../errors/config-error.ts";
@@ -11,6 +12,8 @@ import ConfigError from "../../errors/config-error.ts";
 import type { InputOptions } from "../index.ts";
 import { transformFileSync } from "../../transform-file.ts";
 
+const debug = buildDebug("babel:config:loading:files:module-types");
+
 const require = createRequire(import.meta.url);
 
 let import_: ((specifier: string | URL) => any) | undefined;
@@ -126,8 +129,7 @@ function loadCtsDefault(filepath: string) {
     require.extensions[ext] = handler;
   }
   try {
-    const module = endHiddenCallStack(require)(filepath);
-    return module?.__esModule ? module.default : module;
+    return loadCjsDefault(filepath);
   } finally {
     if (!hasTsSupport) {
       if (require.extensions[ext] === handler) delete require.extensions[ext];
@@ -136,8 +138,27 @@ function loadCtsDefault(filepath: string) {
   }
 }
 
+const LOADING_CJS_FILES = new Set();
+
 function loadCjsDefault(filepath: string) {
-  const module = endHiddenCallStack(require)(filepath);
+  // The `require()` call below can make this code reentrant if a require hook
+  // like @babel/register has been loaded into the system. That would cause
+  // Babel to attempt to compile the `.babelrc.js` file as it loads below. To
+  // cover this case, we auto-ignore re-entrant config processing. ESM loaders
+  // do not have this problem, because loaders do not apply to themselves.
+  if (LOADING_CJS_FILES.has(filepath)) {
+    debug("Auto-ignoring usage of config %o.", filepath);
+    return {};
+  }
+
+  let module;
+  try {
+    LOADING_CJS_FILES.add(filepath);
+    module = endHiddenCallStack(require)(filepath);
+  } finally {
+    LOADING_CJS_FILES.delete(filepath);
+  }
+
   if (process.env.BABEL_8_BREAKING) {
     return module?.__esModule ? module.default : module;
   } else {


Test patch:
diff --git a/packages/babel-core/test/config-loading.js b/packages/babel-core/test/config-loading.js
--- a/packages/babel-core/test/config-loading.js
+++ b/packages/babel-core/test/config-loading.js
@@ -3,12 +3,14 @@ import {
   loadOptionsSync,
   loadOptionsAsync,
   loadPartialConfig,
+  loadPartialConfigAsync,
   loadPartialConfigSync,
   createConfigItem,
   createConfigItemSync,
 } from "../lib/index.js";
 import path from "path";
 import { itNoWin32, itBabel8, commonJS } from "$repo-utils";
+import { supportsESM } from "./helpers/esm.js";
 
 const { require, __dirname } = commonJS(import.meta.url);
 
@@ -166,6 +168,36 @@ describe("@babel/core config loading", () => {
     });
   });
 
+  describe("loadPartialConfigAsync", () => {
+    // https://github.com/babel/babel/issues/15916
+    (supportsESM ? it : it.skip)(
+      "two calls in parallel loading the same ESM config",
+      async () => {
+        const cwd = path.join(
+          __dirname,
+          "fixtures",
+          "config",
+          "config-files",
+          "babel-config-mjs-object",
+        );
+
+        const [config1, config2] = await Promise.all([
+          loadPartialConfigAsync({
+            cwd,
+            filename: path.join(cwd, "./a.js"),
+          }),
+          loadPartialConfigAsync({
+            cwd,
+            filename: path.join(cwd, "./b.js"),
+          }),
+        ]);
+
+        // eslint-disable-next-line jest/no-standalone-expect
+        expect(config1.options.plugins).toEqual(config2.options.plugins);
+      },
+    );
+  });
+
   describe("loadOptions", () => {
     itBabel8("throws on undefined callback", () => {
       const opts = makeOpts();
diff --git a/packages/babel-core/test/fixtures/config/config-files/babel-config-mjs-object/babel.config.mjs b/packages/babel-core/test/fixtures/config/config-files/babel-config-mjs-object/babel.config.mjs
new file mode 100644
--- /dev/null
+++ b/packages/babel-core/test/fixtures/config/config-files/babel-config-mjs-object/babel.config.mjs
@@ -0,0 +1,3 @@
+export default {
+  plugins: [() => ({})],
+};