Skip to content

Commit 808450c

Browse files
Add support for import.defer(...)
1 parent bdf98f5 commit 808450c

20 files changed

+351
-9
lines changed

src/compiler/checker.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -37650,7 +37650,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3765037650
error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_es2020_es2022_esnext_system_node16_node18_or_nodenext);
3765137651
}
3765237652
const file = getSourceFileOfNode(node);
37653-
Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag.");
37653+
Debug.assert(node.name.escapedText === "defer" || !!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag.");
3765437654
return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType;
3765537655
}
3765637656

@@ -41172,8 +41172,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4117241172
case SyntaxKind.ElementAccessExpression:
4117341173
return checkIndexedAccess(node as ElementAccessExpression, checkMode);
4117441174
case SyntaxKind.CallExpression:
41175-
if ((node as CallExpression).expression.kind === SyntaxKind.ImportKeyword) {
41176-
return checkImportCallExpression(node as ImportCall);
41175+
if (isImportCall(node)) {
41176+
return checkImportCallExpression(node);
4117741177
}
4117841178
// falls through
4117941179
case SyntaxKind.NewExpression:
@@ -52612,8 +52612,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5261252612
}
5261352613
break;
5261452614
case SyntaxKind.ImportKeyword:
52615-
if (escapedText !== "meta") {
52616-
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, unescapeLeadingUnderscores(node.name.escapedText), tokenToString(node.keywordToken), "meta");
52615+
if (escapedText !== "meta" && escapedText !== "defer") {
52616+
const suggestion = node.parent.kind === SyntaxKind.CallExpression && (node.parent as CallExpression).expression === node
52617+
? "meta' or 'defer"
52618+
: "meta";
52619+
return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, unescapeLeadingUnderscores(node.name.escapedText), tokenToString(node.keywordToken), suggestion);
5261752620
}
5261852621
break;
5261952622
}
@@ -52902,7 +52905,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5290252905
return grammarErrorOnNode(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled);
5290352906
}
5290452907

52905-
if (moduleKind === ModuleKind.ES2015) {
52908+
if (node.expression.kind === SyntaxKind.MetaProperty) {
52909+
if (moduleKind !== ModuleKind.ESNext) {
52910+
return grammarErrorOnNode(node, Diagnostics.Deferred_imports_are_only_supported_when_the_module_flag_is_set_to_esnext);
52911+
}
52912+
} if (moduleKind === ModuleKind.ES2015) {
5290652913
return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node16_node18_or_nodenext);
5290752914
}
5290852915

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -8445,5 +8445,9 @@
84458445
"Deferred imports are only supported when the '--module' flag is set to 'esnext'.": {
84468446
"category": "Error",
84478447
"code": 18060
8448+
},
8449+
"'import.defer' must be followed by '('": {
8450+
"category": "Error",
8451+
"code": 18061
84488452
}
84498453
}

