diff --git a/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts b/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts
index 75937df6ccf68..6bc6a64f257ef 100644
--- a/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts
+++ b/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts
@@ -12,8 +12,7 @@ import ts from 'typescript';
import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../../cycles';
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../../diagnostics';
import {absoluteFrom, relative} from '../../../file_system';
-import {assertSuccessfulReferenceEmit, ImportedFile, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
-import {DeferredSymbolTracker} from '../../../imports/src/deferred_symbol_tracker';
+import {assertSuccessfulReferenceEmit, DeferredSymbolTracker, ImportedFile, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {DependencyTracker} from '../../../incremental/api';
import {extractSemanticTypeParameters, SemanticDepGraphUpdater} from '../../../incremental/semantic_graph';
import {IndexingContext} from '../../../indexer';
@@ -987,13 +986,15 @@ export class ComponentDecoratorHandler implements
// the `isDeferrable` flag and the `importPath` to reflect the current
// state after visiting all components during the `resolve` phase.
for (const [_, deferBlockDeps] of resolution.deferBlocks) {
- for (const dep of deferBlockDeps) {
- const classDecl =
- (dep as unknown as {classDeclaration: ts.ClassDeclaration}).classDeclaration;
- const importDecl = resolution.deferrableDeclToImportDecl.get(classDecl) ?? null;
+ for (const deferBlockDep of deferBlockDeps) {
+ const dep = deferBlockDep as unknown as {classDeclaration: ts.ClassDeclaration};
+ const classDecl = dep.classDeclaration as unknown as Expression;
+ const importDecl = resolution.deferrableDeclToImportDecl.get(classDecl) as unknown as
+ ts.ImportDeclaration ??
+ null;
if (importDecl && this.deferredSymbolTracker.canDefer(importDecl)) {
- dep.isDeferrable = true;
- dep.importPath = importDecl.moduleSpecifier.text;
+ deferBlockDep.isDeferrable = true;
+ deferBlockDep.importPath = (importDecl.moduleSpecifier as ts.StringLiteral).text;
}
}
}
@@ -1128,7 +1129,7 @@ export class ComponentDecoratorHandler implements
resolutionData.deferBlocks.set(deferBlock, deps);
}
- // For standalone components with the `imports` field - inspect teh list of
+ // For standalone components with the `imports` field - inspect the list of
// referenced symbols and mark the ones used in defer blocks as potential candidates
// for defer loading.
if (analysisData.meta.isStandalone && analysisData.rawImports !== null &&
@@ -1186,7 +1187,8 @@ export class ComponentDecoratorHandler implements
// Keep track of how this class made it into the current source file
// (which ts.ImportDeclaration was used for this symbol).
- resolutionData.deferrableDeclToImportDecl.set(decl.node, imp.node);
+ resolutionData.deferrableDeclToImportDecl.set(
+ decl.node as unknown as Expression, imp.node as unknown as Expression);
// We're okay deferring this reference to the imported symbol.
this.deferredSymbolTracker.markAsDeferrableCandidate(node, imp.node);
diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
index 57463b0a37430..abe03b0d0dfc0 100644
--- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
+++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
@@ -8872,23 +8872,52 @@ function allTests(os: string) {
});
});
- // TODO: replace with tests for `defer`.
- // TODO: maybe put deferred tests in a separate file?
describe('deferred blocks', () => {
it('should not error for deferred blocks', () => {
env.tsconfig({_enabledBlockTypes: ['defer']});
+ env.write('cmp-a.ts', `
+ import {Component} from '@angular/core';
+
+ @Component({
+ standalone: true,
+ selector: 'cmp-a',
+ template: 'CmpA!'
+ })
+ export class CmpA {}
+ `);
+
env.write('/test.ts', `
import {Component} from '@angular/core';
+ import {CmpA} from './cmp-a';
+
+ @Component({
+ selector: 'local-dep',
+ standalone: true,
+ template: 'Local dependency',
+ })
+ export class LocalDep {}
@Component({
selector: 'test-cmp',
- template: '{#defer}hello{/defer}',
+ standalone: true,
+ imports: [CmpA, LocalDep],
+ template: \`
+ {#defer}
+
+
+ {/defer}
+ \`,
})
export class TestCmp {}
- `);
+ `);
- const diags = env.driveDiagnostics();
- expect(diags.length).toBe(0);
+ env.driveMain();
+
+ const jsContents = env.getContents('test.js');
+ expect(jsContents).toContain('ɵɵdefer(0, TestCmp_Defer_0_DepsFn)');
+ expect(jsContents)
+ .toContain(
+ 'function TestCmp_Defer_0_DepsFn() { return [import("./cmp-a").then(function (m) { return m.CmpA; }), LocalDep]; }');
});
});
});
diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts
index 6ed21604cfb0a..6a3565787ba10 100644
--- a/packages/compiler/src/render3/r3_identifiers.ts
+++ b/packages/compiler/src/render3/r3_identifiers.ts
@@ -139,6 +139,8 @@ export class Identifiers {
static templateCreate: o.ExternalReference = {name: 'ɵɵtemplate', moduleName: CORE};
+ static defer: o.ExternalReference = {name: 'ɵɵdefer', moduleName: CORE};
+
static text: o.ExternalReference = {name: 'ɵɵtext', moduleName: CORE};
static enableBindings: o.ExternalReference = {name: 'ɵɵenableBindings', moduleName: CORE};
diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts
index d65e28fbe72ad..d44a1c52ff091 100644
--- a/packages/compiler/src/render3/view/api.ts
+++ b/packages/compiler/src/render3/view/api.ts
@@ -218,11 +218,11 @@ export interface R3ComponentMetadata
declarations: DeclarationT[];
/**
- * Map of all types that can be defer loaded -> corresponding module specifier
- * strings (that can later be used as a value in dynamic imports).
+ * Map of all types that can be defer loaded (ts.ClassDeclaration) ->
+ * corresponding import declaration (ts.ImportDeclaration) within
+ * the current source file.
*/
- // TODO: fix types! (ClassDeclaration -> ImportDeclaration)
- deferrableDeclToImportDecl: Map;
+ deferrableDeclToImportDecl: Map;
/**
* Map of {#defer} blocks -> their corresponding dependencies.
diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts
index 726dd2ca8f52f..4151b4d3752d5 100644
--- a/packages/compiler/src/render3/view/compiler.ts
+++ b/packages/compiler/src/render3/view/compiler.ts
@@ -194,7 +194,7 @@ export function compileComponentFromMetadata(
const template = meta.template;
const templateBuilder = new TemplateDefinitionBuilder(
constantPool, BindingScope.createRootScope(), 0, templateTypeName, null, null, templateName,
- R3.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds);
+ R3.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds, meta.deferBlocks);
const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []);
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index 7cebf020f56d7..4dc9ac88af896 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -33,6 +33,7 @@ import {Identifiers as R3} from '../r3_identifiers';
import {htmlAstToRender3Ast} from '../r3_template_transform';
import {prepareSyntheticListenerFunctionName, prepareSyntheticListenerName, prepareSyntheticPropertyName} from '../util';
+import {DeferBlockTemplateDependency} from './api';
import {I18nContext} from './i18n/context';
import {createGoogleGetMsgStatements} from './i18n/get_msg_utils';
import {createLocalizeStatements} from './i18n/localize_utils';
@@ -221,6 +222,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
private templateIndex: number|null, private templateName: string|null,
private _namespace: o.ExternalReference, relativeContextFilePath: string,
private i18nUseExternalIds: boolean,
+ private deferBlocks: Map,
private _constants: ComponentDefConsts = createComponentDefConsts()) {
this._bindingScope = parentBindingScope.nestedScope(level);
@@ -923,7 +925,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
const templateVisitor = new TemplateDefinitionBuilder(
this.constantPool, this._bindingScope, this.level + 1, contextName, this.i18n,
templateIndex, templateName, this._namespace, this.fileBasedI18nSuffix,
- this.i18nUseExternalIds, this._constants);
+ this.i18nUseExternalIds, this.deferBlocks, this._constants);
// Nested templates must not be visited until after their parent templates have completed
// processing, so they are queued here until after the initial pass. Otherwise, we wouldn't
@@ -1071,8 +1073,53 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver
return null;
}
- // TODO: implement deferred block instructions.
- visitDeferredBlock(deferred: t.DeferredBlock): void {}
+ visitDeferredBlock(deferred: t.DeferredBlock): void {
+ const templateIndex = this.allocateDataSlot();
+ const deferredDeps = this.deferBlocks.get(deferred);
+
+ const contextName = `${this.contextName}_Defer_${templateIndex}`;
+ const depsFnName = `${contextName}_DepsFn`;
+
+ const parameters: o.Expression[] = [
+ o.literal(templateIndex),
+ deferredDeps ? o.variable(depsFnName) : o.TYPED_NULL_EXPR,
+ ];
+
+ if (deferredDeps) {
+ // This defer block has deps for which we need to generate dynamic imports.
+ const dependencyExp: o.Expression[] = [];
+ for (const deferredDep of deferredDeps) {
+ if (deferredDep.isDeferrable) {
+ // Callback function, e.g. `function(m) { return m.MyCmp; }`.
+ const innerFn = o.fn(
+ [new o.FnParam('m', o.DYNAMIC_TYPE)],
+ [new o.ReturnStatement(o.variable('m').prop(deferredDep.symbolName))]);
+
+ const fileName = deferredDep.importPath!;
+ // Dynamic import, e.g. `import('./a').then(...)`.
+ const importExpr = (new o.DynamicImportExpr(fileName)).prop('then').callFn([innerFn]);
+ dependencyExp.push(importExpr);
+ } else {
+ // Non-deferrable symbol, just use a reference to the type.
+ dependencyExp.push(deferredDep.type);
+ }
+ }
+
+ const depsFnBody: o.Statement[] = [];
+ depsFnBody.push(new o.ReturnStatement(o.literalArr(dependencyExp)));
+
+ const depsFnExpr = o.fn([] /* args */, depsFnBody, o.INFERRED_TYPE, null, depsFnName);
+
+ this.constantPool.statements.push(depsFnExpr.toDeclStmt(depsFnName));
+ }
+
+ // e.g. `defer(1, MyComp_Defer_1_DepsFn, ...)`
+ this.creationInstruction(deferred.sourceSpan, R3.defer, () => {
+ return trimTrailingNulls(parameters);
+ });
+ }
+
+ // TODO: implement nested deferred block instructions.
visitDeferredTrigger(trigger: t.DeferredTrigger): void {}
visitDeferredBlockPlaceholder(block: t.DeferredBlockPlaceholder): void {}
visitDeferredBlockError(block: t.DeferredBlockError): void {}
diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts
index 3f9a7655128ed..e05c964e0091d 100644
--- a/packages/core/src/core_render3_private_export.ts
+++ b/packages/core/src/core_render3_private_export.ts
@@ -197,6 +197,7 @@ export {
ɵɵsyntheticHostProperty,
ɵɵtemplate,
ɵɵtemplateRefExtractor,
+ ɵɵdefer,
ɵɵtext,
ɵɵtextInterpolate,
ɵɵtextInterpolate1,
diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts
index 9ccf3f1b54117..9bdacff1764f0 100644
--- a/packages/core/src/render3/index.ts
+++ b/packages/core/src/render3/index.ts
@@ -118,6 +118,8 @@ export {
ɵɵtemplate,
+ ɵɵdefer,
+
ɵɵtext,
ɵɵtextInterpolate,
ɵɵtextInterpolate1,
diff --git a/packages/core/src/render3/instructions/all.ts b/packages/core/src/render3/instructions/all.ts
index b4d0473008039..f71a8c74fc1f6 100644
--- a/packages/core/src/render3/instructions/all.ts
+++ b/packages/core/src/render3/instructions/all.ts
@@ -50,4 +50,5 @@ export * from './style_map_interpolation';
export * from './style_prop_interpolation';
export * from './host_property';
export * from './i18n';
+export * from './defer';
export {ɵgetUnknownElementStrictMode, ɵsetUnknownElementStrictMode, ɵgetUnknownPropertyStrictMode, ɵsetUnknownPropertyStrictMode} from './element_validation';
diff --git a/packages/core/src/render3/instructions/defer.ts b/packages/core/src/render3/instructions/defer.ts
new file mode 100644
index 0000000000000..c21e5f983e1dc
--- /dev/null
+++ b/packages/core/src/render3/instructions/defer.ts
@@ -0,0 +1,23 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Type} from '../../interface/type';
+
+export type DeferredDepsFn = () => Array>|Type>;
+
+/**
+ * Creates runtime data structures for `{#defer}` blocks.
+ *
+ * @param index The index of the defer block in the data array
+ * @param deferredDepsFn Function that contains dependencies for this defer block
+ *
+ * @codeGenApi
+ */
+export function ɵɵdefer(index: number, deferredDepsFn: DeferredDepsFn|null) {
+ // TODO: implement runtime logic.
+}
diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts
index 4458f4fa79b69..b114cf8ccd3ad 100644
--- a/packages/core/src/render3/jit/environment.ts
+++ b/packages/core/src/render3/jit/environment.ts
@@ -138,6 +138,7 @@ export const angularCoreEnv: {[name: string]: Function} =
'ɵɵclassProp': r3.ɵɵclassProp,
'ɵɵadvance': r3.ɵɵadvance,
'ɵɵtemplate': r3.ɵɵtemplate,
+ 'ɵɵdefer': r3.ɵɵdefer,
'ɵɵtext': r3.ɵɵtext,
'ɵɵtextInterpolate': r3.ɵɵtextInterpolate,
'ɵɵtextInterpolate1': r3.ɵɵtextInterpolate1,