diff --git a/babel.config.js b/babel.config.js index 1ec169604ab5..d25af01ef093 100644 --- a/babel.config.js +++ b/babel.config.js @@ -12,6 +12,8 @@ module.exports = function(api) { }; const envOpts = Object.assign({}, envOptsNoTargets); + const compileDynamicImport = env === "test" || env === "development"; + let convertESM = true; let ignoreLib = true; let includeRegeneratorRuntime = false; @@ -106,11 +108,10 @@ module.exports = function(api) { ["@babel/plugin-proposal-optional-chaining", { loose: true }], ["@babel/plugin-proposal-nullish-coalescing-operator", { loose: true }], + compileDynamicImport ? dynamicImportUrlToPath : null, + compileDynamicImport ? "@babel/plugin-proposal-dynamic-import" : 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), overrides: [ { @@ -152,3 +153,45 @@ module.exports = function(api) { return config; }; + +// !!! WARNING !!! Hacks are coming + +// import() uses file:// URLs for absolute imports, while require() uses +// file paths. +// Since this isn't handled by @babel/plugin-transform-modules-commonjs, +// we must handle it here. +// However, fileURLToPath is only supported starting from Node.js 10. +// In older versions, we can remove the pathToFileURL call so that it keeps +// the original absolute path. +// NOTE: This plugin must run before @babel/plugin-transform-modules-commonjs, +// and assumes that the target is the current node version. +function dynamicImportUrlToPath({ template }) { + const currentNodeSupportsURL = !!require("url").pathToFileURL; + + if (currentNodeSupportsURL) { + return { + visitor: { + CallExpression(path) { + if (path.get("callee").isImport()) { + path.get("arguments.0").replaceWith( + template.expression.ast` + require("url").fileURLToPath(${path.node.arguments[0]}) + ` + ); + } + }, + }, + }; + } else { + // TODO: Remove in Babel 8 (it's not needed when using Node 10) + return { + visitor: { + CallExpression(path) { + if (path.get("callee").isIdentifier({ name: "pathToFileURL" })) { + path.replaceWith(path.get("arguments.0")); + } + }, + }, + }; + } +} diff --git a/eslint/babel-eslint-parser/test/index.js b/eslint/babel-eslint-parser/test/index.js index 1c14ec3f4f0a..16204fbb9a53 100644 --- a/eslint/babel-eslint-parser/test/index.js +++ b/eslint/babel-eslint-parser/test/index.js @@ -1,4 +1,5 @@ import path from "path"; +import { pathToFileURL } from "url"; import escope from "eslint-scope"; import unpad from "dedent"; import { parseForESLint } from "../src"; @@ -71,7 +72,8 @@ describe("Babel and Espree", () => { const espreePath = require.resolve("espree", { paths: [path.dirname(require.resolve("eslint"))], }); - espree = await import(espreePath); + + espree = await import(pathToFileURL(espreePath)); }); describe("compatibility", () => { diff --git a/packages/babel-core/src/config/files/module-types.js b/packages/babel-core/src/config/files/module-types.js index 2cffeb613d84..4c761597f9f1 100644 --- a/packages/babel-core/src/config/files/module-types.js +++ b/packages/babel-core/src/config/files/module-types.js @@ -1,6 +1,7 @@ import { isAsync, waitFor } from "../../gensync-utils/async"; import type { Handler } from "gensync"; import path from "path"; +import { pathToFileURL } from "url"; let import_; try { @@ -55,6 +56,8 @@ async function loadMjsDefault(filepath: string) { ); } - const module = await import_(filepath); + // import() expects URLs, not file paths. + // https://github.com/nodejs/node/issues/31710 + const module = await import_(pathToFileURL(filepath)); return module.default; }