src/compiler/parser.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -5939,7 +5939,16 @@ namespace Parser {
59395939
nextToken(); // advance past the 'import'
59405940
nextToken(); // advance past the dot
59415941
expression = finishNode(factory.createMetaProperty(SyntaxKind.ImportKeyword, parseIdentifierName()), pos);
5942-
sourceFlags |= NodeFlags.PossiblyContainsImportMeta;
5942+
5943+
if ((expression as MetaProperty).name.escapedText === "defer") {
5944+
if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) {
5945+
sourceFlags |= NodeFlags.PossiblyContainsDynamicImport
5946+
} else {
5947+
parseErrorAt(pos, getNodePos(), Diagnostics.import_defer_must_be_followed_by);
5948+
}
5949+
} else {
5950+
sourceFlags |= NodeFlags.PossiblyContainsImportMeta;
5951+
}
59435952
}
59445953
else {
59455954
expression = parseMemberExpressionOrHigher();

src/compiler/types.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -3101,7 +3101,7 @@ export interface SuperCall extends CallExpression {
31013101
}
31023102

31033103
export interface ImportCall extends CallExpression {
3104-
readonly expression: ImportExpression;
3104+
readonly expression: ImportExpression | ImportDeferProperty;
31053105
}
31063106

31073107
export interface ExpressionWithTypeArguments extends MemberExpression, NodeWithTypeArguments {
@@ -3181,6 +3181,12 @@ export interface ImportMetaProperty extends MetaProperty {
31813181
readonly name: Identifier & { readonly escapedText: __String & "meta"; };
31823182
}
31833183

3184+
/** @internal */
3185+
export interface ImportDeferProperty extends MetaProperty {
3186+
readonly keywordToken: SyntaxKind.ImportKeyword;
3187+
readonly name: Identifier & { readonly escapedText: __String & "defer"; };
3188+
}
3189+
31843190
/// A JSX expression of the form <TagName attrs>...</TagName>
31853191
export interface JsxElement extends PrimaryExpression {
31863192
readonly kind: SyntaxKind.JsxElement;

src/compiler/utilities.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -2632,7 +2632,13 @@ export function isSuperCall(n: Node): n is SuperCall {
26322632

26332633
/** @internal */
26342634
export function isImportCall(n: Node): n is ImportCall {
2635-
return n.kind === SyntaxKind.CallExpression && (n as CallExpression).expression.kind === SyntaxKind.ImportKeyword;
2635+
if (n.kind !== SyntaxKind.CallExpression) return false;
2636+
const e = (n as CallExpression).expression;
2637+
return e.kind === SyntaxKind.ImportKeyword || (
2638+
isMetaProperty(e)
2639+
&& e.keywordToken === SyntaxKind.ImportKeyword
2640+
&& e.name.escapedText === "defer"
2641+
);
26362642
}
26372643

26382644
/** @internal */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error TS2468: Cannot find global value 'Promise'.
2+
b.ts(1,1): error TS2712: A dynamic import call in ES5 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your '--lib' option.
3+
b.ts(1,14): error TS2792: Cannot find module 'a'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?
4+
5+
6+
!!! error TS2468: Cannot find global value 'Promise'.
7+
==== a.ts (0 errors) ====
8+
export function foo() {
9+
console.log("foo from a");
10+
}
11+
12+
==== b.ts (2 errors) ====
13+
import.defer("a").then(ns => {
14+
~~~~~~~~~~~~~~~~~
15+
!!! error TS2712: A dynamic import call in ES5 requires the 'Promise' constructor. Make sure you have a declaration for the 'Promise' constructor or include 'ES2015' in your '--lib' option.
16+
~~~
17+
!!! error TS2792: Cannot find module 'a'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?
18+
ns.foo();
19+
});
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [tests/cases/conformance/importDefer/dynamicImportDefer.ts] ////
2+
3+
//// [a.ts]
4+
export function foo() {
5+
console.log("foo from a");
6+
}
7+
8+
//// [b.ts]
9+
import.defer("a").then(ns => {
10+
ns.foo();
11+
});
12+
13+
14+
//// [a.js]
15+
export function foo() {
16+
console.log("foo from a");
17+
}
18+
//// [b.js]
19+
import.defer("a").then(function (ns) {
20+
ns.foo();
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//// [tests/cases/conformance/importDefer/dynamicImportDefer.ts] ////
2+
3+
=== a.ts ===
4+
export function foo() {
5+
>foo : Symbol(foo, Decl(a.ts, 0, 0))
6+
7+
console.log("foo from a");
8+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
9+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
10+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
11+
}
12+
13+
=== b.ts ===
14+
import.defer("a").then(ns => {
15+
>import.defer("a").then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
16+
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
17+
>ns : Symbol(ns, Decl(b.ts, 0, 23))
18+
19+
ns.foo();
20+
>ns : Symbol(ns, Decl(b.ts, 0, 23))
21+
22+
});
23+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [tests/cases/conformance/importDefer/dynamicImportDefer.ts] ////
2+
3+
=== a.ts ===
4+
export function foo() {
5+
>foo : () => void
6+
> : ^^^^^^^^^^
7+
8+
console.log("foo from a");
9+
>console.log("foo from a") : void
10+
> : ^^^^
11+
>console.log : (...data: any[]) => void
12+
> : ^^^^ ^^ ^^^^^
13+
>console : Console
14+
> : ^^^^^^^
15+
>log : (...data: any[]) => void
16+
> : ^^^^ ^^ ^^^^^
17+
>"foo from a" : "foo from a"
18+
> : ^^^^^^^^^^^^
19+
}
20+
21+
=== b.ts ===
22+
import.defer("a").then(ns => {
23+
>import.defer("a").then(ns => { ns.foo();}) : Promise<void>
24+
> : ^^^^^^^^^^^^^
25+
>import.defer("a").then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
26+
> : ^ ^^^^^^^^ ^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
27+
>import.defer("a") : Promise<any>
28+
> : ^^^^^^^^^^^^
29+
>import.defer : any
30+
> : ^^^
31+
>defer : any
32+
> : ^^^
33+
>"a" : "a"
34+
> : ^^^
35+
>then : <TResult1 = any, TResult2 = never>(onfulfilled?: (value: any) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
36+
> : ^ ^^^^^^^^ ^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
37+
>ns => { ns.foo();} : (ns: any) => void
38+
> : ^ ^^^^^^^^^^^^^^
39+
>ns : any
40+
> : ^^^
41+
42+
ns.foo();
43+
>ns.foo() : any
44+
> : ^^^
45+
>ns.foo : any
46+
> : ^^^
47+
>ns : any
48+
> : ^^^
49+
>foo : any
50+
> : ^^^
51+
52+
});
53+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
b.ts(1,1): error TS18061: 'import.defer' must be followed by '('
2+
b.ts(3,2): error TS18061: 'import.defer' must be followed by '('
3+
4+
5+
==== a.ts (0 errors) ====
6+
export function foo() {
7+
console.log("foo from a");
8+
}
9+
10+
==== b.ts (2 errors) ====
11+
import.defer;
12+
~~~~~~~~~~~~
13+
!!! error TS18061: 'import.defer' must be followed by '('
14+
15+
(import.defer)("a");
16+
~~~~~~~~~~~~
17+
!!! error TS18061: 'import.defer' must be followed by '('
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//// [tests/cases/conformance/importDefer/dynamicImportDeferInvalidStandalone.ts] ////
2+
3+
//// [a.ts]
4+
export function foo() {
5+
console.log("foo from a");
6+
}
7+
8+
//// [b.ts]
9+
import.defer;
10+
11+
(import.defer)("a");
12+
13+
14+
//// [a.js]
15+
export function foo() {
16+
console.log("foo from a");
17+
}
18+
//// [b.js]
19+
import.defer;
20+
(import.defer)("a");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [tests/cases/conformance/importDefer/dynamicImportDeferInvalidStandalone.ts] ////
2+
3+
=== a.ts ===
4+
export function foo() {
5+
>foo : Symbol(foo, Decl(a.ts, 0, 0))
6+
7+
console.log("foo from a");
8+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
9+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
10+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
11+
}
12+
13+
=== b.ts ===
14+
15+
import.defer;
16+
17+
(import.defer)("a");
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//// [tests/cases/conformance/importDefer/dynamicImportDeferInvalidStandalone.ts] ////
2+
3+
=== a.ts ===
4+
export function foo() {
5+
>foo : () => void
6+
> : ^^^^^^^^^^
7+
8+
console.log("foo from a");
9+
>console.log("foo from a") : void
10+
> : ^^^^
11+
>console.log : (...data: any[]) => void
12+
> : ^^^^ ^^ ^^^^^
13+
>console : Console
14+
> : ^^^^^^^
15+
>log : (...data: any[]) => void
16+
> : ^^^^ ^^ ^^^^^
17+
>"foo from a" : "foo from a"
18+
> : ^^^^^^^^^^^^
19+
}
20+
21+
=== b.ts ===
22+
import.defer;
23+
>import.defer : any
24+
> : ^^^
25+
>defer : any
26+
> : ^^^
27+
28+
(import.defer)("a");
29+
>(import.defer)("a") : any
30+
> : ^^^
31+
>(import.defer) : any
32+
> : ^^^
33+
>import.defer : any
34+
> : ^^^
35+
>defer : any
36+
> : ^^^
37+
>"a" : "a"
38+
> : ^^^
39+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
b.ts(1,1): error TS18060: Deferred imports are only supported when the '--module' flag is set to 'esnext'.
2+
b.ts(1,14): error TS2792: Cannot find module 'a'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?
3+
4+
5+
==== b.ts (2 errors) ====
6+
import.defer("a").then(ns => {
7+
~~~~~~~~~~~~~~~~~
8+
!!! error TS18060: Deferred imports are only supported when the '--module' flag is set to 'esnext'.
9+
~~~
10+
!!! error TS2792: Cannot find module 'a'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?
11+
ns.foo();
12+
});
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//// [tests/cases/conformance/importDefer/dynamicImportDeferMissingModuleESNext.ts] ////
2+
3+
//// [b.ts]
4+
import.defer("a").then(ns => {
5+
ns.foo();
6+
});
7+
8+
9+
//// [b.js]
10+
import.defer("a").then(ns => {
11+
ns.foo();
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//// [tests/cases/conformance/importDefer/dynamicImportDeferMissingModuleESNext.ts] ////
2+
3+
=== b.ts ===
4+
import.defer("a").then(ns => {
5+
>import.defer("a").then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
6+
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
7+
>ns : Symbol(ns, Decl(b.ts, 0, 23))
8+
9+
ns.foo();
10+
>ns : Symbol(ns, Decl(b.ts, 0, 23))
11+
12+
});
13+

0 commit comments

Comments
 (0)