From 6a0e909c13d00cf910b5574c94ceb77021a8b1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Wed, 18 Nov 2020 16:02:02 +0100 Subject: [PATCH] Don't compile `import()` in development (#12288) --- babel.config.js | 49 +---- eslint/babel-eslint-parser/test/index.js | 3 +- package.json | 2 +- packages/babel-core/test/config-chain.js | 199 ++++++++++-------- .../.babelrc.mjs | 3 - .../babel.config.mjs | 3 - .../config-files-templates/.babelrc.mjs | 11 +- .../config-files-templates/babel.config.mjs | 11 +- .../babelrc-mjs-error/.babelrc.mjs | 9 +- yarn.lock | 10 +- 10 files changed, 127 insertions(+), 173 deletions(-) delete mode 100644 packages/babel-core/test/fixtures/config/config-files-templates-prepublish/.babelrc.mjs delete mode 100644 packages/babel-core/test/fixtures/config/config-files-templates-prepublish/babel.config.mjs diff --git a/babel.config.js b/babel.config.js index 1716227855de..e1b84525c139 100644 --- a/babel.config.js +++ b/babel.config.js @@ -14,12 +14,10 @@ module.exports = function (api) { const envOptsNoTargets = { loose: true, shippedProposals: true, + modules: false, }; const envOpts = Object.assign({}, envOptsNoTargets); - const compileDynamicImport = - env === "test" || env === "development" || env === "test-legacy"; - let convertESM = true; let ignoreLib = true; let includeRegeneratorRuntime = false; @@ -115,9 +113,8 @@ module.exports = function (api) { "@babel/proposal-object-rest-spread", { useBuiltIns: true, loose: true }, ], - compileDynamicImport ? dynamicImportUrlToPath : null, - compileDynamicImport ? "@babel/plugin-proposal-dynamic-import" : null, + convertESM ? "@babel/proposal-export-namespace-from" : null, convertESM ? "@babel/transform-modules-commonjs" : null, ].filter(Boolean), overrides: [ @@ -163,45 +160,3 @@ 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, env }) { - const currentNodeSupportsURL = - !!require("url").pathToFileURL && env() !== "test-legacy"; // test-legacy is run on legacy node versions without pathToFileURL support - 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 5259f1667ec0..60868edf3003 100644 --- a/eslint/babel-eslint-parser/test/index.js +++ b/eslint/babel-eslint-parser/test/index.js @@ -1,5 +1,4 @@ import path from "path"; -import { pathToFileURL } from "url"; import escope from "eslint-scope"; import unpad from "dedent"; import { parseForESLint } from "../src"; @@ -80,7 +79,7 @@ describe("Babel and Espree", () => { paths: [path.dirname(require.resolve("eslint"))], }); - espree = await import(pathToFileURL(espreePath)); + espree = require(espreePath); }); describe("compatibility", () => { diff --git a/package.json b/package.json index 33bfe8f7da16..941d01e9836f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@babel/eslint-parser": "workspace:*", "@babel/eslint-plugin-development": "workspace:*", "@babel/eslint-plugin-development-internal": "workspace:*", - "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-export-namespace-from": "^7.12.1", "@babel/plugin-proposal-object-rest-spread": "^7.11.0", "@babel/plugin-transform-for-of": "^7.10.4", "@babel/plugin-transform-modules-commonjs": "^7.10.4", diff --git a/packages/babel-core/test/config-chain.js b/packages/babel-core/test/config-chain.js index 167dc1e3bb91..8de2bc99d086 100644 --- a/packages/babel-core/test/config-chain.js +++ b/packages/babel-core/test/config-chain.js @@ -6,6 +6,28 @@ import util from "util"; import escapeRegExp from "lodash/escapeRegExp"; import * as babel from "../lib"; +// "minNodeVersion": "10.0.0" <-- For Ctrl+F when dropping node 10 +const supportsESM = parseInt(process.versions.node) >= 12; + +const isMJS = file => path.extname(file) === ".mjs"; + +const skipUnsupportedESM = (esm, name) => { + if (esm && !supportsESM) { + console.warn( + `Skipping "${name}" because native ECMAScript modules are not supported.`, + ); + return true; + } + // This can be removed when loadOptionsAsyncInSpawedProcess is removed. + if (esm && process.platform === "win32") { + console.warn( + `Skipping "${name}" because the ESM runner cannot be spawned on Windows.`, + ); + return true; + } + return false; +}; + // TODO: In Babel 8, we can directly uses fs.promises which is supported by // node 8+ const pfs = @@ -49,15 +71,19 @@ function loadOptions(opts) { return babel.loadOptions({ cwd: __dirname, ...opts }); } -function loadOptionsAsync(opts) { - return babel.loadOptionsAsync({ cwd: __dirname, ...opts }); +function loadOptionsAsync({ filename, cwd = __dirname }, mjs) { + if (mjs) { + // import() crashes with jest + return loadOptionsAsyncInSpawedProcess({ filename, cwd }); + } + + return babel.loadOptionsAsync({ filename, cwd }); } +// !!!! hack is coming !!!! +// Remove this function when https://github.com/nodejs/node/issues/35889 is resolved. +// Jest supports dynamic import(), but Node.js segfaults when using it in our tests. async function loadOptionsAsyncInSpawedProcess({ filename, cwd }) { - // !!!! hack is coming !!!! - // todo(Babel 8): remove this section when https://github.com/facebook/jest/issues/9430 is resolved - // We don't check process.versions.node here, it will fail if node does not support esm - // please publish Babel on a modernized node :) const { stdout, stderr } = await util.promisify(cp.execFile)( require.resolve("./fixtures/babel-load-options-async.mjs"), // pass `cwd` as params as `process.cwd()` will normalize `cwd` on macOS @@ -67,7 +93,10 @@ async function loadOptionsAsyncInSpawedProcess({ filename, cwd }) { env: process.env, }, ); - if (stderr) { + + const EXPERIMENTAL_WARNING = /\(node:\d+\) ExperimentalWarning: The ESM module loader is experimental\./; + + if (stderr.replace(EXPERIMENTAL_WARNING, "").trim()) { throw new Error( "error is thrown in babel-load-options-async.mjs: stdout\n" + stdout + @@ -88,10 +117,11 @@ function pairs(items) { return pairs; } -async function getTemp(name, fixtureFolder = "config-files-templates") { +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(fixtureFolder, name), tmp(name)); + const config = name => + pfs.copyFile(fixture("config-files-templates", name), tmp(name)); return { cwd, tmp, config }; } @@ -1060,16 +1090,17 @@ describe("buildConfigChain", function () { ); }); - test.each( - [ - "babel.config.json", - "babel.config.js", - "babel.config.cjs", - // We can't transpile import() while publishing, and it isn't supported - // by jest. - process.env.IS_PUBLISH ? "" : "babel.config.mjs", - ].filter(Boolean), - )("should load %s asynchronously", async name => { + test.each([ + "babel.config.json", + "babel.config.js", + "babel.config.cjs", + "babel.config.mjs", + ])("should load %s asynchronously", async name => { + const esm = isMJS(name); + if (skipUnsupportedESM(esm, `should load ${name} asynchronously`)) { + return; + } + const { cwd, tmp, config } = await getTemp( `babel-test-load-config-async-${name}`, ); @@ -1077,13 +1108,15 @@ describe("buildConfigChain", function () { await config(name); - expect(await loadOptionsAsync({ filename, cwd })).toEqual({ - ...getDefaults(), - filename, - cwd, - root: cwd, - comments: true, - }); + await expect(loadOptionsAsync({ filename, cwd }, esm)).resolves.toEqual( + { + ...getDefaults(), + filename, + cwd, + root: cwd, + comments: true, + }, + ); }); test.each( @@ -1094,48 +1127,26 @@ describe("buildConfigChain", function () { "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}`, - ); - - // We can't transpile import() while publishing, and it isn't supported - // by jest. + const esm = isMJS(name1) || isMJS(name2); if ( - process.env.IS_PUBLISH && - (name1 === "babel.config.mjs" || name2 === "babel.config.mjs") + skipUnsupportedESM( + esm, + `should throw if both ${name1} and ${name2} are used`, + ) ) { return; } + const { cwd, tmp, config } = await getTemp( + `babel-test-dup-config-${name1}-${name2}`, + ); + await Promise.all([config(name1), config(name2)]); await expect( - loadOptionsAsync({ filename: tmp("src.js"), cwd }), + loadOptionsAsync({ filename: tmp("src.js"), cwd }, esm), ).rejects.toThrow(/Multiple configuration files found/); }); - - if (process.env.IS_PUBLISH) { - test.each(["babel.config.mjs", ".babelrc.mjs"])( - "should load %s asynchronously", - async name => { - const { cwd, tmp, config } = await getTemp( - `babel-test-load-config-async-prepublish-${name}`, - "config-files-templates-prepublish", - ); - const filename = tmp("src.js"); - await config(name); - expect( - await loadOptionsAsyncInSpawedProcess({ filename, cwd }), - ).toEqual({ - ...getDefaults(), - filename, - cwd, - root: cwd, - comments: true, - }); - }, - ); - } }); describe("relative", () => { @@ -1181,11 +1192,14 @@ describe("buildConfigChain", function () { ".babelrc", ".babelrc.js", ".babelrc.cjs", - // We can't transpile import() while publishing, and it isn't supported - // by jest. - process.env.IS_PUBLISH ? "" : "babel.config.mjs", + ".babelrc.mjs", ].filter(Boolean), )("should load %s asynchronously", async name => { + const esm = isMJS(name); + if (skipUnsupportedESM(esm, `should load ${name} asynchronously`)) { + return; + } + const { cwd, tmp, config } = await getTemp( `babel-test-load-config-${name}`, ); @@ -1193,13 +1207,15 @@ describe("buildConfigChain", function () { await config(name); - expect(await loadOptionsAsync({ filename, cwd })).toEqual({ - ...getDefaults(), - filename, - cwd, - root: cwd, - comments: true, - }); + await expect(loadOptionsAsync({ filename, cwd }, esm)).resolves.toEqual( + { + ...getDefaults(), + filename, + cwd, + root: cwd, + comments: true, + }, + ); }); it("should load .babelignore", () => { @@ -1220,23 +1236,24 @@ describe("buildConfigChain", function () { ".babelrc.json", ]), )("should throw if both %s and %s are used", async (name1, name2) => { - const { cwd, tmp, config } = await getTemp( - `babel-test-dup-config-${name1}-${name2}`, - ); - - // We can't transpile import() while publishing, and it isn't supported - // by jest. + const esm = isMJS(name1) || isMJS(name2); if ( - process.env.IS_PUBLISH && - (name1 === ".babelrc.mjs" || name2 === ".babelrc.mjs") + skipUnsupportedESM( + esm, + `should throw if both ${name1} and ${name2} are used`, + ) ) { return; } + const { cwd, tmp, config } = await getTemp( + `babel-test-dup-config-${name1}-${name2}`, + ); + await Promise.all([config(name1), config(name2)]); await expect( - loadOptionsAsync({ filename: tmp("src.js"), cwd }), + loadOptionsAsync({ filename: tmp("src.js"), cwd }, esm), ).rejects.toThrow(/Multiple configuration files found/); }); @@ -1260,17 +1277,23 @@ describe("buildConfigChain", function () { ${".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", async ({ dir, error }) => { - const filename = fixture("config-files", dir, "src.js"); - - // We can't transpile import() while publishing, and it isn't supported - // by jest. - if (process.env.IS_PUBLISH && dir === "babelrc-mjs-error") return; - - await expect( - loadOptionsAsync({ filename, cwd: path.dirname(filename) }), - ).rejects.toThrow(error); - }); + `( + "should show helpful errors for $config", + async ({ config, dir, error }) => { + const esm = isMJS(config); + if ( + skipUnsupportedESM(esm, `should show helpful errors for ${config}`) + ) { + return; + } + + const filename = fixture("config-files", dir, "src.js"); + + await expect( + loadOptionsAsync({ filename, cwd: path.dirname(filename) }, esm), + ).rejects.toThrow(error); + }, + ); it("loadPartialConfig should return a list of files that were extended", () => { const filename = fixture("config-files", "babelrc-extended", "src.js"); diff --git a/packages/babel-core/test/fixtures/config/config-files-templates-prepublish/.babelrc.mjs b/packages/babel-core/test/fixtures/config/config-files-templates-prepublish/.babelrc.mjs deleted file mode 100644 index 813cbb0f7962..000000000000 --- a/packages/babel-core/test/fixtures/config/config-files-templates-prepublish/.babelrc.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export default { - comments: true, -}; diff --git a/packages/babel-core/test/fixtures/config/config-files-templates-prepublish/babel.config.mjs b/packages/babel-core/test/fixtures/config/config-files-templates-prepublish/babel.config.mjs deleted file mode 100644 index 813cbb0f7962..000000000000 --- a/packages/babel-core/test/fixtures/config/config-files-templates-prepublish/babel.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export default { - comments: true, -}; diff --git a/packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.mjs b/packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.mjs index 7fc8bbfea752..813cbb0f7962 100644 --- a/packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.mjs +++ b/packages/babel-core/test/fixtures/config/config-files-templates/.babelrc.mjs @@ -1,8 +1,3 @@ -// Until Jest supports native mjs, we must simulate it 🤷 - -module.exports = new Promise(resolve => resolve({ - default: { - comments: true - } -})); -module.exports.__esModule = true; +export default { + comments: true, +}; diff --git a/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.mjs b/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.mjs index 7fc8bbfea752..813cbb0f7962 100644 --- a/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.mjs +++ b/packages/babel-core/test/fixtures/config/config-files-templates/babel.config.mjs @@ -1,8 +1,3 @@ -// Until Jest supports native mjs, we must simulate it 🤷 - -module.exports = new Promise(resolve => resolve({ - default: { - comments: true - } -})); -module.exports.__esModule = true; +export default { + comments: true, +}; diff --git a/packages/babel-core/test/fixtures/config/config-files/babelrc-mjs-error/.babelrc.mjs b/packages/babel-core/test/fixtures/config/config-files/babelrc-mjs-error/.babelrc.mjs index 5a4fdd06a352..766c4cb890d0 100644 --- a/packages/babel-core/test/fixtures/config/config-files/babelrc-mjs-error/.babelrc.mjs +++ b/packages/babel-core/test/fixtures/config/config-files/babelrc-mjs-error/.babelrc.mjs @@ -1,8 +1 @@ -// 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; +throw new Error("Babelrc threw an error"); diff --git a/yarn.lock b/yarn.lock index 56410d8c9749..79d9298dd813 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1078,15 +1078,15 @@ __metadata: languageName: unknown linkType: soft -"@babel/plugin-proposal-export-namespace-from@npm:^7.12.0": - version: 7.12.0 - resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.12.0" +"@babel/plugin-proposal-export-namespace-from@npm:^7.12.0, @babel/plugin-proposal-export-namespace-from@npm:^7.12.1": + version: 7.12.1 + resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.12.1" dependencies: "@babel/helper-plugin-utils": ^7.10.4 "@babel/plugin-syntax-export-namespace-from": ^7.8.3 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 371d59486a28445f51352b498d22c372de6b8ae59f44a0d62de626498f162372b596430bedbc869e1ea6c0397aa1cb33dc806e7872da6d67625316fb8a019023 + checksum: ae5317ca008cc9eb2890b1f238156fbb990e5030fd1b7f123a5d11d89f8617a867b11db129aeafe51ef3bb4dddc4059e8172ddf99e8cdc42cbfa2a45dce1a16b languageName: node linkType: hard @@ -4814,7 +4814,7 @@ __metadata: "@babel/eslint-parser": "workspace:*" "@babel/eslint-plugin-development": "workspace:*" "@babel/eslint-plugin-development-internal": "workspace:*" - "@babel/plugin-proposal-dynamic-import": ^7.10.4 + "@babel/plugin-proposal-export-namespace-from": ^7.12.1 "@babel/plugin-proposal-object-rest-spread": ^7.11.0 "@babel/plugin-transform-for-of": ^7.10.4 "@babel/plugin-transform-modules-commonjs": ^7.10.4