From bea1b0d0af7ebe09df2d98dfbaff561d57e7dfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 4 Nov 2019 00:24:44 +0100 Subject: [PATCH] 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 --- .../src/config/files/configuration.js | 106 +++---- packages/babel-core/test/config-chain.js | 295 +++++++++--------- .../.babelrc} | 2 +- .../.babelrc.cjs} | 0 .../.babelrc.js} | 0 .../babel.config.cjs} | 2 +- .../config-files-templates/babel.config.js | 3 + .../babel.config.json | 0 .../package.json | 0 .../babelrc-cjs-error/.babelrc.cjs | 3 + .../config/config-files/babelrc/.babelrc | 3 - .../config/config-files/both-babelrc/.babelrc | 1 - .../config-files/both-babelrc/.babelrc.js | 1 - .../config-files/pkg-babelrc-js/.babelrc.js | 1 - .../config-files/pkg-babelrc-js/package.json | 3 - .../config/config-files/pkg-babelrc/.babelrc | 1 - .../config-files/pkg-babelrc/package.json | 3 - 17 files changed, 204 insertions(+), 220 deletions(-) rename packages/babel-core/test/fixtures/config/{config-files/babel-config-json/babel.config.json => config-files-templates/.babelrc} (91%) rename packages/babel-core/test/fixtures/config/{config-files/babel-config-js-and-json/babel.config.js => config-files-templates/.babelrc.cjs} (100%) rename packages/babel-core/test/fixtures/config/{config-files/babel-config-js/babel.config.js => config-files-templates/.babelrc.js} (100%) rename packages/babel-core/test/fixtures/config/{config-files/babelrc-js/.babelrc.js => config-files-templates/babel.config.cjs} (55%) create mode 100644 packages/babel-core/test/fixtures/config/config-files-templates/babel.config.js rename packages/babel-core/test/fixtures/config/{config-files/babel-config-js-and-json => config-files-templates}/babel.config.json (100%) rename packages/babel-core/test/fixtures/config/{config-files/pkg => config-files-templates}/package.json (100%) create mode 100644 packages/babel-core/test/fixtures/config/config-files/babelrc-cjs-error/.babelrc.cjs delete mode 100644 packages/babel-core/test/fixtures/config/config-files/babelrc/.babelrc delete mode 100644 packages/babel-core/test/fixtures/config/config-files/both-babelrc/.babelrc delete mode 100644 packages/babel-core/test/fixtures/config/config-files/both-babelrc/.babelrc.js delete mode 100644 packages/babel-core/test/fixtures/config/config-files/pkg-babelrc-js/.babelrc.js delete mode 100644 packages/babel-core/test/fixtures/config/config-files/pkg-babelrc-js/package.json delete mode 100644 packages/babel-core/test/fixtures/config/config-files/pkg-babelrc/.babelrc delete mode 100644 packages/babel-core/test/fixtures/config/config-files/pkg-babelrc/package.json diff --git a/packages/babel-core/src/config/files/configuration.js b/packages/babel-core/src/config/files/configuration.js index adf26e6c68..515171ceba 100644 --- a/packages/babel-core/src/config/files/configuration.js +++ b/packages/babel-core/src/config/files/configuration.js @@ -18,26 +18,22 @@ import type { CallerMetadata } from "../validation/options"; 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 = [ - BABEL_CONFIG_JS_FILENAME, - BABEL_CONFIG_JSON_FILENAME, + "babel.config.js", + "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"; export function findConfigUpwards(rootDir: string): string | null { let dirname = rootDir; while (true) { - if ( - fs.existsSync(path.join(dirname, BABEL_CONFIG_JS_FILENAME)) || - fs.existsSync(path.join(dirname, BABEL_CONFIG_JSON_FILENAME)) - ) { - return dirname; - } + const configFileFound = ROOT_CONFIG_FILENAMES.some(filename => + fs.existsSync(path.join(dirname, filename)), + ); + if (configFileFound) return dirname; const nextDir = path.dirname(dirname); if (dirname === nextDir) break; @@ -59,45 +55,15 @@ export function findRelativeConfig( for (const loc of packageData.directories) { if (!config) { - config = [BABELRC_FILENAME, BABELRC_JS_FILENAME].reduce( - (previousConfig: ConfigFile | null, name) => { - const filepath = path.join(loc, name); - const config = readConfig(filepath, 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 = + config = loadOneConfig( + RELATIVE_CONFIG_FILENAMES, + loc, + envName, + caller, packageData.pkg && packageData.pkg.dirname === loc ? packageToBabelConfig(packageData.pkg) - : 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); - } + : null, + ); } if (!ignore) { @@ -118,24 +84,31 @@ export function findRootConfig( envName: string, caller: CallerMetadata | void, ): ConfigFile | null { - const config = ROOT_CONFIG_FILENAMES.reduce( - (previousConfig: ConfigFile | null, name) => { - const filepath = path.resolve(dirname, name); - const config = readConfig(filepath, envName, caller); + return loadOneConfig(ROOT_CONFIG_FILENAMES, dirname, envName, caller); +} - if (config && previousConfig) { - throw new Error( - `Multiple configuration files found. Please remove one:\n` + - ` - ${path.basename(previousConfig.filepath)}\n` + - ` - ${name}\n` + - `from ${dirname}`, - ); - } +function loadOneConfig( + names: string[], + dirname: string, + envName: string, + caller: CallerMetadata | void, + 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; - }, - null, - ); + if (config && previousConfig) { + throw new Error( + `Multiple configuration files found. Please remove one:\n` + + ` - ${path.basename(previousConfig.filepath)}\n` + + ` - ${name}\n` + + `from ${dirname}`, + ); + } + + return config || previousConfig; + }, previousConfig); if (config) { 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. */ 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 }) : readConfigJSON5(filepath); } diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js index c5f6e2ebf2..078cd91dce 100644 --- a/packages/babel-core/test/config-chain.js +++ b/packages/babel-core/test/config-chain.js @@ -1,8 +1,44 @@ import fs from "fs"; +import os from "os"; import path from "path"; import escapeRegExp from "lodash/escapeRegExp"; 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) { 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("test", () => { describe("single", () => { @@ -944,159 +998,122 @@ describe("buildConfigChain", function() { } }); - it("should load babel.config.json", () => { - const filename = fixture("config-files", "babel-config-json", "src.js"); + describe("root", () => { + 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( - loadOptions({ - filename, - cwd: path.dirname(filename), - }), - ).toEqual({ - ...getDefaults(), - filename: filename, - cwd: path.dirname(filename), - root: path.dirname(filename), - comments: true, - }); - }); + await config(name); - it("should load babel.config.js", () => { - const filename = fixture("config-files", "babel-config-js", "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 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( + loadOptions({ + filename, + cwd, + }), + ).toEqual({ + ...getDefaults(), + filename, + cwd, + root: cwd, + comments: true, + }); + }, ); - expect(() => - loadOptions({ filename, cwd: path.dirname(filename) }), - ).toThrow(/Multiple configuration files found/); + test.each( + pairs(["babel.config.json", "babel.config.js", "babel.config.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 load .babelrc", () => { - const filename = fixture("config-files", "babelrc", "src.js"); + describe("relative", () => { + 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( - loadOptions({ - filename, + await config(name); + + 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), - }), - ).toEqual({ - ...getDefaults(), - filename: filename, - cwd: path.dirname(filename), - root: path.dirname(filename), - comments: true, + root: path.dirname(filename), + comments: true, + }); }); - }); - it("should load .babelrc.js", () => { - const filename = fixture("config-files", "babelrc-js", "src.js"); + test.each` + 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({ - ...getDefaults(), - filename: filename, - cwd: path.dirname(filename), - root: path.dirname(filename), - comments: true, + expect(() => + loadOptions({ filename, cwd: path.dirname(filename) }), + ).toThrow(error); }); }); - 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", () => { expect(() => loadOptions({ test: /\.ts$/, plugins: [] })).toThrow( /Configuration contains string\/RegExp pattern/, diff --git a/packages/babel-core/test/fixtures/config/config-files/babel-config-json/babel.config.json b/packages/babel-core/test/fixtures/config/config-files-templates/.babelrc similarity index 91% rename from packages/babel-core/test/fixtures/config/config-files/babel-config-json/babel.config.json rename to packages/babel-core/test/fixtures/config/config-files-templates/.babelrc index 4b7be6033f..b445e5d663 100644 --- a/packages/babel-core/test/fixtures/config/config-files/babel-config-json/babel.config.json +++ b/packages/babel-core/test/fixtures/config/config-files-templates/.babelrc @@ -1,3 +1,3 @@ { "comments": true -} \ No newline at end of file +} diff --git a/packages/babel-core/test/fixtures/config/config-files/babel-config-js-and-json/babel.config.js b/packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.cjs similarity index 100% rename from packages/babel-core/test/fixtures/config/config-files/babel-config-js-and-json/babel.config.js rename to packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.cjs diff --git a/packages/babel-core/test/fixtures/config/config-files/babel-config-js/babel.config.js b/packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.js similarity index 100% rename from packages/babel-core/test/fixtures/config/config-files/babel-config-js/babel.config.js rename to packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.js diff --git a/packages/babel-core/test/fixtures/config/config-files/babelrc-js/.babelrc.js b/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.cjs similarity index 55% rename from packages/babel-core/test/fixtures/config/config-files/babelrc-js/.babelrc.js rename to packages/babel-core/test/fixtures/config/config-files-templates/babel.config.cjs index 37622f0ad3..409f4a98ac 100644 --- a/packages/babel-core/test/fixtures/config/config-files/babelrc-js/.babelrc.js +++ b/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.cjs @@ -1,3 +1,3 @@ module.exports = { - comments: true, + comments: true }; diff --git a/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.js b/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.js new file mode 100644 index 0000000000..409f4a98ac --- /dev/null +++ b/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + comments: true +}; diff --git a/packages/babel-core/test/fixtures/config/config-files/babel-config-js-and-json/babel.config.json b/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.json similarity index 100% rename from packages/babel-core/test/fixtures/config/config-files/babel-config-js-and-json/babel.config.json rename to packages/babel-core/test/fixtures/config/config-files-templates/babel.config.json diff --git a/packages/babel-core/test/fixtures/config/config-files/pkg/package.json b/packages/babel-core/test/fixtures/config/config-files-templates/package.json similarity index 100% rename from packages/babel-core/test/fixtures/config/config-files/pkg/package.json rename to packages/babel-core/test/fixtures/config/config-files-templates/package.json diff --git a/packages/babel-core/test/fixtures/config/config-files/babelrc-cjs-error/.babelrc.cjs b/packages/babel-core/test/fixtures/config/config-files/babelrc-cjs-error/.babelrc.cjs new file mode 100644 index 0000000000..accbe7c461 --- /dev/null +++ b/packages/babel-core/test/fixtures/config/config-files/babelrc-cjs-error/.babelrc.cjs @@ -0,0 +1,3 @@ +module.exports = function() { + throw new Error("Babelrc threw an error"); +}; diff --git a/packages/babel-core/test/fixtures/config/config-files/babelrc/.babelrc b/packages/babel-core/test/fixtures/config/config-files/babelrc/.babelrc deleted file mode 100644 index 2f6b10872c..0000000000 --- a/packages/babel-core/test/fixtures/config/config-files/babelrc/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - comments: true, -} diff --git a/packages/babel-core/test/fixtures/config/config-files/both-babelrc/.babelrc b/packages/babel-core/test/fixtures/config/config-files/both-babelrc/.babelrc deleted file mode 100644 index 0967ef424b..0000000000 --- a/packages/babel-core/test/fixtures/config/config-files/both-babelrc/.babelrc +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/packages/babel-core/test/fixtures/config/config-files/both-babelrc/.babelrc.js b/packages/babel-core/test/fixtures/config/config-files/both-babelrc/.babelrc.js deleted file mode 100644 index f053ebf797..0000000000 --- a/packages/babel-core/test/fixtures/config/config-files/both-babelrc/.babelrc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc-js/.babelrc.js b/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc-js/.babelrc.js deleted file mode 100644 index f053ebf797..0000000000 --- a/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc-js/.babelrc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc-js/package.json b/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc-js/package.json deleted file mode 100644 index e25be3ecd2..0000000000 --- a/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc-js/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "babel": {} -} diff --git a/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc/.babelrc b/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc/.babelrc deleted file mode 100644 index 0967ef424b..0000000000 --- a/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc/.babelrc +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc/package.json b/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc/package.json deleted file mode 100644 index e25be3ecd2..0000000000 --- a/packages/babel-core/test/fixtures/config/config-files/pkg-babelrc/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "babel": {} -}