diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json index 3ab139d8826da3..0088a6dcaccf3f 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json @@ -84,8 +84,7 @@ ], "failureMessage": "Incorrect template" } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should generate a deferred block with external dependencies", @@ -105,8 +104,7 @@ ], "failureMessage": "Incorrect template" } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should generate a deferred block with triggers", diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_external_deps_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_external_deps_template.js index f195899eb01162..f1f39f3dff7c4a 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_external_deps_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_external_deps_template.js @@ -2,7 +2,7 @@ import {EagerDep} from './deferred_with_external_deps_eager'; import {LoadingDep} from './deferred_with_external_deps_loading'; import * as $r3$ from "@angular/core"; -const MyApp_Defer_4_DepsFn = () => [import("./deferred_with_external_deps_lazy").then(m => m.LazyDep)]; +const $MyApp_Defer_4_DepsFn$ = () => [import("./deferred_with_external_deps_lazy").then(m => m.LazyDep)]; function MyApp_Defer_2_Template(rf, ctx) { if (rf & 1) { @@ -23,7 +23,7 @@ MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵelement(1, "eager-dep"); $r3$.ɵɵtemplate(2, MyApp_Defer_2_Template, 1, 0)(3, MyApp_DeferLoading_3_Template, 1, 0); - $r3$.ɵɵdefer(4, 2, MyApp_Defer_4_DepsFn, 3); + $r3$.ɵɵdefer(4, 2, $MyApp_Defer_4_DepsFn$, 3); $r3$.ɵɵdeferOnIdle(); $r3$.ɵɵelementEnd(); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_local_deps_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_local_deps_template.js index 86f91b0492c621..7f650e8715fe90 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_local_deps_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_local_deps_template.js @@ -1,4 +1,4 @@ -const MyApp_Defer_4_DepsFn = () => [LazyDep]; +const $MyApp_Defer_4_DepsFn$ = () => [LazyDep]; … MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ … @@ -7,7 +7,7 @@ MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({ $r3$.ɵɵelementStart(0, "div"); $r3$.ɵɵelement(1, "eager-dep"); $r3$.ɵɵtemplate(2, MyApp_Defer_2_Template, 1, 0)(3, MyApp_DeferLoading_3_Template, 1, 0); - $r3$.ɵɵdefer(4, 2, MyApp_Defer_4_DepsFn, 3); + $r3$.ɵɵdefer(4, 2, $MyApp_Defer_4_DepsFn$, 3); $r3$.ɵɵdeferOnIdle(); $r3$.ɵɵelementEnd(); } diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 6c84f6743f0f48..04bcd6df14511c 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -236,7 +236,7 @@ export function compileComponentFromMetadata( // ingested into IR: const tpl = ingestComponent( meta.name, meta.template.nodes, constantPool, meta.relativeContextFilePath, - meta.i18nUseExternalIds); + meta.i18nUseExternalIds, meta.deferBlocks); // Then the IR is transformed to prepare it for cod egeneration. transform(tpl, CompilationJobKind.Tmpl); diff --git a/packages/compiler/src/template/pipeline/ir/src/ops/create.ts b/packages/compiler/src/template/pipeline/ir/src/ops/create.ts index b327a887a19fd1..eb5c1bd60e535d 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/create.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/create.ts @@ -9,6 +9,7 @@ import * as i18n from '../../../../../i18n/i18n_ast'; import * as o from '../../../../../output/output_ast'; import {ParseSourceSpan} from '../../../../../parse_util'; +import {R3DeferBlockMetadata} from '../../../../../render3/view/api'; import {BindingKind, DeferTriggerKind, I18nParamValueFlags, Namespace, OpKind} from '../enums'; import {SlotHandle} from '../handle'; import {Op, OpList, XrefId} from '../operations'; @@ -688,11 +689,23 @@ export interface DeferOp extends Op, ConsumesSlotOpTrait { placeholderConfig: o.Expression|null; loadingConfig: o.Expression|null; + /** + * Metadata about this defer block, provided by the parser. + */ + metadata: R3DeferBlockMetadata; + + /** + * After processing, the resolver function for the defer deps will be extracted to the constant + * pool, and a reference to that function will be populated here. + */ + resolverFn: o.Expression|null; + sourceSpan: ParseSourceSpan; } export function createDeferOp( - xref: XrefId, main: XrefId, mainSlot: SlotHandle, sourceSpan: ParseSourceSpan): DeferOp { + xref: XrefId, main: XrefId, mainSlot: SlotHandle, metadata: R3DeferBlockMetadata, + sourceSpan: ParseSourceSpan): DeferOp { return { kind: OpKind.Defer, xref, @@ -710,6 +723,8 @@ export function createDeferOp( placeholderMinimumTime: null, errorView: null, errorSlot: null, + metadata, + resolverFn: null, sourceSpan, ...NEW_OP, ...TRAIT_CONSUMES_SLOT, diff --git a/packages/compiler/src/template/pipeline/src/compilation.ts b/packages/compiler/src/template/pipeline/src/compilation.ts index dfcb07ab545ed3..1f38cdc3cf7397 100644 --- a/packages/compiler/src/template/pipeline/src/compilation.ts +++ b/packages/compiler/src/template/pipeline/src/compilation.ts @@ -8,6 +8,8 @@ import {ConstantPool} from '../../../constant_pool'; import * as o from '../../../output/output_ast'; +import * as t from '../../../render3/r3_ast'; +import {R3DeferBlockMetadata} from '../../../render3/view/api'; import * as ir from '../ir'; export enum CompilationJobKind { @@ -64,7 +66,8 @@ export abstract class CompilationJob { export class ComponentCompilationJob extends CompilationJob { constructor( componentName: string, pool: ConstantPool, compatibility: ir.CompatibilityMode, - readonly relativeContextFilePath: string, readonly i18nUseExternalIds: boolean) { + readonly relativeContextFilePath: string, readonly i18nUseExternalIds: boolean, + readonly deferBlocksMeta: Map) { super(componentName, pool, compatibility); this.root = new ViewCompilationUnit(this, this.allocateXrefId(), null); this.views.set(this.root.xref, this.root); diff --git a/packages/compiler/src/template/pipeline/src/emit.ts b/packages/compiler/src/template/pipeline/src/emit.ts index 3f04bb4adbadf8..b4b6ce44d6a1a6 100644 --- a/packages/compiler/src/template/pipeline/src/emit.ts +++ b/packages/compiler/src/template/pipeline/src/emit.ts @@ -22,6 +22,7 @@ import {chain} from './phases/chaining'; import {collapseSingletonInterpolations} from './phases/collapse_singleton_interpolations'; import {generateConditionalExpressions} from './phases/conditionals'; import {collectElementConsts} from './phases/const_collection'; +import {createDeferDepsFns} from './phases/create_defer_deps_fns'; import {configureDeferInstructions} from './phases/defer_configs'; import {resolveDeferTargetNames} from './phases/defer_resolve_targets'; import {collapseEmptyInstructions} from './phases/empty_elements'; @@ -121,6 +122,7 @@ const phases: Phase[] = [ {kind: Kind.Both, fn: expandSafeReads}, {kind: Kind.Both, fn: generateTemporaryVariables}, {kind: Kind.Tmpl, fn: allocateSlots}, + {kind: Kind.Tmpl, fn: createDeferDepsFns}, {kind: Kind.Tmpl, fn: extractI18nMessages}, {kind: Kind.Tmpl, fn: resolveI18nElementPlaceholders}, {kind: Kind.Tmpl, fn: resolveI18nExpressionPlaceholders}, diff --git a/packages/compiler/src/template/pipeline/src/ingest.ts b/packages/compiler/src/template/pipeline/src/ingest.ts index 21341ed11d3576..d8bb5361e92d39 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -14,6 +14,7 @@ import {splitNsName} from '../../../ml_parser/tags'; import * as o from '../../../output/output_ast'; import {ParseSourceSpan} from '../../../parse_util'; import * as t from '../../../render3/r3_ast'; +import {R3DeferBlockMetadata} from '../../../render3/view/api'; import {BindingParser} from '../../../template_parser/binding_parser'; import * as ir from '../ir'; @@ -29,9 +30,11 @@ const compatibilityMode = ir.CompatibilityMode.TemplateDefinitionBuilder; */ export function ingestComponent( componentName: string, template: t.Node[], constantPool: ConstantPool, - relativeContextFilePath: string, i18nUseExternalIds: boolean): ComponentCompilationJob { + relativeContextFilePath: string, i18nUseExternalIds: boolean, + deferBlocksMeta: Map): ComponentCompilationJob { const job = new ComponentCompilationJob( - componentName, constantPool, compatibilityMode, relativeContextFilePath, i18nUseExternalIds); + componentName, constantPool, compatibilityMode, relativeContextFilePath, i18nUseExternalIds, + deferBlocksMeta); ingestNodes(job.root, template); return job; } @@ -361,6 +364,11 @@ function ingestDeferView( } function ingestDeferBlock(unit: ViewCompilationUnit, deferBlock: t.DeferredBlock): void { + const blockMeta = unit.job.deferBlocksMeta.get(deferBlock); + if (blockMeta === undefined) { + throw new Error(`AssertionError: unable to find metadata for deferred block`) + } + // Generate the defer main view and all secondary views. const main = ingestDeferView(unit, '', deferBlock.children, deferBlock.sourceSpan)!; const loading = ingestDeferView( @@ -372,7 +380,8 @@ function ingestDeferBlock(unit: ViewCompilationUnit, deferBlock: t.DeferredBlock // Create the main defer op, and ops for all secondary views. const deferXref = unit.job.allocateXrefId(); - const deferOp = ir.createDeferOp(deferXref, main.xref, main.handle, deferBlock.sourceSpan); + const deferOp = + ir.createDeferOp(deferXref, main.xref, main.handle, blockMeta, deferBlock.sourceSpan); deferOp.placeholderView = placeholder?.xref ?? null; deferOp.placeholderSlot = placeholder?.handle ?? null; deferOp.loadingSlot = loading?.handle ?? null; @@ -383,7 +392,6 @@ function ingestDeferBlock(unit: ViewCompilationUnit, deferBlock: t.DeferredBlock unit.create.push(deferOp); // Configure all defer `on` conditions. - // TODO: refactor prefetch triggers to use a separate op type, with a shared superclass. This will // make it easier to refactor prefetch behavior in the future. let prefetch = false; diff --git a/packages/compiler/src/template/pipeline/src/instruction.ts b/packages/compiler/src/template/pipeline/src/instruction.ts index ebd4e4a058cfca..33c049ff45756f 100644 --- a/packages/compiler/src/template/pipeline/src/instruction.ts +++ b/packages/compiler/src/template/pipeline/src/instruction.ts @@ -192,14 +192,14 @@ export function text( } export function defer( - selfSlot: number, primarySlot: number, dependencyResolverFn: null, loadingSlot: number|null, - placeholderSlot: number|null, errorSlot: number|null, loadingConfig: o.Expression|null, - placeholderConfig: o.Expression|null, enableTimerScheduling: boolean, - sourceSpan: ParseSourceSpan|null): ir.CreateOp { + selfSlot: number, primarySlot: number, dependencyResolverFn: o.Expression|null, + loadingSlot: number|null, placeholderSlot: number|null, errorSlot: number|null, + loadingConfig: o.Expression|null, placeholderConfig: o.Expression|null, + enableTimerScheduling: boolean, sourceSpan: ParseSourceSpan|null): ir.CreateOp { const args: Array = [ o.literal(selfSlot), o.literal(primarySlot), - o.literal(dependencyResolverFn), + dependencyResolverFn ?? o.literal(null), o.literal(loadingSlot), o.literal(placeholderSlot), o.literal(errorSlot), diff --git a/packages/compiler/src/template/pipeline/src/phases/create_defer_deps_fns.ts b/packages/compiler/src/template/pipeline/src/phases/create_defer_deps_fns.ts new file mode 100644 index 00000000000000..56b8f4517a239b --- /dev/null +++ b/packages/compiler/src/template/pipeline/src/phases/create_defer_deps_fns.ts @@ -0,0 +1,49 @@ +/** + * @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 * as o from '../../../../output/output_ast'; +import * as ir from '../../ir'; +import {ComponentCompilationJob} from '../compilation'; + +/** + * Create extracted deps functions for defer ops. + */ +export function createDeferDepsFns(job: ComponentCompilationJob): void { + for (const unit of job.units) { + for (const op of unit.create) { + if (op.kind === ir.OpKind.Defer) { + if (op.metadata.deps.length === 0) { + continue; + } + const dependencies: o.Expression[] = []; + for (const dep of op.metadata.deps) { + if (dep.isDeferrable) { + // Callback function, e.g. `m () => m.MyCmp;`. + const innerFn = o.arrowFn( + [new o.FnParam('m', o.DYNAMIC_TYPE)], o.variable('m').prop(dep.symbolName)); + + // Dynamic import, e.g. `import('./a').then(...)`. + const importExpr = + (new o.DynamicImportExpr(dep.importPath!)).prop('then').callFn([innerFn]); + dependencies.push(importExpr); + } else { + // Non-deferrable symbol, just use a reference to the type. + dependencies.push(dep.type); + } + } + const depsFnExpr = o.arrowFn([], o.literalArr(dependencies)); + if (op.handle.slot === null) { + throw new Error( + 'AssertionError: slot must be assigned bfore extracting defer deps functions'); + } + op.resolverFn = job.pool.getSharedFunctionReference( + depsFnExpr, `${job.componentName}_Defer_${op.handle.slot}_DepsFn`); + } + } + } +} diff --git a/packages/compiler/src/template/pipeline/src/phases/reify.ts b/packages/compiler/src/template/pipeline/src/phases/reify.ts index 21556a6e2c7445..d27d39070eb088 100644 --- a/packages/compiler/src/template/pipeline/src/phases/reify.ts +++ b/packages/compiler/src/template/pipeline/src/phases/reify.ts @@ -151,7 +151,7 @@ function reifyCreateOperations(unit: CompilationUnit, ops: ir.OpList