diff --git a/packages/babel-helper-module-transforms/src/dynamic-import.ts b/packages/babel-helper-module-transforms/src/dynamic-import.ts index df9aedc79ac5..e9c0fae51a22 100644 --- a/packages/babel-helper-module-transforms/src/dynamic-import.ts +++ b/packages/babel-helper-module-transforms/src/dynamic-import.ts @@ -4,6 +4,7 @@ import * as t from "@babel/types"; import template from "@babel/template"; +// TODO(Babel 8): Remove this export function getDynamicImportSource( node: t.CallExpression, ): t.StringLiteral | t.TemplateLiteral { @@ -13,3 +14,49 @@ export function getDynamicImportSource( ? source : (template.expression.ast`\`\${${source}}\`` as t.TemplateLiteral); } + +export function buildDynamicImport( + node: t.CallExpression, + deferToThen: boolean, + wrapWithPromise: boolean, + builder: (specifier: t.Expression) => t.Expression, +): t.Expression { + const [specifier] = node.arguments; + + if ( + t.isStringLiteral(specifier) || + (t.isTemplateLiteral(specifier) && specifier.quasis.length === 0) + ) { + if (deferToThen) { + return template.expression.ast` + Promise.resolve().then(() => ${builder(specifier)}) + `; + } else return builder(specifier); + } + + const specifierToString = t.isTemplateLiteral(specifier) + ? t.identifier("specifier") + : t.templateLiteral( + [t.templateElement({ raw: "" }), t.templateElement({ raw: "" })], + [t.identifier("specifier")], + ); + + if (deferToThen) { + return template.expression.ast` + (specifier => + new Promise(r => r(${specifierToString})) + .then(s => ${builder(t.identifier("s"))}) + )(${specifier}) + `; + } else if (wrapWithPromise) { + return template.expression.ast` + (specifier => + new Promise(r => r(${builder(specifierToString)})) + )(${specifier}) + `; + } else { + return template.expression.ast` + (specifier => ${builder(specifierToString)})(${specifier}) + `; + } +} diff --git a/packages/babel-helper-module-transforms/src/index.ts b/packages/babel-helper-module-transforms/src/index.ts index d939661d6168..fc7f53950f67 100644 --- a/packages/babel-helper-module-transforms/src/index.ts +++ b/packages/babel-helper-module-transforms/src/index.ts @@ -35,7 +35,7 @@ import type { } from "./normalize-and-load-metadata"; import type { NodePath } from "@babel/traverse"; -export { getDynamicImportSource } from "./dynamic-import"; +export { buildDynamicImport, getDynamicImportSource } from "./dynamic-import"; export { default as getModuleName } from "./get-module-name"; export type { PluginOptions } from "./get-module-name"; diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/amd/to-string/input.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/amd/to-string/input.js new file mode 100644 index 000000000000..130d3cafa5cb --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/amd/to-string/input.js @@ -0,0 +1 @@ +import(2); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/amd/to-string/output.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/amd/to-string/output.js new file mode 100644 index 000000000000..f8ffd28bbf17 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/amd/to-string/output.js @@ -0,0 +1,3 @@ +define(["require"], function (_require) { + (specifier => new Promise((_resolve, _reject) => _require([`${specifier}`], imported => _resolve(babelHelpers.interopRequireWildcard(imported)), _reject)))(2); +}); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-template-literal/1.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-template-literal/1.js new file mode 100644 index 000000000000..bd816eaba4ca --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-template-literal/1.js @@ -0,0 +1 @@ +module.exports = 1; diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-template-literal/exec.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-template-literal/exec.js new file mode 100644 index 000000000000..2f3a13932e5c --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-template-literal/exec.js @@ -0,0 +1,3 @@ +let x = 1; +return expect(import(`./${x}.js`)) + .resolves.toHaveProperty("default", 1); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-template-literal/options.json b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-template-literal/options.json new file mode 100644 index 000000000000..eeebc1a57738 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-template-literal/options.json @@ -0,0 +1,5 @@ +{ + "parserOpts": { + "allowReturnOutsideFunction": true + } +} diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-throw/exec.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-throw/exec.js new file mode 100644 index 000000000000..6a80723db1f7 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-throw/exec.js @@ -0,0 +1,11 @@ +expect(() => { + function f() { throw "should throw"; } + import(f()); +}).toThrow("should throw"); + +expect(() => { + const a = { + get x() { throw "should throw"; }, + }; + import(a.x); +}).toThrow("should throw"); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-primitive/exec.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-primitive/exec.js new file mode 100644 index 000000000000..09e8bf06d4e5 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-primitive/exec.js @@ -0,0 +1,6 @@ +return expect(import({ + [Symbol.toPrimitive](hint) { + if (hint === "string") return "./foo.js"; + return null; + } +})).resolves.toHaveProperty("default", "foo"); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-primitive/foo.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-primitive/foo.js new file mode 100644 index 000000000000..e7134e7006d9 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-primitive/foo.js @@ -0,0 +1 @@ +module.exports = "foo"; diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-primitive/options.json b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-primitive/options.json new file mode 100644 index 000000000000..eeebc1a57738 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-primitive/options.json @@ -0,0 +1,5 @@ +{ + "parserOpts": { + "allowReturnOutsideFunction": true + } +} diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-order/1.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-order/1.js new file mode 100644 index 000000000000..bd816eaba4ca --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-order/1.js @@ -0,0 +1 @@ +module.exports = 1; diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-order/exec.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-order/exec.js new file mode 100644 index 000000000000..3e6bffbdfbb4 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-order/exec.js @@ -0,0 +1,7 @@ +let i = 0; +const promise = expect(import({ toString: () => `./${++i}.js` })) + .resolves.toHaveProperty("default", 1); +expect(i).toBe(1); +++i; +expect(i).toBe(2); +return promise; diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-order/options.json b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-order/options.json new file mode 100644 index 000000000000..eeebc1a57738 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-order/options.json @@ -0,0 +1,5 @@ +{ + "parserOpts": { + "allowReturnOutsideFunction": true + } +} diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-throw/exec.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-throw/exec.js new file mode 100644 index 000000000000..a4a59a3fec25 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-throw/exec.js @@ -0,0 +1,2 @@ +return expect(import({ toString: () => { throw "toString failed"; } })) + .rejects.toBe("toString failed"); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-throw/options.json b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-throw/options.json new file mode 100644 index 000000000000..eeebc1a57738 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/exec-to-string-throw/options.json @@ -0,0 +1,5 @@ +{ + "parserOpts": { + "allowReturnOutsideFunction": true + } +} diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/template-literal/input.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/template-literal/input.js new file mode 100644 index 000000000000..ebb8aaa09cd3 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/template-literal/input.js @@ -0,0 +1,2 @@ +const x = 1; +import(`./${x}.js`); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/template-literal/output.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/template-literal/output.js new file mode 100644 index 000000000000..6e90671b2706 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/template-literal/output.js @@ -0,0 +1,2 @@ +const x = 1; +(specifier => new Promise(r => r(specifier)).then(s => babelHelpers.interopRequireWildcard(require(s))))(`./${x}.js`); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/to-string/output.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/to-string/output.js index 7ec1d85d66b6..3618cfbe2b92 100644 --- a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/to-string/output.js +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/to-string/output.js @@ -1 +1 @@ -Promise.resolve(`${2}`).then(s => babelHelpers.interopRequireWildcard(require(s))); +(specifier => new Promise(r => r(`${specifier}`)).then(s => babelHelpers.interopRequireWildcard(require(s))))(2); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/systemjs/to-string/input.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/systemjs/to-string/input.js new file mode 100644 index 000000000000..130d3cafa5cb --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/systemjs/to-string/input.js @@ -0,0 +1 @@ +import(2); diff --git a/packages/babel-plugin-proposal-dynamic-import/test/fixtures/systemjs/to-string/output.js b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/systemjs/to-string/output.js new file mode 100644 index 000000000000..6af1121eb491 --- /dev/null +++ b/packages/babel-plugin-proposal-dynamic-import/test/fixtures/systemjs/to-string/output.js @@ -0,0 +1,10 @@ +System.register([], function (_export, _context) { + "use strict"; + + return { + setters: [], + execute: function () { + (specifier => new Promise(r => r(_context.import(`${specifier}`))))(2); + } + }; +}); diff --git a/packages/babel-plugin-transform-modules-amd/src/index.ts b/packages/babel-plugin-transform-modules-amd/src/index.ts index 10cc77de1e1f..b46dff0e132f 100644 --- a/packages/babel-plugin-transform-modules-amd/src/index.ts +++ b/packages/babel-plugin-transform-modules-amd/src/index.ts @@ -1,5 +1,6 @@ import { declare } from "@babel/helper-plugin-utils"; import { + buildDynamicImport, isModule, rewriteModuleStatementsAndPrepareHeader, type RewriteModuleStatementsAndPrepareHeaderOptions, @@ -9,7 +10,6 @@ import { ensureStatementsHoisted, wrapInterop, getModuleName, - getDynamicImportSource, } from "@babel/helper-module-transforms"; import { template, types as t } from "@babel/core"; import type { PluginOptions } from "@babel/helper-module-transforms"; @@ -99,14 +99,20 @@ export default declare((api, options: Options) => { if (!noInterop) result = wrapInterop(path, result, "namespace"); path.replaceWith( - template.expression.ast` - new Promise((${resolveId}, ${rejectId}) => - ${requireId}( - [${getDynamicImportSource(path.node)}], - imported => ${t.cloneNode(resolveId)}(${result}), - ${t.cloneNode(rejectId)} + buildDynamicImport( + path.node, + false, + false, + specifier => template.expression.ast` + new Promise((${resolveId}, ${rejectId}) => + ${requireId}( + [${specifier}], + imported => ${t.cloneNode(resolveId)}(${result}), + ${t.cloneNode(rejectId)} + ) ) - )`, + `, + ), ); }, diff --git a/packages/babel-plugin-transform-modules-commonjs/src/dynamic-import.ts b/packages/babel-plugin-transform-modules-commonjs/src/dynamic-import.ts index 0faf9f5099a0..bb132d85a1ab 100644 --- a/packages/babel-plugin-transform-modules-commonjs/src/dynamic-import.ts +++ b/packages/babel-plugin-transform-modules-commonjs/src/dynamic-import.ts @@ -3,7 +3,7 @@ import type { NodePath } from "@babel/traverse"; import { types as t, template, type File } from "@babel/core"; -import { getDynamicImportSource } from "@babel/helper-module-transforms"; +import { buildDynamicImport } from "@babel/helper-module-transforms"; const requireNoInterop = (source: t.Expression) => template.expression.ast`require(${source})`; @@ -20,19 +20,9 @@ export function transformDynamicImport( ) { const buildRequire = noInterop ? requireNoInterop : requireInterop; - const source = getDynamicImportSource(path.node); - - const replacement = - t.isStringLiteral(source) || - (t.isTemplateLiteral(source) && source.quasis.length === 0) - ? template.expression.ast` - Promise.resolve().then(() => ${buildRequire(source, file)}) - ` - : template.expression.ast` - Promise.resolve(${source}).then( - s => ${buildRequire(t.identifier("s"), file)} - ) - `; - - path.replaceWith(replacement); + path.replaceWith( + buildDynamicImport(path.node, true, false, specifier => + buildRequire(specifier, file), + ), + ); } diff --git a/packages/babel-plugin-transform-modules-systemjs/src/index.ts b/packages/babel-plugin-transform-modules-systemjs/src/index.ts index c6b935534c74..7e471c754c32 100644 --- a/packages/babel-plugin-transform-modules-systemjs/src/index.ts +++ b/packages/babel-plugin-transform-modules-systemjs/src/index.ts @@ -2,9 +2,9 @@ import { declare } from "@babel/helper-plugin-utils"; import hoistVariables from "@babel/helper-hoist-variables"; import { template, types as t } from "@babel/core"; import { - rewriteThis, + buildDynamicImport, getModuleName, - getDynamicImportSource, + rewriteThis, } from "@babel/helper-module-transforms"; import type { PluginOptions } from "@babel/helper-module-transforms"; import { isIdentifierName } from "@babel/helper-validator-identifier"; @@ -274,12 +274,14 @@ export default declare((api, options: Options) => { } path.replaceWith( - t.callExpression( - t.memberExpression( - t.identifier(state.contextIdent), - t.identifier("import"), + buildDynamicImport(path.node, false, true, specifier => + t.callExpression( + t.memberExpression( + t.identifier(state.contextIdent), + t.identifier("import"), + ), + [specifier], ), - [getDynamicImportSource(path.node)], ), ); }