Add support for .cjs config files (#10599)
* Remove duplicate config loading logic and errors * Add support for .cjs config files * Add tests * [tests] Fallback for fs.promises on node 6
This commit is contained in:
parent
58a646be59
commit
bea1b0d0af
@ -18,26 +18,22 @@ import type { CallerMetadata } from "../validation/options";
|
|||||||
|
|
||||||
const debug = buildDebug("babel:config:loading:files:configuration");
|
const debug = buildDebug("babel:config:loading:files:configuration");
|
||||||
|
|
||||||
const BABEL_CONFIG_JS_FILENAME = "babel.config.js";
|
|
||||||
const BABEL_CONFIG_JSON_FILENAME = "babel.config.json";
|
|
||||||
const ROOT_CONFIG_FILENAMES = [
|
const ROOT_CONFIG_FILENAMES = [
|
||||||
BABEL_CONFIG_JS_FILENAME,
|
"babel.config.js",
|
||||||
BABEL_CONFIG_JSON_FILENAME,
|
"babel.config.cjs",
|
||||||
|
"babel.config.json",
|
||||||
];
|
];
|
||||||
|
const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs"];
|
||||||
|
|
||||||
const BABELRC_FILENAME = ".babelrc";
|
|
||||||
const BABELRC_JS_FILENAME = ".babelrc.js";
|
|
||||||
const BABELIGNORE_FILENAME = ".babelignore";
|
const BABELIGNORE_FILENAME = ".babelignore";
|
||||||
|
|
||||||
export function findConfigUpwards(rootDir: string): string | null {
|
export function findConfigUpwards(rootDir: string): string | null {
|
||||||
let dirname = rootDir;
|
let dirname = rootDir;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (
|
const configFileFound = ROOT_CONFIG_FILENAMES.some(filename =>
|
||||||
fs.existsSync(path.join(dirname, BABEL_CONFIG_JS_FILENAME)) ||
|
fs.existsSync(path.join(dirname, filename)),
|
||||||
fs.existsSync(path.join(dirname, BABEL_CONFIG_JSON_FILENAME))
|
);
|
||||||
) {
|
if (configFileFound) return dirname;
|
||||||
return dirname;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextDir = path.dirname(dirname);
|
const nextDir = path.dirname(dirname);
|
||||||
if (dirname === nextDir) break;
|
if (dirname === nextDir) break;
|
||||||
@ -59,45 +55,15 @@ export function findRelativeConfig(
|
|||||||
|
|
||||||
for (const loc of packageData.directories) {
|
for (const loc of packageData.directories) {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
config = [BABELRC_FILENAME, BABELRC_JS_FILENAME].reduce(
|
config = loadOneConfig(
|
||||||
(previousConfig: ConfigFile | null, name) => {
|
RELATIVE_CONFIG_FILENAMES,
|
||||||
const filepath = path.join(loc, name);
|
loc,
|
||||||
const config = readConfig(filepath, envName, caller);
|
envName,
|
||||||
|
caller,
|
||||||
if (config && previousConfig) {
|
|
||||||
throw new Error(
|
|
||||||
`Multiple configuration files found. Please remove one:\n` +
|
|
||||||
` - ${path.basename(previousConfig.filepath)}\n` +
|
|
||||||
` - ${name}\n` +
|
|
||||||
`from ${loc}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config || previousConfig;
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const pkgConfig =
|
|
||||||
packageData.pkg && packageData.pkg.dirname === loc
|
packageData.pkg && packageData.pkg.dirname === loc
|
||||||
? packageToBabelConfig(packageData.pkg)
|
? packageToBabelConfig(packageData.pkg)
|
||||||
: null;
|
: null,
|
||||||
|
);
|
||||||
if (pkgConfig) {
|
|
||||||
if (config) {
|
|
||||||
throw new Error(
|
|
||||||
`Multiple configuration files found. Please remove one:\n` +
|
|
||||||
` - ${path.basename(pkgConfig.filepath)}#babel\n` +
|
|
||||||
` - ${path.basename(config.filepath)}\n` +
|
|
||||||
`from ${loc}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
config = pkgConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config) {
|
|
||||||
debug("Found configuration %o from %o.", config.filepath, dirname);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ignore) {
|
if (!ignore) {
|
||||||
@ -118,24 +84,31 @@ export function findRootConfig(
|
|||||||
envName: string,
|
envName: string,
|
||||||
caller: CallerMetadata | void,
|
caller: CallerMetadata | void,
|
||||||
): ConfigFile | null {
|
): ConfigFile | null {
|
||||||
const config = ROOT_CONFIG_FILENAMES.reduce(
|
return loadOneConfig(ROOT_CONFIG_FILENAMES, dirname, envName, caller);
|
||||||
(previousConfig: ConfigFile | null, name) => {
|
}
|
||||||
const filepath = path.resolve(dirname, name);
|
|
||||||
const config = readConfig(filepath, envName, caller);
|
|
||||||
|
|
||||||
if (config && previousConfig) {
|
function loadOneConfig(
|
||||||
throw new Error(
|
names: string[],
|
||||||
`Multiple configuration files found. Please remove one:\n` +
|
dirname: string,
|
||||||
` - ${path.basename(previousConfig.filepath)}\n` +
|
envName: string,
|
||||||
` - ${name}\n` +
|
caller: CallerMetadata | void,
|
||||||
`from ${dirname}`,
|
previousConfig?: ConfigFile | null = null,
|
||||||
);
|
): ConfigFile | null {
|
||||||
}
|
const config = names.reduce((previousConfig: ConfigFile | null, name) => {
|
||||||
|
const filepath = path.resolve(dirname, name);
|
||||||
|
const config = readConfig(filepath, envName, caller);
|
||||||
|
|
||||||
return config || previousConfig;
|
if (config && previousConfig) {
|
||||||
},
|
throw new Error(
|
||||||
null,
|
`Multiple configuration files found. Please remove one:\n` +
|
||||||
);
|
` - ${path.basename(previousConfig.filepath)}\n` +
|
||||||
|
` - ${name}\n` +
|
||||||
|
`from ${dirname}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config || previousConfig;
|
||||||
|
}, previousConfig);
|
||||||
|
|
||||||
if (config) {
|
if (config) {
|
||||||
debug("Found configuration %o from %o.", config.filepath, dirname);
|
debug("Found configuration %o from %o.", config.filepath, dirname);
|
||||||
@ -165,7 +138,8 @@ export function loadConfig(
|
|||||||
* throw if there are parsing errors while loading a config.
|
* throw if there are parsing errors while loading a config.
|
||||||
*/
|
*/
|
||||||
function readConfig(filepath, envName, caller): ConfigFile | null {
|
function readConfig(filepath, envName, caller): ConfigFile | null {
|
||||||
return path.extname(filepath) === ".js"
|
const ext = path.extname(filepath);
|
||||||
|
return ext === ".js" || ext === ".cjs"
|
||||||
? readConfigJS(filepath, { envName, caller })
|
? readConfigJS(filepath, { envName, caller })
|
||||||
: readConfigJSON5(filepath);
|
: readConfigJSON5(filepath);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,44 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
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 { loadOptions as loadOptionsOrig } from "../lib";
|
||||||
|
|
||||||
|
// TODO: In Babel 8, we can directly uses fs.promises which is supported by
|
||||||
|
// node 8+
|
||||||
|
const pfs =
|
||||||
|
fs.promises ??
|
||||||
|
new Proxy(fs, {
|
||||||
|
get(target, name) {
|
||||||
|
if (name === "copyFile") {
|
||||||
|
// fs.copyFile is only supported since node 8.5
|
||||||
|
// https://stackoverflow.com/a/30405105/2359289
|
||||||
|
return function copyFile(source, target) {
|
||||||
|
const rd = fs.createReadStream(source);
|
||||||
|
const wr = fs.createWriteStream(target);
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
rd.on("error", reject);
|
||||||
|
wr.on("error", reject);
|
||||||
|
wr.on("finish", resolve);
|
||||||
|
rd.pipe(wr);
|
||||||
|
}).catch(function(error) {
|
||||||
|
rd.destroy();
|
||||||
|
wr.end();
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (...args) =>
|
||||||
|
new Promise((resolve, reject) =>
|
||||||
|
target[name](...args, (error, result) => {
|
||||||
|
if (error) reject(error);
|
||||||
|
else resolve(result);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function fixture(...args) {
|
function fixture(...args) {
|
||||||
return path.join(__dirname, "fixtures", "config", ...args);
|
return path.join(__dirname, "fixtures", "config", ...args);
|
||||||
}
|
}
|
||||||
@ -14,6 +50,24 @@ function loadOptions(opts) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pairs(items) {
|
||||||
|
const pairs = [];
|
||||||
|
for (let i = 0; i < items.length - 1; i++) {
|
||||||
|
for (let j = i + 1; j < items.length; j++) {
|
||||||
|
pairs.push([items[i], items[j]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pairs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTemp(name) {
|
||||||
|
const cwd = await pfs.mkdtemp(os.tmpdir() + path.sep + name);
|
||||||
|
const tmp = name => path.join(cwd, name);
|
||||||
|
const config = name =>
|
||||||
|
pfs.copyFile(fixture("config-files-templates", name), tmp(name));
|
||||||
|
return { cwd, tmp, config };
|
||||||
|
}
|
||||||
|
|
||||||
describe("buildConfigChain", function() {
|
describe("buildConfigChain", function() {
|
||||||
describe("test", () => {
|
describe("test", () => {
|
||||||
describe("single", () => {
|
describe("single", () => {
|
||||||
@ -944,159 +998,122 @@ describe("buildConfigChain", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should load babel.config.json", () => {
|
describe("root", () => {
|
||||||
const filename = fixture("config-files", "babel-config-json", "src.js");
|
test.each(["babel.config.json", "babel.config.js", "babel.config.cjs"])(
|
||||||
|
"should load %s",
|
||||||
|
async name => {
|
||||||
|
const { cwd, tmp, config } = await getTemp(
|
||||||
|
`babel-test-load-config-${name}`,
|
||||||
|
);
|
||||||
|
const filename = tmp("src.js");
|
||||||
|
|
||||||
expect(
|
await config(name);
|
||||||
loadOptions({
|
|
||||||
filename,
|
|
||||||
cwd: path.dirname(filename),
|
|
||||||
}),
|
|
||||||
).toEqual({
|
|
||||||
...getDefaults(),
|
|
||||||
filename: filename,
|
|
||||||
cwd: path.dirname(filename),
|
|
||||||
root: path.dirname(filename),
|
|
||||||
comments: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should load babel.config.js", () => {
|
expect(
|
||||||
const filename = fixture("config-files", "babel-config-js", "src.js");
|
loadOptions({
|
||||||
|
filename,
|
||||||
expect(
|
cwd,
|
||||||
loadOptions({
|
}),
|
||||||
filename,
|
).toEqual({
|
||||||
cwd: path.dirname(filename),
|
...getDefaults(),
|
||||||
}),
|
filename,
|
||||||
).toEqual({
|
cwd,
|
||||||
...getDefaults(),
|
root: cwd,
|
||||||
filename: filename,
|
comments: true,
|
||||||
cwd: path.dirname(filename),
|
});
|
||||||
root: path.dirname(filename),
|
},
|
||||||
comments: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should whtow if both babel.config.json and babel.config.js are used", () => {
|
|
||||||
const filename = fixture(
|
|
||||||
"config-files",
|
|
||||||
"babel-config-js-and-json",
|
|
||||||
"src.js",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(() =>
|
test.each(
|
||||||
loadOptions({ filename, cwd: path.dirname(filename) }),
|
pairs(["babel.config.json", "babel.config.js", "babel.config.cjs"]),
|
||||||
).toThrow(/Multiple configuration files found/);
|
)("should throw if both %s and %s are used", async (name1, name2) => {
|
||||||
|
const { cwd, tmp, config } = await getTemp(
|
||||||
|
`babel-test-dup-config-${name1}-${name2}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all([config(name1), config(name2)]);
|
||||||
|
|
||||||
|
expect(() => loadOptions({ filename: tmp("src.js"), cwd })).toThrow(
|
||||||
|
/Multiple configuration files found/,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should load .babelrc", () => {
|
describe("relative", () => {
|
||||||
const filename = fixture("config-files", "babelrc", "src.js");
|
test.each(["package.json", ".babelrc", ".babelrc.js", ".babelrc.cjs"])(
|
||||||
|
"should load %s",
|
||||||
|
async name => {
|
||||||
|
const { cwd, tmp, config } = await getTemp(
|
||||||
|
`babel-test-load-config-${name}`,
|
||||||
|
);
|
||||||
|
const filename = tmp("src.js");
|
||||||
|
|
||||||
expect(
|
await config(name);
|
||||||
loadOptions({
|
|
||||||
filename,
|
expect(
|
||||||
|
loadOptions({
|
||||||
|
filename,
|
||||||
|
cwd,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
...getDefaults(),
|
||||||
|
filename,
|
||||||
|
cwd,
|
||||||
|
root: cwd,
|
||||||
|
comments: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("should load .babelignore", () => {
|
||||||
|
const filename = fixture("config-files", "babelignore", "src.js");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
loadOptions({ filename, cwd: path.dirname(filename) }),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(
|
||||||
|
pairs(["package.json", ".babelrc", ".babelrc.js", ".babelrc.cjs"]),
|
||||||
|
)("should throw if both %s and %s are used", async (name1, name2) => {
|
||||||
|
const { cwd, tmp, config } = await getTemp(
|
||||||
|
`babel-test-dup-config-${name1}-${name2}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all([config(name1), config(name2)]);
|
||||||
|
|
||||||
|
expect(() => loadOptions({ filename: tmp("src.js"), cwd })).toThrow(
|
||||||
|
/Multiple configuration files found/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ignore package.json without a 'babel' property", () => {
|
||||||
|
const filename = fixture("config-files", "pkg-ignored", "src.js");
|
||||||
|
|
||||||
|
expect(loadOptions({ filename, cwd: path.dirname(filename) })).toEqual({
|
||||||
|
...getDefaults(),
|
||||||
|
filename: filename,
|
||||||
cwd: path.dirname(filename),
|
cwd: path.dirname(filename),
|
||||||
}),
|
root: path.dirname(filename),
|
||||||
).toEqual({
|
comments: true,
|
||||||
...getDefaults(),
|
});
|
||||||
filename: filename,
|
|
||||||
cwd: path.dirname(filename),
|
|
||||||
root: path.dirname(filename),
|
|
||||||
comments: true,
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it("should load .babelrc.js", () => {
|
test.each`
|
||||||
const filename = fixture("config-files", "babelrc-js", "src.js");
|
config | dir | error
|
||||||
|
${".babelrc"} | ${"babelrc-error"} | ${/Error while parsing config - /}
|
||||||
|
${".babelrc.js"} | ${"babelrc-js-error"} | ${/Babelrc threw an error/}
|
||||||
|
${".babelrc.cjs"} | ${"babelrc-cjs-error"} | ${/Babelrc threw an error/}
|
||||||
|
${"package.json"} | ${"pkg-error"} | ${/Error while parsing JSON - /}
|
||||||
|
`("should show helpful errors for $config", ({ dir, error }) => {
|
||||||
|
const filename = fixture("config-files", dir, "src.js");
|
||||||
|
|
||||||
expect(loadOptions({ filename, cwd: path.dirname(filename) })).toEqual({
|
expect(() =>
|
||||||
...getDefaults(),
|
loadOptions({ filename, cwd: path.dirname(filename) }),
|
||||||
filename: filename,
|
).toThrow(error);
|
||||||
cwd: path.dirname(filename),
|
|
||||||
root: path.dirname(filename),
|
|
||||||
comments: true,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should load package.json#babel", () => {
|
|
||||||
const filename = fixture("config-files", "pkg", "src.js");
|
|
||||||
|
|
||||||
expect(loadOptions({ filename, cwd: path.dirname(filename) })).toEqual({
|
|
||||||
...getDefaults(),
|
|
||||||
filename: filename,
|
|
||||||
cwd: path.dirname(filename),
|
|
||||||
root: path.dirname(filename),
|
|
||||||
comments: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should load .babelignore", () => {
|
|
||||||
const filename = fixture("config-files", "babelignore", "src.js");
|
|
||||||
|
|
||||||
expect(loadOptions({ filename, cwd: path.dirname(filename) })).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw if there are both .babelrc and .babelrc.js", () => {
|
|
||||||
const filename = fixture("config-files", "both-babelrc", "src.js");
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
loadOptions({ filename, cwd: path.dirname(filename) }),
|
|
||||||
).toThrow(/Multiple configuration files found/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw if there are both .babelrc and package.json", () => {
|
|
||||||
const filename = fixture("config-files", "pkg-babelrc", "src.js");
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
loadOptions({ filename, cwd: path.dirname(filename) }),
|
|
||||||
).toThrow(/Multiple configuration files found/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw if there are both .babelrc.js and package.json", () => {
|
|
||||||
const filename = fixture("config-files", "pkg-babelrc-js", "src.js");
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
loadOptions({ filename, cwd: path.dirname(filename) }),
|
|
||||||
).toThrow(/Multiple configuration files found/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should ignore package.json without a 'babel' property", () => {
|
|
||||||
const filename = fixture("config-files", "pkg-ignored", "src.js");
|
|
||||||
|
|
||||||
expect(loadOptions({ filename, cwd: path.dirname(filename) })).toEqual({
|
|
||||||
...getDefaults(),
|
|
||||||
filename: filename,
|
|
||||||
cwd: path.dirname(filename),
|
|
||||||
root: path.dirname(filename),
|
|
||||||
comments: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show helpful errors for .babelrc", () => {
|
|
||||||
const filename = fixture("config-files", "babelrc-error", "src.js");
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
loadOptions({ filename, cwd: path.dirname(filename) }),
|
|
||||||
).toThrow(/Error while parsing config - /);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show helpful errors for .babelrc.js", () => {
|
|
||||||
const filename = fixture("config-files", "babelrc-js-error", "src.js");
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
loadOptions({ filename, cwd: path.dirname(filename) }),
|
|
||||||
).toThrow(/Babelrc threw an error/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show helpful errors for package.json", () => {
|
|
||||||
const filename = fixture("config-files", "pkg-error", "src.js");
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
loadOptions({ filename, cwd: path.dirname(filename) }),
|
|
||||||
).toThrow(/Error while parsing JSON - /);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw when `test` presents but `filename` is not passed", () => {
|
it("should throw when `test` presents but `filename` is not passed", () => {
|
||||||
expect(() => loadOptions({ test: /\.ts$/, plugins: [] })).toThrow(
|
expect(() => loadOptions({ test: /\.ts$/, plugins: [] })).toThrow(
|
||||||
/Configuration contains string\/RegExp pattern/,
|
/Configuration contains string\/RegExp pattern/,
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
comments: true,
|
comments: true
|
||||||
};
|
};
|
||||||
3
packages/babel-core/test/fixtures/config/config-files-templates/babel.config.js
vendored
Normal file
3
packages/babel-core/test/fixtures/config/config-files-templates/babel.config.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
comments: true
|
||||||
|
};
|
||||||
3
packages/babel-core/test/fixtures/config/config-files/babelrc-cjs-error/.babelrc.cjs
vendored
Normal file
3
packages/babel-core/test/fixtures/config/config-files/babelrc-cjs-error/.babelrc.cjs
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = function() {
|
||||||
|
throw new Error("Babelrc threw an error");
|
||||||
|
};
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
comments: true,
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
module.exports = {};
|
|
||||||
@ -1 +0,0 @@
|
|||||||
module.exports = {};
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"babel": {}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"babel": {}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user