New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for babel.config.mjs and .babelrc.mjs #10903
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,57 @@ | ||||||||||||
import { isAsync, waitFor } from "../../gensync-utils/async"; | ||||||||||||
import type { Handler } from "gensync"; | ||||||||||||
|
||||||||||||
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(path: string): "cjs" | "mjs" | "unknown" { | ||||||||||||
switch (path.slice(-4)) { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth using |
||||||||||||
case ".cjs": | ||||||||||||
return "cjs"; | ||||||||||||
case ".mjs": | ||||||||||||
return "mjs"; | ||||||||||||
default: | ||||||||||||
return "unknown"; | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
function loadCjsDefault(filepath: string) { | ||||||||||||
const module = (require(filepath): mixed); | ||||||||||||
return module?.__esModule ? module.default || undefined : module; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure; I only moved this code here from babel/packages/babel-core/src/config/files/configuration.js Lines 181 to 185 in 5b907e9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe in case someone exports There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright, we should investigate this in later PRs, or just remove it in Babel 8 because it looks suspicious. |
||||||||||||
} | ||||||||||||
|
||||||||||||
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; | ||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ import fs from "fs"; | |
import os from "os"; | ||
import path from "path"; | ||
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 | ||
// node 8+ | ||
|
@@ -44,10 +44,11 @@ function fixture(...args) { | |
} | ||
|
||
function loadOptions(opts) { | ||
return loadOptionsOrig({ | ||
cwd: __dirname, | ||
...opts, | ||
}); | ||
return babel.loadOptions({ cwd: __dirname, ...opts }); | ||
} | ||
|
||
function loadOptionsAsync(opts) { | ||
return babel.loadOptionsAsync({ cwd: __dirname, ...opts }); | ||
} | ||
|
||
function pairs(items) { | ||
|
@@ -1000,21 +1001,16 @@ describe("buildConfigChain", function() { | |
|
||
describe("root", () => { | ||
test.each(["babel.config.json", "babel.config.js", "babel.config.cjs"])( | ||
"should load %s", | ||
"should load %s synchronously", | ||
async name => { | ||
const { cwd, tmp, config } = await getTemp( | ||
`babel-test-load-config-${name}`, | ||
`babel-test-load-config-sync-${name}`, | ||
); | ||
const filename = tmp("src.js"); | ||
|
||
await config(name); | ||
|
||
expect( | ||
loadOptions({ | ||
filename, | ||
cwd, | ||
}), | ||
).toEqual({ | ||
expect(loadOptions({ filename, cwd })).toEqual({ | ||
...getDefaults(), | ||
filename, | ||
cwd, | ||
|
@@ -1024,24 +1020,64 @@ 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/, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't use it because the error contains the path of the file in the tmp folder, which might change. |
||
); | ||
}); | ||
|
||
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( | ||
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) => { | ||
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/, | ||
); | ||
await expect( | ||
loadOptionsAsync({ filename: tmp("src.js"), cwd }), | ||
).rejects.toThrow(/Multiple configuration files found/); | ||
}); | ||
}); | ||
|
||
describe("relative", () => { | ||
test.each(["package.json", ".babelrc", ".babelrc.js", ".babelrc.cjs"])( | ||
"should load %s", | ||
"should load %s synchronously", | ||
async name => { | ||
const { cwd, tmp, config } = await getTemp( | ||
`babel-test-load-config-${name}`, | ||
|
@@ -1050,12 +1086,7 @@ describe("buildConfigChain", function() { | |
|
||
await config(name); | ||
|
||
expect( | ||
loadOptions({ | ||
filename, | ||
cwd, | ||
}), | ||
).toEqual({ | ||
expect(loadOptions({ filename, cwd })).toEqual({ | ||
...getDefaults(), | ||
filename, | ||
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", () => { | ||
const filename = fixture("config-files", "babelignore", "src.js"); | ||
|
||
|
@@ -1074,17 +1141,23 @@ describe("buildConfigChain", function() { | |
}); | ||
|
||
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) => { | ||
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/, | ||
); | ||
await expect( | ||
loadOptionsAsync({ filename: tmp("src.js"), cwd }), | ||
).rejects.toThrow(/Multiple configuration files found/); | ||
}); | ||
|
||
it("should ignore package.json without a 'babel' property", () => { | ||
|
@@ -1104,13 +1177,14 @@ describe("buildConfigChain", function() { | |
${".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/} | ||
${".babelrc.mjs"} | ${"babelrc-mjs-error"} | ${/Babelrc threw an error/} | ||
${"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"); | ||
|
||
expect(() => | ||
loadOptions({ filename, cwd: path.dirname(filename) }), | ||
).toThrow(error); | ||
await expect( | ||
loadOptionsAsync({ filename, cwd: path.dirname(filename) }), | ||
).rejects.toThrow(error); | ||
}); | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hate this, but this is the best way we have so far to test it 😢