Add support for babel.config.mjs and .babelrc.mjs (#10903)
* Add support for babel.config.mjs and .babelrc.mjs * Use path.extname
This commit is contained in:
parent
282f81bd67
commit
ae06baf22f
@ -107,6 +107,10 @@ module.exports = function(api) {
|
|||||||
["@babel/plugin-proposal-nullish-coalescing-operator", { loose: true }],
|
["@babel/plugin-proposal-nullish-coalescing-operator", { loose: true }],
|
||||||
|
|
||||||
convertESM ? "@babel/transform-modules-commonjs" : null,
|
convertESM ? "@babel/transform-modules-commonjs" : null,
|
||||||
|
// Until Jest supports native mjs, we must simulate it 🤷
|
||||||
|
env === "test" || env === "development"
|
||||||
|
? "@babel/plugin-proposal-dynamic-import"
|
||||||
|
: null,
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
} from "../caching";
|
} from "../caching";
|
||||||
import makeAPI, { type PluginAPI } from "../helpers/config-api";
|
import makeAPI, { type PluginAPI } from "../helpers/config-api";
|
||||||
import { makeStaticFileCache } from "./utils";
|
import { makeStaticFileCache } from "./utils";
|
||||||
|
import loadCjsOrMjsDefault from "./module-types";
|
||||||
import pathPatternToRegex from "../pattern-to-regex";
|
import pathPatternToRegex from "../pattern-to-regex";
|
||||||
import type { FilePackageData, RelativeConfig, ConfigFile } from "./types";
|
import type { FilePackageData, RelativeConfig, ConfigFile } from "./types";
|
||||||
import type { CallerMetadata } from "../validation/options";
|
import type { CallerMetadata } from "../validation/options";
|
||||||
@ -23,9 +24,15 @@ const debug = buildDebug("babel:config:loading:files:configuration");
|
|||||||
export const ROOT_CONFIG_FILENAMES = [
|
export const ROOT_CONFIG_FILENAMES = [
|
||||||
"babel.config.js",
|
"babel.config.js",
|
||||||
"babel.config.cjs",
|
"babel.config.cjs",
|
||||||
|
"babel.config.mjs",
|
||||||
"babel.config.json",
|
"babel.config.json",
|
||||||
];
|
];
|
||||||
const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs"];
|
const RELATIVE_CONFIG_FILENAMES = [
|
||||||
|
".babelrc",
|
||||||
|
".babelrc.js",
|
||||||
|
".babelrc.cjs",
|
||||||
|
".babelrc.mjs",
|
||||||
|
];
|
||||||
|
|
||||||
const BABELIGNORE_FILENAME = ".babelignore";
|
const BABELIGNORE_FILENAME = ".babelignore";
|
||||||
|
|
||||||
@ -144,7 +151,7 @@ export function* loadConfig(
|
|||||||
*/
|
*/
|
||||||
function readConfig(filepath, envName, caller) {
|
function readConfig(filepath, envName, caller) {
|
||||||
const ext = path.extname(filepath);
|
const ext = path.extname(filepath);
|
||||||
return ext === ".js" || ext === ".cjs"
|
return ext === ".js" || ext === ".cjs" || ext === ".mjs"
|
||||||
? readConfigJS(filepath, { envName, caller })
|
? readConfigJS(filepath, { envName, caller })
|
||||||
: readConfigJSON5(filepath);
|
: readConfigJSON5(filepath);
|
||||||
}
|
}
|
||||||
@ -177,17 +184,14 @@ const readConfigJS = makeStrongCache(function* readConfigJS(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let options;
|
let options: mixed;
|
||||||
try {
|
try {
|
||||||
LOADING_CONFIGS.add(filepath);
|
LOADING_CONFIGS.add(filepath);
|
||||||
|
options = (yield* loadCjsOrMjsDefault(
|
||||||
yield* []; // If we want to allow mjs configs imported using `import()`
|
filepath,
|
||||||
// $FlowIssue
|
"You appear to be using a native ECMAScript module configuration " +
|
||||||
const configModule = (require(filepath): mixed);
|
"file, which is only supported when running Babel asynchronously.",
|
||||||
options =
|
): mixed);
|
||||||
configModule && configModule.__esModule
|
|
||||||
? configModule.default || undefined
|
|
||||||
: configModule;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
err.message = `${filepath}: Error while loading config - ${err.message}`;
|
err.message = `${filepath}: Error while loading config - ${err.message}`;
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
7
packages/babel-core/src/config/files/import.js
Normal file
7
packages/babel-core/src/config/files/import.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// We keep this in a seprate file so that in older node versions, where
|
||||||
|
// import() isn't supported, we can try/catch around the require() call
|
||||||
|
// when loading this file.
|
||||||
|
|
||||||
|
export default function import_(filepath: string) {
|
||||||
|
return import(filepath);
|
||||||
|
}
|
||||||
59
packages/babel-core/src/config/files/module-types.js
Normal file
59
packages/babel-core/src/config/files/module-types.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { isAsync, waitFor } from "../../gensync-utils/async";
|
||||||
|
import type { Handler } from "gensync";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
let import_;
|
||||||
|
try {
|
||||||
|
// Node < 13.3 doesn't support import() syntax.
|
||||||
|
import_ = require("./import").default;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
export default function* loadCjsOrMjsDefault(
|
||||||
|
filepath: string,
|
||||||
|
asyncError: string,
|
||||||
|
): Handler<mixed> {
|
||||||
|
switch (guessJSModuleType(filepath)) {
|
||||||
|
case "cjs":
|
||||||
|
return loadCjsDefault(filepath);
|
||||||
|
case "unknown":
|
||||||
|
try {
|
||||||
|
return loadCjsDefault(filepath);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== "ERR_REQUIRE_ESM") throw e;
|
||||||
|
}
|
||||||
|
case "mjs":
|
||||||
|
if (yield* isAsync()) {
|
||||||
|
return yield* waitFor(loadMjsDefault(filepath));
|
||||||
|
}
|
||||||
|
throw new Error(asyncError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function guessJSModuleType(filename: string): "cjs" | "mjs" | "unknown" {
|
||||||
|
switch (path.extname(filename)) {
|
||||||
|
case ".cjs":
|
||||||
|
return "cjs";
|
||||||
|
case ".mjs":
|
||||||
|
return "mjs";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCjsDefault(filepath: string) {
|
||||||
|
const module = (require(filepath): mixed);
|
||||||
|
// TODO (Babel 8): Remove "undefined" fallback
|
||||||
|
return module?.__esModule ? module.default || undefined : module;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMjsDefault(filepath: string) {
|
||||||
|
if (!import_) {
|
||||||
|
throw new Error(
|
||||||
|
"Internal error: Native ECMAScript modules aren't supported" +
|
||||||
|
" by this platform.\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const module = await import_(filepath);
|
||||||
|
return module.default;
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ import fs from "fs";
|
|||||||
import os from "os";
|
import os from "os";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import escapeRegExp from "lodash/escapeRegExp";
|
import escapeRegExp from "lodash/escapeRegExp";
|
||||||
import { loadOptions as loadOptionsOrig } from "../lib";
|
import * as babel from "../lib";
|
||||||
|
|
||||||
// TODO: In Babel 8, we can directly uses fs.promises which is supported by
|
// TODO: In Babel 8, we can directly uses fs.promises which is supported by
|
||||||
// node 8+
|
// node 8+
|
||||||
@ -44,10 +44,11 @@ function fixture(...args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadOptions(opts) {
|
function loadOptions(opts) {
|
||||||
return loadOptionsOrig({
|
return babel.loadOptions({ cwd: __dirname, ...opts });
|
||||||
cwd: __dirname,
|
}
|
||||||
...opts,
|
|
||||||
});
|
function loadOptionsAsync(opts) {
|
||||||
|
return babel.loadOptionsAsync({ cwd: __dirname, ...opts });
|
||||||
}
|
}
|
||||||
|
|
||||||
function pairs(items) {
|
function pairs(items) {
|
||||||
@ -1000,21 +1001,16 @@ describe("buildConfigChain", function() {
|
|||||||
|
|
||||||
describe("root", () => {
|
describe("root", () => {
|
||||||
test.each(["babel.config.json", "babel.config.js", "babel.config.cjs"])(
|
test.each(["babel.config.json", "babel.config.js", "babel.config.cjs"])(
|
||||||
"should load %s",
|
"should load %s synchronously",
|
||||||
async name => {
|
async name => {
|
||||||
const { cwd, tmp, config } = await getTemp(
|
const { cwd, tmp, config } = await getTemp(
|
||||||
`babel-test-load-config-${name}`,
|
`babel-test-load-config-sync-${name}`,
|
||||||
);
|
);
|
||||||
const filename = tmp("src.js");
|
const filename = tmp("src.js");
|
||||||
|
|
||||||
await config(name);
|
await config(name);
|
||||||
|
|
||||||
expect(
|
expect(loadOptions({ filename, cwd })).toEqual({
|
||||||
loadOptions({
|
|
||||||
filename,
|
|
||||||
cwd,
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
...getDefaults(),
|
...getDefaults(),
|
||||||
filename,
|
filename,
|
||||||
cwd,
|
cwd,
|
||||||
@ -1024,8 +1020,48 @@ describe("buildConfigChain", function() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test("should not load babel.config.mjs synchronously", async () => {
|
||||||
|
const { cwd, tmp, config } = await getTemp(
|
||||||
|
"babel-test-load-config-sync-babel.config.mjs",
|
||||||
|
);
|
||||||
|
const filename = tmp("src.js");
|
||||||
|
|
||||||
|
await config("babel.config.mjs");
|
||||||
|
|
||||||
|
expect(() => loadOptions({ filename, cwd })).toThrow(
|
||||||
|
/is only supported when running Babel asynchronously/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
"babel.config.json",
|
||||||
|
"babel.config.js",
|
||||||
|
"babel.config.cjs",
|
||||||
|
"babel.config.mjs",
|
||||||
|
])("should load %s asynchronously", async name => {
|
||||||
|
const { cwd, tmp, config } = await getTemp(
|
||||||
|
`babel-test-load-config-async-${name}`,
|
||||||
|
);
|
||||||
|
const filename = tmp("src.js");
|
||||||
|
|
||||||
|
await config(name);
|
||||||
|
|
||||||
|
expect(await loadOptionsAsync({ filename, cwd })).toEqual({
|
||||||
|
...getDefaults(),
|
||||||
|
filename,
|
||||||
|
cwd,
|
||||||
|
root: cwd,
|
||||||
|
comments: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.each(
|
test.each(
|
||||||
pairs(["babel.config.json", "babel.config.js", "babel.config.cjs"]),
|
pairs([
|
||||||
|
"babel.config.json",
|
||||||
|
"babel.config.js",
|
||||||
|
"babel.config.cjs",
|
||||||
|
"babel.config.mjs",
|
||||||
|
]),
|
||||||
)("should throw if both %s and %s are used", async (name1, name2) => {
|
)("should throw if both %s and %s are used", async (name1, name2) => {
|
||||||
const { cwd, tmp, config } = await getTemp(
|
const { cwd, tmp, config } = await getTemp(
|
||||||
`babel-test-dup-config-${name1}-${name2}`,
|
`babel-test-dup-config-${name1}-${name2}`,
|
||||||
@ -1033,15 +1069,15 @@ describe("buildConfigChain", function() {
|
|||||||
|
|
||||||
await Promise.all([config(name1), config(name2)]);
|
await Promise.all([config(name1), config(name2)]);
|
||||||
|
|
||||||
expect(() => loadOptions({ filename: tmp("src.js"), cwd })).toThrow(
|
await expect(
|
||||||
/Multiple configuration files found/,
|
loadOptionsAsync({ filename: tmp("src.js"), cwd }),
|
||||||
);
|
).rejects.toThrow(/Multiple configuration files found/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("relative", () => {
|
describe("relative", () => {
|
||||||
test.each(["package.json", ".babelrc", ".babelrc.js", ".babelrc.cjs"])(
|
test.each(["package.json", ".babelrc", ".babelrc.js", ".babelrc.cjs"])(
|
||||||
"should load %s",
|
"should load %s synchronously",
|
||||||
async name => {
|
async name => {
|
||||||
const { cwd, tmp, config } = await getTemp(
|
const { cwd, tmp, config } = await getTemp(
|
||||||
`babel-test-load-config-${name}`,
|
`babel-test-load-config-${name}`,
|
||||||
@ -1050,12 +1086,7 @@ describe("buildConfigChain", function() {
|
|||||||
|
|
||||||
await config(name);
|
await config(name);
|
||||||
|
|
||||||
expect(
|
expect(loadOptions({ filename, cwd })).toEqual({
|
||||||
loadOptions({
|
|
||||||
filename,
|
|
||||||
cwd,
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
...getDefaults(),
|
...getDefaults(),
|
||||||
filename,
|
filename,
|
||||||
cwd,
|
cwd,
|
||||||
@ -1065,6 +1096,42 @@ describe("buildConfigChain", function() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test("should not load .babelrc.mjs synchronously", async () => {
|
||||||
|
const { cwd, tmp, config } = await getTemp(
|
||||||
|
"babel-test-load-config-sync-.babelrc.mjs",
|
||||||
|
);
|
||||||
|
const filename = tmp("src.js");
|
||||||
|
|
||||||
|
await config(".babelrc.mjs");
|
||||||
|
|
||||||
|
expect(() => loadOptions({ filename, cwd })).toThrow(
|
||||||
|
/is only supported when running Babel asynchronously/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
"package.json",
|
||||||
|
".babelrc",
|
||||||
|
".babelrc.js",
|
||||||
|
".babelrc.cjs",
|
||||||
|
".babelrc.mjs",
|
||||||
|
])("should load %s asynchronously", async name => {
|
||||||
|
const { cwd, tmp, config } = await getTemp(
|
||||||
|
`babel-test-load-config-${name}`,
|
||||||
|
);
|
||||||
|
const filename = tmp("src.js");
|
||||||
|
|
||||||
|
await config(name);
|
||||||
|
|
||||||
|
expect(await loadOptionsAsync({ filename, cwd })).toEqual({
|
||||||
|
...getDefaults(),
|
||||||
|
filename,
|
||||||
|
cwd,
|
||||||
|
root: cwd,
|
||||||
|
comments: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should load .babelignore", () => {
|
it("should load .babelignore", () => {
|
||||||
const filename = fixture("config-files", "babelignore", "src.js");
|
const filename = fixture("config-files", "babelignore", "src.js");
|
||||||
|
|
||||||
@ -1074,7 +1141,13 @@ describe("buildConfigChain", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.each(
|
test.each(
|
||||||
pairs(["package.json", ".babelrc", ".babelrc.js", ".babelrc.cjs"]),
|
pairs([
|
||||||
|
"package.json",
|
||||||
|
".babelrc",
|
||||||
|
".babelrc.js",
|
||||||
|
".babelrc.cjs",
|
||||||
|
".babelrc.mjs",
|
||||||
|
]),
|
||||||
)("should throw if both %s and %s are used", async (name1, name2) => {
|
)("should throw if both %s and %s are used", async (name1, name2) => {
|
||||||
const { cwd, tmp, config } = await getTemp(
|
const { cwd, tmp, config } = await getTemp(
|
||||||
`babel-test-dup-config-${name1}-${name2}`,
|
`babel-test-dup-config-${name1}-${name2}`,
|
||||||
@ -1082,9 +1155,9 @@ describe("buildConfigChain", function() {
|
|||||||
|
|
||||||
await Promise.all([config(name1), config(name2)]);
|
await Promise.all([config(name1), config(name2)]);
|
||||||
|
|
||||||
expect(() => loadOptions({ filename: tmp("src.js"), cwd })).toThrow(
|
await expect(
|
||||||
/Multiple configuration files found/,
|
loadOptionsAsync({ filename: tmp("src.js"), cwd }),
|
||||||
);
|
).rejects.toThrow(/Multiple configuration files found/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should ignore package.json without a 'babel' property", () => {
|
it("should ignore package.json without a 'babel' property", () => {
|
||||||
@ -1104,13 +1177,14 @@ describe("buildConfigChain", function() {
|
|||||||
${".babelrc"} | ${"babelrc-error"} | ${/Error while parsing config - /}
|
${".babelrc"} | ${"babelrc-error"} | ${/Error while parsing config - /}
|
||||||
${".babelrc.js"} | ${"babelrc-js-error"} | ${/Babelrc threw an error/}
|
${".babelrc.js"} | ${"babelrc-js-error"} | ${/Babelrc threw an error/}
|
||||||
${".babelrc.cjs"} | ${"babelrc-cjs-error"} | ${/Babelrc threw an error/}
|
${".babelrc.cjs"} | ${"babelrc-cjs-error"} | ${/Babelrc threw an error/}
|
||||||
|
${".babelrc.mjs"} | ${"babelrc-mjs-error"} | ${/Babelrc threw an error/}
|
||||||
${"package.json"} | ${"pkg-error"} | ${/Error while parsing JSON - /}
|
${"package.json"} | ${"pkg-error"} | ${/Error while parsing JSON - /}
|
||||||
`("should show helpful errors for $config", ({ dir, error }) => {
|
`("should show helpful errors for $config", async ({ dir, error }) => {
|
||||||
const filename = fixture("config-files", dir, "src.js");
|
const filename = fixture("config-files", dir, "src.js");
|
||||||
|
|
||||||
expect(() =>
|
await expect(
|
||||||
loadOptions({ filename, cwd: path.dirname(filename) }),
|
loadOptionsAsync({ filename, cwd: path.dirname(filename) }),
|
||||||
).toThrow(error);
|
).rejects.toThrow(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
8
packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.mjs
vendored
Normal file
8
packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.mjs
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Until Jest supports native mjs, we must simulate it 🤷
|
||||||
|
|
||||||
|
module.exports = new Promise(resolve => resolve({
|
||||||
|
default: {
|
||||||
|
comments: true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
module.exports.__esModule = true;
|
||||||
8
packages/babel-core/test/fixtures/config/config-files-templates/babel.config.mjs
vendored
Normal file
8
packages/babel-core/test/fixtures/config/config-files-templates/babel.config.mjs
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Until Jest supports native mjs, we must simulate it 🤷
|
||||||
|
|
||||||
|
module.exports = new Promise(resolve => resolve({
|
||||||
|
default: {
|
||||||
|
comments: true
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
module.exports.__esModule = true;
|
||||||
8
packages/babel-core/test/fixtures/config/config-files/babelrc-mjs-error/.babelrc.mjs
vendored
Normal file
8
packages/babel-core/test/fixtures/config/config-files/babelrc-mjs-error/.babelrc.mjs
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Until Jest supports native mjs, we must simulate it 🤷
|
||||||
|
|
||||||
|
module.exports = new Promise(resolve => resolve({
|
||||||
|
default: function () {
|
||||||
|
throw new Error("Babelrc threw an error");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
module.exports.__esModule = true;
|
||||||
Loading…
x
Reference in New Issue
Block a user