Skip to content

Commit 9af21eb

Browse files
authored
Transform nested dynamic imports (#18998)
* Fix nested dynamic imports when targeting es6 * Fixup nested dynamic imports when targeting downlevel * Remove duplicated expressions in UMD emit * Code review feedback, clone arg if need be * More CR feedback, apply user quotemark styles * Remove blank lines * Use behavior of visitEachChild instead of enw codepath, add new test, use createLiteral to retain quotemarks * Set lib flag for test
1 parent 29ed92e commit 9af21eb

File tree

61 files changed

+1254
-22
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1254
-22
lines changed

src/compiler/transformers/generators.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,7 @@ namespace ts {
11121112
}
11131113

11141114
function visitCallExpression(node: CallExpression) {
1115-
if (forEach(node.arguments, containsYield)) {
1115+
if (!isImportCall(node) && forEach(node.arguments, containsYield)) {
11161116
// [source]
11171117
// a.b(1, yield, 2);
11181118
//
@@ -1123,7 +1123,6 @@ namespace ts {
11231123
// .yield resumeLabel
11241124
// .mark resumeLabel
11251125
// _b.apply(_a, _c.concat([%sent%, 2]));
1126-
11271126
const { target, thisArg } = createCallBinding(node.expression, hoistVariableDeclaration, languageVersion, /*cacheIdentifiers*/ true);
11281127
return setOriginalNode(
11291128
createFunctionApply(

src/compiler/transformers/module/module.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ namespace ts {
2121

2222
const {
2323
startLexicalEnvironment,
24-
endLexicalEnvironment
24+
endLexicalEnvironment,
25+
hoistVariableDeclaration
2526
} = context;
2627

2728
const compilerOptions = context.getCompilerOptions();
@@ -519,18 +520,20 @@ namespace ts {
519520
}
520521

521522
function visitImportCallExpression(node: ImportCall): Expression {
523+
const argument = visitNode(firstOrUndefined(node.arguments), importCallExpressionVisitor);
524+
const containsLexicalThis = !!(node.transformFlags & TransformFlags.ContainsLexicalThis);
522525
switch (compilerOptions.module) {
523526
case ModuleKind.AMD:
524-
return transformImportCallExpressionAMD(node);
527+
return createImportCallExpressionAMD(argument, containsLexicalThis);
525528
case ModuleKind.UMD:
526-
return transformImportCallExpressionUMD(node);
529+
return createImportCallExpressionUMD(argument, containsLexicalThis);
527530
case ModuleKind.CommonJS:
528531
default:
529-
return transformImportCallExpressionCommonJS(node);
532+
return createImportCallExpressionCommonJS(argument, containsLexicalThis);
530533
}
531534
}
532535

533-
function transformImportCallExpressionUMD(node: ImportCall): Expression {
536+
function createImportCallExpressionUMD(arg: Expression | undefined, containsLexicalThis: boolean): Expression {
534537
// (function (factory) {
535538
// ... (regular UMD)
536539
// }
@@ -545,14 +548,25 @@ namespace ts {
545548
// : new Promise(function (_a, _b) { require([x], _a, _b); }); /*Amd Require*/
546549
// });
547550
needUMDDynamicImportHelper = true;
548-
return createConditional(
549-
/*condition*/ createIdentifier("__syncRequire"),
550-
/*whenTrue*/ transformImportCallExpressionCommonJS(node),
551-
/*whenFalse*/ transformImportCallExpressionAMD(node)
552-
);
551+
if (isSimpleCopiableExpression(arg)) {
552+
const argClone = isGeneratedIdentifier(arg) ? arg : isStringLiteral(arg) ? createLiteral(arg) : setEmitFlags(setTextRange(getSynthesizedClone(arg), arg), EmitFlags.NoComments);
553+
return createConditional(
554+
/*condition*/ createIdentifier("__syncRequire"),
555+
/*whenTrue*/ createImportCallExpressionCommonJS(arg, containsLexicalThis),
556+
/*whenFalse*/ createImportCallExpressionAMD(argClone, containsLexicalThis)
557+
);
558+
}
559+
else {
560+
const temp = createTempVariable(hoistVariableDeclaration);
561+
return createComma(createAssignment(temp, arg), createConditional(
562+
/*condition*/ createIdentifier("__syncRequire"),
563+
/*whenTrue*/ createImportCallExpressionCommonJS(temp, containsLexicalThis),
564+
/*whenFalse*/ createImportCallExpressionAMD(temp, containsLexicalThis)
565+
));
566+
}
553567
}
554568

555-
function transformImportCallExpressionAMD(node: ImportCall): Expression {
569+
function createImportCallExpressionAMD(arg: Expression | undefined, containsLexicalThis: boolean): Expression {
556570
// improt("./blah")
557571
// emit as
558572
// define(["require", "exports", "blah"], function (require, exports) {
@@ -570,7 +584,7 @@ namespace ts {
570584
createCall(
571585
createIdentifier("require"),
572586
/*typeArguments*/ undefined,
573-
[createArrayLiteral([firstOrUndefined(node.arguments) || createOmittedExpression()]), resolve, reject]
587+
[createArrayLiteral([arg || createOmittedExpression()]), resolve, reject]
574588
)
575589
)
576590
]);
@@ -598,22 +612,22 @@ namespace ts {
598612
// if there is a lexical 'this' in the import call arguments, ensure we indicate
599613
// that this new function expression indicates it captures 'this' so that the
600614
// es2015 transformer will properly substitute 'this' with '_this'.
601-
if (node.transformFlags & TransformFlags.ContainsLexicalThis) {
615+
if (containsLexicalThis) {
602616
setEmitFlags(func, EmitFlags.CapturesThis);
603617
}
604618
}
605619

606620
return createNew(createIdentifier("Promise"), /*typeArguments*/ undefined, [func]);
607621
}
608622

609-
function transformImportCallExpressionCommonJS(node: ImportCall): Expression {
623+
function createImportCallExpressionCommonJS(arg: Expression | undefined, containsLexicalThis: boolean): Expression {
610624
// import("./blah")
611625
// emit as
612626
// Promise.resolve().then(function () { return require(x); }) /*CommonJs Require*/
613627
// We have to wrap require in then callback so that require is done in asynchronously
614628
// if we simply do require in resolve callback in Promise constructor. We will execute the loading immediately
615629
const promiseResolveCall = createCall(createPropertyAccess(createIdentifier("Promise"), "resolve"), /*typeArguments*/ undefined, /*argumentsArray*/ []);
616-
const requireCall = createCall(createIdentifier("require"), /*typeArguments*/ undefined, node.arguments);
630+
const requireCall = createCall(createIdentifier("require"), /*typeArguments*/ undefined, arg ? [arg] : []);
617631

618632
let func: FunctionExpression | ArrowFunction;
619633
if (languageVersion >= ScriptTarget.ES2015) {
@@ -638,7 +652,7 @@ namespace ts {
638652
// if there is a lexical 'this' in the import call arguments, ensure we indicate
639653
// that this new function expression indicates it captures 'this' so that the
640654
// es2015 transformer will properly substitute 'this' with '_this'.
641-
if (node.transformFlags & TransformFlags.ContainsLexicalThis) {
655+
if (containsLexicalThis) {
642656
setEmitFlags(func, EmitFlags.CapturesThis);
643657
}
644658
}

src/compiler/transformers/module/system.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1495,7 +1495,7 @@ namespace ts {
14951495
createIdentifier("import")
14961496
),
14971497
/*typeArguments*/ undefined,
1498-
node.arguments
1498+
some(node.arguments) ? [visitNode(node.arguments[0], destructuringAndImportCallVisitor)] : []
14991499
);
15001500
}
15011501

src/compiler/transformers/utilities.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,17 @@ namespace ts {
178178
}
179179
return values;
180180
}
181+
182+
/**
183+
* Used in the module transformer to check if an expression is reasonably without sideeffect,
184+
* and thus better to copy into multiple places rather than to cache in a temporary variable
185+
* - this is mostly subjective beyond the requirement that the expression not be sideeffecting
186+
*/
187+
export function isSimpleCopiableExpression(expression: Expression) {
188+
return expression.kind === SyntaxKind.StringLiteral ||
189+
expression.kind === SyntaxKind.NumericLiteral ||
190+
expression.kind === SyntaxKind.NoSubstitutionTemplateLiteral ||
191+
isKeyword(expression.kind) ||
192+
isIdentifier(expression);
193+
}
181194
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//// [asyncImportNestedYield.ts]
2+
async function* foo() {
3+
import((await import(yield "foo")).default);
4+
}
5+
6+
//// [asyncImportNestedYield.js]
7+
var __generator = (this && this.__generator) || function (thisArg, body) {
8+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
9+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
10+
function verb(n) { return function (v) { return step([n, v]); }; }
11+
function step(op) {
12+
if (f) throw new TypeError("Generator is already executing.");
13+
while (_) try {
14+
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
15+
if (y = 0, t) op = [0, t.value];
16+
switch (op[0]) {
17+
case 0: case 1: t = op; break;
18+
case 4: _.label++; return { value: op[1], done: false };
19+
case 5: _.label++; y = op[1]; op = [0]; continue;
20+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
21+
default:
22+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
23+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
24+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
25+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
26+
if (t[2]) _.ops.pop();
27+
_.trys.pop(); continue;
28+
}
29+
op = body.call(thisArg, _);
30+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
31+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
32+
}
33+
};
34+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
35+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
36+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
37+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
38+
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
39+
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
40+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
41+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
42+
function fulfill(value) { resume("next", value); }
43+
function reject(value) { resume("throw", value); }
44+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
45+
};
46+
function foo() {
47+
return __asyncGenerator(this, arguments, function foo_1() {
48+
return __generator(this, function (_a) {
49+
switch (_a.label) {
50+
case 0: return [4 /*yield*/, "foo"];
51+
case 1: return [4 /*yield*/, __await.apply(void 0, [Promise.resolve().then(function () { return require(_a.sent()); })])];
52+
case 2:
53+
Promise.resolve().then(function () { return require((_a.sent())["default"]); });
54+
return [2 /*return*/];
55+
}
56+
});
57+
});
58+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
=== tests/cases/compiler/asyncImportNestedYield.ts ===
2+
async function* foo() {
3+
>foo : Symbol(foo, Decl(asyncImportNestedYield.ts, 0, 0))
4+
5+
import((await import(yield "foo")).default);
6+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
=== tests/cases/compiler/asyncImportNestedYield.ts ===
2+
async function* foo() {
3+
>foo : () => AsyncIterableIterator<"foo">
4+
5+
import((await import(yield "foo")).default);
6+
>import((await import(yield "foo")).default) : Promise<any>
7+
>(await import(yield "foo")).default : any
8+
>(await import(yield "foo")) : any
9+
>await import(yield "foo") : any
10+
>import(yield "foo") : Promise<any>
11+
>yield "foo" : any
12+
>"foo" : "foo"
13+
>default : any
14+
}

tests/baselines/reference/dynamicImportWithNestedThis_es2015.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ c.dynamic();
2929
this._path = './other';
3030
}
3131
dynamic() {
32-
return __syncRequire ? Promise.resolve().then(() => require(this._path)) : new Promise((resolve_1, reject_1) => { require([this._path], resolve_1, reject_1); });
32+
return _a = this._path, __syncRequire ? Promise.resolve().then(() => require(_a)) : new Promise((resolve_1, reject_1) => { require([_a], resolve_1, reject_1); });
33+
var _a;
3334
}
3435
}
3536
const c = new C();

tests/baselines/reference/dynamicImportWithNestedThis_es5.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ c.dynamic();
3030
}
3131
C.prototype.dynamic = function () {
3232
var _this = this;
33-
return __syncRequire ? Promise.resolve().then(function () { return require(_this._path); }) : new Promise(function (resolve_1, reject_1) { require([_this._path], resolve_1, reject_1); });
33+
return _a = this._path, __syncRequire ? Promise.resolve().then(function () { return require(_a); }) : new Promise(function (resolve_1, reject_1) { require([_a], resolve_1, reject_1); });
34+
var _a;
3435
};
3536
return C;
3637
}());

tests/baselines/reference/importCallExpressionGrammarError.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ Promise.resolve().then(() => require(...["PathModule"]));
1616
var p1 = Promise.resolve().then(() => require(...a));
1717
const p2 = Promise.resolve().then(() => require());
1818
const p3 = Promise.resolve().then(() => require());
19-
const p4 = Promise.resolve().then(() => require("pathToModule", "secondModule"));
19+
const p4 = Promise.resolve().then(() => require("pathToModule"));
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [tests/cases/conformance/dynamicImport/importCallExpressionNestedAMD.ts] ////
2+
3+
//// [foo.ts]
4+
export default "./foo";
5+
6+
//// [index.ts]
7+
async function foo() {
8+
return await import((await import("./foo")).default);
9+
}
10+
11+
//// [foo.js]
12+
define(["require", "exports"], function (require, exports) {
13+
"use strict";
14+
Object.defineProperty(exports, "__esModule", { value: true });
15+
exports.default = "./foo";
16+
});
17+
//// [index.js]
18+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
19+
return new (P || (P = Promise))(function (resolve, reject) {
20+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22+
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
23+
step((generator = generator.apply(thisArg, _arguments || [])).next());
24+
});
25+
};
26+
define(["require", "exports"], function (require, exports) {
27+
"use strict";
28+
function foo() {
29+
return __awaiter(this, void 0, void 0, function* () {
30+
return yield new Promise((resolve_1, reject_1) => { require([(yield new Promise((resolve_2, reject_2) => { require(["./foo"], resolve_2, reject_2); })).default], resolve_1, reject_1); });
31+
});
32+
}
33+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/conformance/dynamicImport/foo.ts ===
2+
export default "./foo";
3+
No type information for this code.
4+
No type information for this code.=== tests/cases/conformance/dynamicImport/index.ts ===
5+
async function foo() {
6+
>foo : Symbol(foo, Decl(index.ts, 0, 0))
7+
8+
return await import((await import("./foo")).default);
9+
>(await import("./foo")).default : Symbol(default, Decl(foo.ts, 0, 0))
10+
>"./foo" : Symbol("tests/cases/conformance/dynamicImport/foo", Decl(foo.ts, 0, 0))
11+
>default : Symbol(default, Decl(foo.ts, 0, 0))
12+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== tests/cases/conformance/dynamicImport/foo.ts ===
2+
export default "./foo";
3+
No type information for this code.
4+
No type information for this code.=== tests/cases/conformance/dynamicImport/index.ts ===
5+
async function foo() {
6+
>foo : () => Promise<any>
7+
8+
return await import((await import("./foo")).default);
9+
>await import((await import("./foo")).default) : any
10+
>import((await import("./foo")).default) : Promise<any>
11+
>(await import("./foo")).default : "./foo"
12+
>(await import("./foo")) : typeof "tests/cases/conformance/dynamicImport/foo"
13+
>await import("./foo") : typeof "tests/cases/conformance/dynamicImport/foo"
14+
>import("./foo") : Promise<typeof "tests/cases/conformance/dynamicImport/foo">
15+
>"./foo" : "./foo"
16+
>default : "./foo"
17+
}

0 commit comments

Comments
 (0)