diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts index 87f46eee98f..874364289af 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts @@ -5,7 +5,7 @@ * 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 {BoundTarget, ChangeDetectionStrategy, compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, DeferBlockDepsEmitMode, ForwardRefHandling, InterpolationConfig, makeBindingParser, outputAst as o, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareDirectiveDependencyMetadata, R3DeclarePipeDependencyMetadata, R3DeferBlockMetadata, R3DirectiveDependencyMetadata, R3PartialDeclaration, R3TargetBinder, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SelectorMatcher, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstDeferredTrigger, TmplAstElement, ViewEncapsulation} from '@angular/compiler'; +import {BoundTarget, ChangeDetectionStrategy, compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, DeferBlockDepsEmitMode, ForwardRefHandling, InterpolationConfig, makeBindingParser, outputAst as o, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareDirectiveDependencyMetadata, R3DeclarePipeDependencyMetadata, R3DeferMetadata, R3DirectiveDependencyMetadata, R3PartialDeclaration, R3TargetBinder, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SelectorMatcher, TmplAstDeferredBlock, ViewEncapsulation} from '@angular/compiler'; import semver from 'semver'; import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system'; @@ -178,13 +178,7 @@ export class PartialComponentLinkerVersion1 implements declarationListEmitMode, styles: metaObj.has('styles') ? metaObj.getArray('styles').map(entry => entry.getString()) : [], - deferBlocks: this.createR3DeferredMetadata(boundTarget), - - // Defer blocks are not yet fully supported in partial compilation. - deferrableDeclToImportDecl: new Map(), - deferrableTypes: new Map(), - deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock, - + defer: this.createR3DeferMetadata(boundTarget), encapsulation: metaObj.has('encapsulation') ? parseEncapsulation(metaObj.getValue('encapsulation')) : ViewEncapsulation.Emulated, @@ -264,32 +258,16 @@ export class PartialComponentLinkerVersion1 implements }; } - private createR3DeferredMetadata(boundTarget: BoundTarget): - Map { + private createR3DeferMetadata(boundTarget: BoundTarget): R3DeferMetadata { const deferredBlocks = boundTarget.getDeferBlocks(); - const meta = new Map(); + const blocks = new Map(); for (const block of deferredBlocks) { - const triggerElements = new Map(); - - this.resolveDeferTriggers(block, block.triggers, boundTarget, triggerElements); - this.resolveDeferTriggers(block, block.prefetchTriggers, boundTarget, triggerElements); - // TODO: leaving `deps` empty for now, to be implemented as one of the next steps. - meta.set(block, {deps: [], triggerElements}); + blocks.set(block, null); } - return meta; - } - - private resolveDeferTriggers( - block: TmplAstDeferredBlock, triggers: TmplAstDeferredBlockTriggers, - boundTarget: BoundTarget, - triggerElements: Map): void { - Object.keys(triggers).forEach(key => { - const trigger = triggers[key as keyof TmplAstDeferredBlockTriggers]!; - triggerElements.set(trigger, boundTarget.getDeferredTriggerTarget(block, trigger)); - }); + return {mode: DeferBlockDepsEmitMode.PerBlock, blocks}; } } 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 f4e9536c237..ec577540c92 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AnimationTriggerNames, BoundTarget, compileClassDebugInfo, compileComponentClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DeclareComponentTemplateInfo, DEFAULT_INTERPOLATION_CONFIG, DeferBlockDepsEmitMode, DomElementSchemaRegistry, Expression, ExternalExpr, FactoryTarget, makeBindingParser, R3ComponentMetadata, R3DeferBlockMetadata, R3DeferBlockTemplateDependency, R3DirectiveDependencyMetadata, R3NgModuleDependencyMetadata, R3PipeDependencyMetadata, R3TargetBinder, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SchemaMetadata, SelectorMatcher, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstDeferredTrigger, TmplAstElement, ViewEncapsulation, WrappedNodeExpr} from '@angular/compiler'; +import {AnimationTriggerNames, BoundTarget, compileClassDebugInfo, compileComponentClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DeclareComponentTemplateInfo, DEFAULT_INTERPOLATION_CONFIG, DeferBlockDepsEmitMode, DomElementSchemaRegistry, ExternalExpr, FactoryTarget, makeBindingParser, outputAst as o, R3ComponentMetadata, R3DeferMetadata, R3DirectiveDependencyMetadata, R3NgModuleDependencyMetadata, R3PipeDependencyMetadata, R3TargetBinder, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SchemaMetadata, SelectorMatcher, TmplAstDeferredBlock, ViewEncapsulation} from '@angular/compiler'; import ts from 'typescript'; import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../../cycles'; @@ -32,7 +32,7 @@ import {extractDirectiveMetadata, parseDirectiveStyles} from '../../directive'; import {createModuleWithProvidersResolver, NgModuleSymbol} from '../../ng_module'; import {checkCustomElementSelectorForErrors, makeCyclicImportInfo} from './diagnostics'; -import {ComponentAnalysisData, ComponentResolutionData} from './metadata'; +import {ComponentAnalysisData, ComponentResolutionData, DeferredComponentDependency} from './metadata'; import {_extractTemplateStyleUrls, extractComponentStyleUrls, extractStyleResources, extractTemplate, makeResourceNotFoundError, ParsedTemplateWithSource, parseTemplateDeclaration, preloadAndParseTemplate, ResourceTypeForDiagnostics, StyleUrlMeta, transformDecoratorResources} from './resources'; import {ComponentSymbol} from './symbol'; import {animationTriggerResolver, collectAnimationNames, validateAndFlattenComponentImports} from './util'; @@ -245,19 +245,19 @@ export class ComponentDecoratorHandler implements resolveEncapsulationEnumValueLocally(component.get('encapsulation'))) ?? ViewEncapsulation.Emulated; - let changeDetection: number|Expression|null = null; + let changeDetection: number|o.Expression|null = null; if (this.compilationMode !== CompilationMode.LOCAL) { changeDetection = resolveEnumValue(this.evaluator, component, 'changeDetection', 'ChangeDetectionStrategy'); } else if (component.has('changeDetection')) { - changeDetection = new WrappedNodeExpr(component.get('changeDetection')!); + changeDetection = new o.WrappedNodeExpr(component.get('changeDetection')!); } - let animations: Expression|null = null; + let animations: o.Expression|null = null; let animationTriggerNames: AnimationTriggerNames|null = null; if (component.has('animations')) { const animationExpression = component.get('animations')!; - animations = new WrappedNodeExpr(animationExpression); + animations = new o.WrappedNodeExpr(animationExpression); const animationsValue = this.evaluator.evaluate(animationExpression, animationTriggerResolver); animationTriggerNames = {includesDynamicAnimations: false, staticTriggerNames: []}; @@ -281,13 +281,13 @@ export class ComponentDecoratorHandler implements // we can distinguish where an error is coming from when logging the diagnostics in `resolve`. let viewProvidersRequiringFactory: Set>|null = null; let providersRequiringFactory: Set>|null = null; - let wrappedViewProviders: Expression|null = null; + let wrappedViewProviders: o.Expression|null = null; if (component.has('viewProviders')) { const viewProviders = component.get('viewProviders')!; viewProvidersRequiringFactory = resolveProvidersRequiringFactory(viewProviders, this.reflector, this.evaluator); - wrappedViewProviders = new WrappedNodeExpr( + wrappedViewProviders = new o.WrappedNodeExpr( this.annotateForClosureCompiler ? wrapFunctionExpressionsInParens(viewProviders) : viewProviders); } @@ -520,7 +520,7 @@ export class ComponentDecoratorHandler implements viewProviders: wrappedViewProviders, i18nUseExternalIds: this.i18nUseExternalIds, relativeContextFilePath, - rawImports: rawImports !== null ? new WrappedNodeExpr(rawImports) : undefined, + rawImports: rawImports !== null ? new o.WrappedNodeExpr(rawImports) : undefined, useTemplatePipeline: this.useTemplatePipeline, }, typeCheckMeta: extractDirectiveTypeCheckMeta(node, inputs, this.reflector), @@ -710,10 +710,10 @@ export class ComponentDecoratorHandler implements declarationListEmitMode: (!analysis.meta.isStandalone || analysis.rawImports !== null) ? DeclarationListEmitMode.RuntimeResolved : DeclarationListEmitMode.Direct, - deferBlocks: this.locateDeferBlocksWithoutScope(analysis.template), + deferBlockDependencies: this.locateDeferBlocksWithoutScope(analysis.template), deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerComponent, deferrableDeclToImportDecl: new Map(), - deferrableTypes: new Map(), + deferrableTypes: analysis.explicitlyDeferredTypes ?? new Map(), }; if (this.localCompilationExtraImportsTracker === null) { @@ -726,7 +726,7 @@ export class ComponentDecoratorHandler implements data = { declarations: EMPTY_ARRAY, declarationListEmitMode: DeclarationListEmitMode.Direct, - deferBlocks: new Map(), + deferBlockDependencies: new Map(), deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock, deferrableDeclToImportDecl: new Map(), deferrableTypes: new Map(), @@ -910,8 +910,7 @@ export class ComponentDecoratorHandler implements // Process information related to defer blocks if (this.compilationMode !== CompilationMode.LOCAL) { - this.resolveDeferBlocks( - node, deferBlocks, declarations, data, analysis, eagerlyUsed, bound); + this.resolveDeferBlocks(node, deferBlocks, declarations, data, analysis, eagerlyUsed); } const cyclesFromDirectives = new Map(); @@ -1028,7 +1027,7 @@ export class ComponentDecoratorHandler implements } else { // If there is no scope, we can still use the binder to retrieve *some* information about the // deferred blocks. - data.deferBlocks = this.locateDeferBlocksWithoutScope(metadata.template); + data.deferBlockDependencies = this.locateDeferBlocksWithoutScope(metadata.template); } // Run diagnostics only in global mode. @@ -1048,7 +1047,7 @@ export class ComponentDecoratorHandler implements } if (analysis.providersRequiringFactory !== null && - analysis.meta.providers instanceof WrappedNodeExpr) { + analysis.meta.providers instanceof o.WrappedNodeExpr) { const providerDiagnostics = getProviderDiagnostics( analysis.providersRequiringFactory, analysis.meta.providers!.node, this.injectableRegistry); @@ -1056,7 +1055,7 @@ export class ComponentDecoratorHandler implements } if (analysis.viewProvidersRequiringFactory !== null && - analysis.meta.viewProviders instanceof WrappedNodeExpr) { + analysis.meta.viewProviders instanceof o.WrappedNodeExpr) { const viewProviderDiagnostics = getProviderDiagnostics( analysis.viewProvidersRequiringFactory, analysis.meta.viewProviders!.node, this.injectableRegistry); @@ -1145,7 +1144,7 @@ export class ComponentDecoratorHandler implements const meta: R3ComponentMetadata = { ...analysis.meta, ...resolution, - deferrableTypes, + defer: this.compileDeferBlocks(resolution), useTemplatePipeline, }; const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component)); @@ -1176,7 +1175,7 @@ export class ComponentDecoratorHandler implements sourceUrl: analysis.template.declaration.resolvedTemplateUrl, isInline: analysis.template.declaration.isInline, inlineTemplateLiteralExpression: analysis.template.sourceMapping.type === 'direct' ? - new WrappedNodeExpr(analysis.template.sourceMapping.node) : + new o.WrappedNodeExpr(analysis.template.sourceMapping.node) : null, }; @@ -1184,6 +1183,7 @@ export class ComponentDecoratorHandler implements const meta: R3ComponentMetadata = { ...analysis.meta, ...resolution, + defer: this.compileDeferBlocks(resolution), useTemplatePipeline }; const fac = compileDeclareFactory(toFactoryMetadata(meta, FactoryTarget.Component)); @@ -1213,7 +1213,7 @@ export class ComponentDecoratorHandler implements const meta = { ...analysis.meta, ...resolution, - deferrableTypes: deferrableTypes ?? new Map(), + defer: this.compileDeferBlocks(resolution), useTemplatePipeline, } as R3ComponentMetadata; @@ -1240,18 +1240,15 @@ export class ComponentDecoratorHandler implements * For example, this happens in the local compilation mode. */ private locateDeferBlocksWithoutScope(template: ComponentTemplate): - Map { - const deferBlocks = new Map(); + Map { + const deferBlocks = new Map(); const directivelessBinder = new R3TargetBinder(new SelectorMatcher()); const bound = directivelessBinder.bind({template: template.nodes}); const deferredBlocks = bound.getDeferBlocks(); - const triggerElements = new Map(); for (const block of deferredBlocks) { - this.resolveDeferTriggers(block, block.triggers, bound, triggerElements); - this.resolveDeferTriggers(block, block.prefetchTriggers, bound, triggerElements); // We can't determine the dependencies without a scope so we leave them empty. - deferBlocks.set(block, {deps: [], triggerElements}); + deferBlocks.set(block, []); } return deferBlocks; } @@ -1265,14 +1262,10 @@ export class ComponentDecoratorHandler implements // Go over all dependencies of all defer blocks and update the value of // the `isDeferrable` flag and the `importPath` to reflect the current // state after visiting all components during the `resolve` phase. - for (const [_, metadata] of resolution.deferBlocks) { - for (const deferBlockDep of metadata.deps) { - const dep = deferBlockDep as unknown as { - classDeclaration: ts.ClassDeclaration; - }; - const classDecl = dep.classDeclaration as unknown as Expression; - const importDecl = (resolution.deferrableDeclToImportDecl.get(classDecl) ?? - null) as (ts.ImportDeclaration | null); + for (const [_, deps] of resolution.deferBlockDependencies) { + for (const deferBlockDep of deps) { + const importDecl = + resolution.deferrableDeclToImportDecl.get(deferBlockDep.type.node) ?? null; if (importDecl !== null && this.deferredSymbolTracker.canDefer(importDecl)) { deferBlockDep.isDeferrable = true; deferBlockDep.importPath = (importDecl.moduleSpecifier as ts.StringLiteral).text; @@ -1320,7 +1313,7 @@ export class ComponentDecoratorHandler implements * @returns a `Cycle` object if a cycle would be created, otherwise `null`. */ private _checkForCyclicImport( - importedFile: ImportedFile, expr: Expression, origin: ts.SourceFile): Cycle|null { + importedFile: ImportedFile, expr: o.Expression, origin: ts.SourceFile): Cycle|null { const imported = resolveImportedFile(this.moduleResolver, importedFile, expr, origin); if (imported === null) { return null; @@ -1330,7 +1323,7 @@ export class ComponentDecoratorHandler implements } private maybeRecordSyntheticImport( - importedFile: ImportedFile, expr: Expression, origin: ts.SourceFile): void { + importedFile: ImportedFile, expr: o.Expression, origin: ts.SourceFile): void { const imported = resolveImportedFile(this.moduleResolver, importedFile, expr, origin); if (imported === null) { return; @@ -1350,7 +1343,6 @@ export class ComponentDecoratorHandler implements resolutionData: ComponentResolutionData, analysisData: Readonly, eagerlyUsedDecls: Set, - componentBoundTarget: BoundTarget, ) { // Collect all deferred decls from all defer blocks from the entire template // to intersect with the information from the `imports` field of a particular @@ -1360,9 +1352,14 @@ export class ComponentDecoratorHandler implements for (const [deferBlock, bound] of deferBlocks) { const usedDirectives = new Set(bound.getEagerlyUsedDirectives().map(d => d.ref.node)); const usedPipes = new Set(bound.getEagerlyUsedPipes()); - const deps: Array = - []; - const triggerElements = new Map(); + let deps: DeferredComponentDependency[]; + + if (resolutionData.deferBlockDependencies.has(deferBlock)) { + deps = resolutionData.deferBlockDependencies.get(deferBlock)!; + } else { + deps = []; + resolutionData.deferBlockDependencies.set(deferBlock, deps); + } for (const decl of Array.from(deferrableDecls.values())) { if (decl.kind === R3TemplateDependencyKind.NgModule) { @@ -1379,23 +1376,14 @@ export class ComponentDecoratorHandler implements // `isDeferrable`, `importPath` and `isDefaultImport` will be // added later during the `compile` step. deps.push({ - type: decl.type as WrappedNodeExpr, - symbolName: decl.ref.node.name.escapedText as string, + type: decl.ref, + symbolName: decl.ref.node.name.text, isDeferrable: false, importPath: null, isDefaultImport: false, - // Extra info to match corresponding import during the `compile` phase. - classDeclaration: decl.ref.node as ts.ClassDeclaration, }); allDeferredDecls.add(decl.ref.node); } - - this.resolveDeferTriggers( - deferBlock, deferBlock.triggers, componentBoundTarget, triggerElements); - this.resolveDeferTriggers( - deferBlock, deferBlock.prefetchTriggers, componentBoundTarget, triggerElements); - - resolutionData.deferBlocks.set(deferBlock, {deps, triggerElements}); } // For standalone components with the `imports` and `deferredImports` fields - @@ -1483,23 +1471,78 @@ 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 as unknown as Expression, imp.node as unknown as Expression); + resolutionData.deferrableDeclToImportDecl.set(decl.node, imp.node); this.deferredSymbolTracker.markAsDeferrableCandidate( node, imp.node, componentClassDecl, isDeferredImport); } } - /** Resolves the triggers of the defer block to the elements that they're pointing to. */ - private resolveDeferTriggers( - block: TmplAstDeferredBlock, triggers: TmplAstDeferredBlockTriggers, - componentBoundTarget: BoundTarget, - triggerElements: Map): void { - Object.keys(triggers).forEach(key => { - const trigger = triggers[key as keyof TmplAstDeferredBlockTriggers]!; - triggerElements.set(trigger, componentBoundTarget.getDeferredTriggerTarget(block, trigger)); - }); + private compileDeferBlocks(resolution: Readonly>): + R3DeferMetadata { + if (resolution.deferBlockDepsEmitMode === DeferBlockDepsEmitMode.PerBlock) { + if (!resolution.deferBlockDependencies) { + throw new Error( + 'Internal error: deferBlockDependencies must be present when compiling in PerBlock mode'); + } + + const blocks = new Map(); + + for (const [block, dependencies] of resolution.deferBlockDependencies) { + const depExpressions: o.Expression[] = []; + for (const dep of dependencies) { + if (dep.isDeferrable) { + // Callback function, e.g. `m () => m.MyCmp;`. + const innerFn = o.arrowFn( + // Default imports are always accessed through the `default` property. + [new o.FnParam('m', o.DYNAMIC_TYPE)], + o.variable('m').prop(dep.isDefaultImport ? 'default' : dep.symbolName)); + + // Dynamic import, e.g. `import('./a').then(...)`. + const importExpr = + (new o.DynamicImportExpr(dep.importPath!)).prop('then').callFn([innerFn]); + depExpressions.push(importExpr); + } else { + // Non-deferrable symbol, just use a reference to the type. + depExpressions.push(o.variable(dep.symbolName)); + } + } + blocks.set( + block, + depExpressions.length === 0 ? null : o.arrowFn([], o.literalArr(depExpressions))); + } + + return {mode: DeferBlockDepsEmitMode.PerBlock, blocks}; + } + + if (resolution.deferBlockDepsEmitMode === DeferBlockDepsEmitMode.PerComponent) { + if (!resolution.deferBlockDependencies || !resolution.deferrableTypes) { + throw new Error( + 'Internal error: deferBlockDependencies and deferrableTypes must be present in PerComponent mode'); + } + + // This defer block has deps for which we need to generate dynamic imports. + const depExpressions: o.Expression[] = []; + + for (const [symbolName, {importPath, isDefaultImport}] of resolution.deferrableTypes) { + // Callback function, e.g. `m () => m.MyCmp;`. + const innerFn = o.arrowFn( + [new o.FnParam('m', o.DYNAMIC_TYPE)], + o.variable('m').prop(isDefaultImport ? 'default' : symbolName)); + + // Dynamic import, e.g. `import('./a').then(...)`. + const importExpr = (new o.DynamicImportExpr(importPath)).prop('then').callFn([innerFn]); + depExpressions.push(importExpr); + } + + return { + mode: DeferBlockDepsEmitMode.PerComponent, + dependenciesFn: depExpressions.length === 0 ? null : + o.arrowFn([], o.literalArr(depExpressions)) + }; + } + + throw new Error(`Invalid deferBlockDepsEmitMode. Cannot compile deferred block metadata.`); } } @@ -1546,8 +1589,8 @@ function removeDeferrableTypesFromComponentDecorator( if (analysis.classMetadata) { const deferrableSymbols = new Set(deferrableTypes.keys()); const rewrittenDecoratorsNode = removeIdentifierReferences( - (analysis.classMetadata.decorators as WrappedNodeExpr).node, deferrableSymbols); - analysis.classMetadata.decorators = new WrappedNodeExpr(rewrittenDecoratorsNode); + (analysis.classMetadata.decorators as o.WrappedNodeExpr).node, deferrableSymbols); + analysis.classMetadata.decorators = new o.WrappedNodeExpr(rewrittenDecoratorsNode); } } diff --git a/packages/compiler-cli/src/ngtsc/annotations/component/src/metadata.ts b/packages/compiler-cli/src/ngtsc/annotations/component/src/metadata.ts index f1a406d39f6..02b201f8f7c 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/component/src/metadata.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/component/src/metadata.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AnimationTriggerNames, R3ClassDebugInfo, R3ClassMetadata, R3ComponentMetadata, R3TemplateDependencyMetadata, SchemaMetadata} from '@angular/compiler'; +import {AnimationTriggerNames, DeclarationListEmitMode, DeferBlockDepsEmitMode, R3ClassDebugInfo, R3ClassMetadata, R3ComponentMetadata, R3TemplateDependencyMetadata, SchemaMetadata, TmplAstDeferredBlock} from '@angular/compiler'; import ts from 'typescript'; import {Reference} from '../../../imports'; @@ -24,8 +24,7 @@ import {ParsedTemplateWithSource, StyleUrlMeta} from './resources'; */ export type ComponentMetadataResolvedFields = SubsetOfKeys< R3ComponentMetadata, - 'declarations'|'declarationListEmitMode'|'deferBlocks'|'deferrableDeclToImportDecl'| - 'deferrableTypes'|'deferBlockDepsEmitMode'>; + 'declarations'|'declarationListEmitMode'|'defer'>; export interface ComponentAnalysisData { /** @@ -90,5 +89,61 @@ export interface ComponentAnalysisData { rawHostDirectives: ts.Expression|null; } -export type ComponentResolutionData = - Pick, ComponentMetadataResolvedFields>; +export interface ComponentResolutionData { + declarations: R3TemplateDependencyMetadata[]; + declarationListEmitMode: DeclarationListEmitMode; + + /** + * Map of all types that can be defer loaded (ts.ClassDeclaration) -> + * corresponding import declaration (ts.ImportDeclaration) within + * the current source file. + */ + deferrableDeclToImportDecl: Map; + + /** + * Map of `@defer` blocks -> their corresponding metadata. + */ + deferBlockDependencies: Map; + + /** + * Defines how dynamic imports for deferred dependencies should be grouped: + * - either in a function on per-component basis (in case of local compilation) + * - or in a function on per-block basis (in full compilation mode) + */ + deferBlockDepsEmitMode: DeferBlockDepsEmitMode; + + /** + * Map of deferrable symbol names -> corresponding import paths. + */ + deferrableTypes: Map; +} + +/** + * Describes a dependency used within a `@defer` block. + */ +export interface DeferredComponentDependency { + /** + * Reference to a dependency. + */ + type: Reference; + + /** + * Dependency class name. + */ + symbolName: string; + + /** + * Whether this dependency can be defer-loaded. + */ + isDeferrable: boolean; + + /** + * Import path where this dependency is located. + */ + importPath: string|null; + + /** + * Whether the symbol is the default export. + */ + isDefaultImport: boolean; +} diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index e67a36b72bd..4a565a500dc 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -11,17 +11,17 @@ import {ConstantPool} from './constant_pool'; import {ChangeDetectionStrategy, HostBinding, HostListener, Input, Output, ViewEncapsulation} from './core'; import {compileInjectable} from './injectable_compiler_2'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/defaults'; -import {DeclareVarStmt, Expression, literal, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast'; +import {ArrowFunctionExpr, DeclareVarStmt, Expression, literal, LiteralExpr, Statement, StmtModifier, WrappedNodeExpr} from './output/output_ast'; import {JitEvaluator} from './output/output_jit'; import {ParseError, ParseSourceSpan, r3JitTypeSourceSpan} from './parse_util'; -import {DeferredBlock, DeferredBlockTriggers, DeferredTrigger, Element} from './render3/r3_ast'; +import {DeferredBlock} from './render3/r3_ast'; import {compileFactoryFunction, FactoryTarget, R3DependencyMetadata} from './render3/r3_factory'; import {compileInjector, R3InjectorMetadata} from './render3/r3_injector_compiler'; import {R3JitReflector} from './render3/r3_jit'; import {compileNgModule, compileNgModuleDeclarationExpression, R3NgModuleMetadata, R3NgModuleMetadataKind, R3SelectorScopeMode} from './render3/r3_module_compiler'; import {compilePipeFromMetadata, R3PipeMetadata} from './render3/r3_pipe_compiler'; import {createMayBeForwardRefExpression, ForwardRefHandling, getSafePropertyAccessString, MaybeForwardRefExpression, wrapReference} from './render3/util'; -import {DeclarationListEmitMode, DeferBlockDepsEmitMode, R3ComponentMetadata, R3DeferBlockMetadata, R3DirectiveDependencyMetadata, R3DirectiveMetadata, R3HostDirectiveMetadata, R3HostMetadata, R3InputMetadata, R3PipeDependencyMetadata, R3QueryMetadata, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata} from './render3/view/api'; +import {DeclarationListEmitMode, DeferBlockDepsEmitMode, R3ComponentMetadata, R3DeferMetadata, R3DirectiveDependencyMetadata, R3DirectiveMetadata, R3HostDirectiveMetadata, R3HostMetadata, R3InputMetadata, R3PipeDependencyMetadata, R3QueryMetadata, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata} from './render3/view/api'; import {compileComponentFromMetadata, compileDirectiveFromMetadata, ParsedHostBindings, parseHostBindings, verifyHostBindings} from './render3/view/compiler'; import type {BoundTarget} from './render3/view/t2_api'; import {R3TargetBinder} from './render3/view/t2_binder'; @@ -182,7 +182,7 @@ export class CompilerFacadeImpl implements CompilerFacade { angularCoreEnv: CoreEnvironment, sourceMapUrl: string, facade: R3ComponentMetadataFacade): any { // Parse the template and check for errors. - const {template, interpolation, deferBlocks} = parseJitTemplate( + const {template, interpolation, defer} = parseJitTemplate( facade.template, facade.name, sourceMapUrl, facade.preserveWhitespaces, facade.interpolation); @@ -194,10 +194,7 @@ export class CompilerFacadeImpl implements CompilerFacade { template, declarations: facade.declarations.map(convertDeclarationFacadeToMetadata), declarationListEmitMode: DeclarationListEmitMode.Direct, - deferBlocks, - deferrableTypes: new Map(), - deferrableDeclToImportDecl: new Map(), - deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock, + defer, styles: [...facade.styles, ...template.styles], encapsulation: facade.encapsulation, @@ -447,7 +444,7 @@ function convertOpaqueValuesToExpressions(obj: {[key: string]: OpaqueValue}): function convertDeclareComponentFacadeToMetadata( decl: R3DeclareComponentFacade, typeSourceSpan: ParseSourceSpan, sourceMapUrl: string): R3ComponentMetadata { - const {template, interpolation, deferBlocks} = parseJitTemplate( + const {template, interpolation, defer} = parseJitTemplate( decl.template, decl.type.name, sourceMapUrl, decl.preserveWhitespaces ?? false, decl.interpolation); @@ -484,10 +481,7 @@ function convertDeclareComponentFacadeToMetadata( viewProviders: decl.viewProviders !== undefined ? new WrappedNodeExpr(decl.viewProviders) : null, animations: decl.animations !== undefined ? new WrappedNodeExpr(decl.animations) : null, - deferBlocks, - deferrableTypes: new Map(), - deferrableDeclToImportDecl: new Map(), - deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock, + defer, changeDetection: decl.changeDetection ?? ChangeDetectionStrategy.Default, encapsulation: decl.encapsulation ?? ViewEncapsulation.Emulated, @@ -562,7 +556,7 @@ function parseJitTemplate( return { template: parsed, interpolation: interpolationConfig, - deferBlocks: createR3DeferredMetadata(boundTarget) + defer: createR3DeferMetadata(boundTarget) }; } @@ -633,31 +627,17 @@ function createR3DependencyMetadata( return {token, attributeNameType, host, optional, self, skipSelf}; } -function createR3DeferredMetadata(boundTarget: BoundTarget): - Map { +function createR3DeferMetadata(boundTarget: BoundTarget): R3DeferMetadata { const deferredBlocks = boundTarget.getDeferBlocks(); - const meta = new Map(); + const blocks = new Map(); for (const block of deferredBlocks) { - const triggerElements = new Map(); - - resolveDeferTriggers(block, block.triggers, boundTarget, triggerElements); - resolveDeferTriggers(block, block.prefetchTriggers, boundTarget, triggerElements); - - // TODO: leaving `deps` empty in JIT mode for now, to be implemented as one of the next steps. - meta.set(block, {deps: [], triggerElements}); + // TODO: leaving dependency function empty in JIT mode for now, + // to be implemented as one of the next steps. + blocks.set(block, null); } - return meta; -} - -function resolveDeferTriggers( - block: DeferredBlock, triggers: DeferredBlockTriggers, boundTarget: BoundTarget, - triggerElements: Map): void { - Object.keys(triggers).forEach(key => { - const trigger = triggers[key as keyof DeferredBlockTriggers]!; - triggerElements.set(trigger, boundTarget.getDeferredTriggerTarget(block, trigger)); - }); + return {mode: DeferBlockDepsEmitMode.PerBlock, blocks}; } function extractHostBindings( diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index 07fb2d0dcc7..f59b74b0e3e 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -196,47 +196,6 @@ export const enum DeclarationListEmitMode { RuntimeResolved, } -/** - * Describes a dependency used within a `@defer` block. - */ -export interface R3DeferBlockTemplateDependency { - /** - * Reference to a dependency. - */ - type: o.WrappedNodeExpr; - - /** - * Dependency class name. - */ - symbolName: string; - - /** - * Whether this dependency can be defer-loaded. - */ - isDeferrable: boolean; - - /** - * Import path where this dependency is located. - */ - importPath: string|null; - - /** - * Whether the symbol is the default export. - */ - isDefaultImport: boolean; -} - -/** - * Information necessary to compile a `defer` block. - */ -export interface R3DeferBlockMetadata { - /** Dependencies used within the block. */ - deps: R3DeferBlockTemplateDependency[]; - - /** Mapping between triggers and the DOM nodes they refer to. */ - triggerElements: Map; -} - /** * Information needed to compile a component for the render3 runtime. */ @@ -265,29 +224,8 @@ export interface R3ComponentMetadata declarations: DeclarationT[]; - /** - * Map of all types that can be defer loaded (ts.ClassDeclaration) -> - * corresponding import declaration (ts.ImportDeclaration) within - * the current source file. - */ - deferrableDeclToImportDecl: Map; - - /** - * Map of `@defer` blocks -> their corresponding metadata. - */ - deferBlocks: Map; - - /** - * Defines how dynamic imports for deferred dependencies should be grouped: - * - either in a function on per-component basis (in case of local compilation) - * - or in a function on per-block basis (in full compilation mode) - */ - deferBlockDepsEmitMode: DeferBlockDepsEmitMode; - - /** - * Map of deferrable symbol names -> corresponding import paths. - */ - deferrableTypes: Map; + /** Metadata related to the deferred blocks in the component's template. */ + defer: R3DeferMetadata; /** * Specifies how the 'directives' and/or `pipes` array, if generated, need to be emitted. @@ -356,6 +294,17 @@ export interface R3ComponentMetadata useTemplatePipeline: boolean; } +/** + * Information about the deferred blocks in a component's template. + */ +export type R3DeferMetadata = { + mode: DeferBlockDepsEmitMode.PerBlock, + blocks: Map, +}|{ + mode: DeferBlockDepsEmitMode.PerComponent, + dependenciesFn: o.ArrowFunctionExpr | null, +}; + /** * Metadata for an individual input on a directive. */ diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index c4862e4486a..bcccd0f0d14 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -159,34 +159,6 @@ export function compileDirectiveFromMetadata( return {expression, type, statements: []}; } -/** - * Creates an AST for a function that contains dynamic imports representing - * deferrable dependencies. - */ -function createDeferredDepsFunction( - constantPool: ConstantPool, name: string, - deps: Map) { - // This defer block has deps for which we need to generate dynamic imports. - const dependencyExp: o.Expression[] = []; - - for (const [symbolName, {importPath, isDefaultImport}] of deps) { - // Callback function, e.g. `m () => m.MyCmp;`. - const innerFn = o.arrowFn( - [new o.FnParam('m', o.DYNAMIC_TYPE)], - o.variable('m').prop(isDefaultImport ? 'default' : symbolName)); - - // Dynamic import, e.g. `import('./a').then(...)`. - const importExpr = (new o.DynamicImportExpr(importPath)).prop('then').callFn([innerFn]); - dependencyExp.push(importExpr); - } - - const depsFnExpr = o.arrowFn([], o.literalArr(dependencyExp)); - - constantPool.statements.push(depsFnExpr.toDeclStmt(name, o.StmtModifier.Final)); - - return o.variable(name); -} - /** * Compile a component for the render3 runtime as defined by the `R3ComponentMetadata`. */ @@ -219,10 +191,12 @@ export function compileComponentFromMetadata( let allDeferrableDepsFn: o.ReadVarExpr|null = null; - if (meta.deferBlocks.size > 0 && meta.deferrableTypes.size > 0 && - meta.deferBlockDepsEmitMode === DeferBlockDepsEmitMode.PerComponent) { + if (meta.defer.mode === DeferBlockDepsEmitMode.PerComponent && + meta.defer.dependenciesFn !== null) { const fnName = `${templateTypeName}_DeferFn`; - allDeferrableDepsFn = createDeferredDepsFunction(constantPool, fnName, meta.deferrableTypes); + constantPool.statements.push( + meta.defer.dependenciesFn.toDeclStmt(fnName, o.StmtModifier.Final)); + allDeferrableDepsFn = o.variable(fnName); } // Template compilation is currently conditional as we're in the process of rewriting it. @@ -233,8 +207,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, meta.deferBlocks, - new Map(), allDeferrableDepsFn); + R3.namespaceHTML, meta.relativeContextFilePath, meta.i18nUseExternalIds, new Map()); const templateFunctionExpression = templateBuilder.buildTemplateFunction(template.nodes, []); @@ -276,7 +249,7 @@ export function compileComponentFromMetadata( // ingested into IR: const tpl = ingestComponent( meta.name, meta.template.nodes, constantPool, meta.relativeContextFilePath, - meta.i18nUseExternalIds, meta.deferBlocks, allDeferrableDepsFn); + meta.i18nUseExternalIds, meta.defer, allDeferrableDepsFn); // Then the IR is transformed to prepare it for cod egeneration. transform(tpl, CompilationJobKind.Tmpl); diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 1d78b8da761..be36711a4f7 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -25,7 +25,6 @@ import {ParseError, ParseSourceSpan, sanitizeIdentifier} from '../../parse_util' import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry'; import {isIframeSecuritySensitiveAttr} from '../../schema/dom_security_schema'; import {isTrustedTypesSink} from '../../schema/trusted_types_sinks'; -import {CssSelector} from '../../selector'; import {BindingParser} from '../../template_parser/binding_parser'; import {error, partitionArray} from '../../util'; import * as t from '../r3_ast'; @@ -33,7 +32,6 @@ import {Identifiers as R3} from '../r3_identifiers'; import {htmlAstToRender3Ast} from '../r3_template_transform'; import {prepareSyntheticListenerFunctionName, prepareSyntheticListenerName, prepareSyntheticPropertyName} from '../util'; -import {R3DeferBlockMetadata} from './api'; import {I18nContext} from './i18n/context'; import {createGoogleGetMsgStatements} from './i18n/get_msg_utils'; import {createLocalizeStatements} from './i18n/localize_utils'; @@ -250,9 +248,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 elementLocations: Map, - private allDeferrableDepsFn: o.ReadVarExpr|null, private _constants: ComponentDefConsts = createComponentDefConsts()) { this._bindingScope = parentBindingScope.nestedScope(level); @@ -972,8 +968,8 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Create the template function const visitor = new TemplateDefinitionBuilder( this.constantPool, this._bindingScope, this.level + 1, contextName, this.i18n, index, name, - this._namespace, this.fileBasedI18nSuffix, this.i18nUseExternalIds, this.deferBlocks, - this.elementLocations, this.allDeferrableDepsFn, this._constants); + this._namespace, this.fileBasedI18nSuffix, this.i18nUseExternalIds, this.elementLocations, + 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 @@ -1327,192 +1323,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } visitDeferredBlock(deferred: t.DeferredBlock): void { - const {loading, placeholder, error, triggers, prefetchTriggers} = deferred; - const metadata = this.deferBlocks.get(deferred); - - if (!metadata) { - throw new Error('Could not resolve `defer` block metadata. Block may need to be analyzed.'); - } - - const primaryTemplateIndex = this.createEmbeddedTemplateFn( - null, deferred.children, '_Defer', deferred.sourceSpan, undefined, undefined, undefined, - deferred.i18n); - const loadingIndex = loading ? this.createEmbeddedTemplateFn( - null, loading.children, '_DeferLoading', loading.sourceSpan, - undefined, undefined, undefined, loading.i18n) : - null; - const loadingConsts = loading ? - trimTrailingNulls([o.literal(loading.minimumTime), o.literal(loading.afterTime)]) : - null; - - const placeholderIndex = placeholder ? - this.createEmbeddedTemplateFn( - null, placeholder.children, '_DeferPlaceholder', placeholder.sourceSpan, undefined, - undefined, undefined, placeholder.i18n) : - null; - const placeholderConsts = placeholder && placeholder.minimumTime !== null ? - // TODO(crisbeto): potentially pass the time directly instead of storing it in the `consts` - // since the placeholder block can only have one parameter? - o.literalArr([o.literal(placeholder.minimumTime)]) : - null; - - const errorIndex = error ? this.createEmbeddedTemplateFn( - null, error.children, '_DeferError', error.sourceSpan, undefined, - undefined, undefined, error.i18n) : - null; - - // Note: we generate this last so the index matches the instruction order. - const deferredIndex = this.allocateDataSlot(); - const depsFnName = `${this.contextName}_Defer_${deferredIndex}_DepsFn`; - - // e.g. `defer(1, 0, MyComp_Defer_1_DepsFn, ...)` - this.creationInstruction( - deferred.sourceSpan, R3.defer, trimTrailingNulls([ - o.literal(deferredIndex), - o.literal(primaryTemplateIndex), - this.allDeferrableDepsFn ?? this.createDeferredDepsFunction(depsFnName, metadata), - o.literal(loadingIndex), - o.literal(placeholderIndex), - o.literal(errorIndex), - loadingConsts?.length ? this.addToConsts(o.literalArr(loadingConsts)) : o.TYPED_NULL_EXPR, - placeholderConsts ? this.addToConsts(placeholderConsts) : o.TYPED_NULL_EXPR, - (loadingConsts?.length || placeholderConsts) ? - o.importExpr(R3.deferEnableTimerScheduling) : - o.TYPED_NULL_EXPR, - ])); - - // Allocate an extra data slot right after a defer block slot to store - // instance-specific state of that defer block at runtime. - this.allocateDataSlot(); - - // Note: the triggers need to be processed *after* the various templates, - // otherwise pipes injecting some symbols won't work (see #52102). - this.createDeferTriggerInstructions(deferredIndex, triggers, metadata, false); - this.createDeferTriggerInstructions(deferredIndex, prefetchTriggers, metadata, true); - } - - private createDeferredDepsFunction(name: string, metadata: R3DeferBlockMetadata) { - if (metadata.deps.length === 0) { - return o.TYPED_NULL_EXPR; - } - - // This defer block has deps for which we need to generate dynamic imports. - const dependencyExp: o.Expression[] = []; - - for (const deferredDep of metadata.deps) { - if (deferredDep.isDeferrable) { - // Callback function, e.g. `m () => m.MyCmp;`. - const innerFn = o.arrowFn( - [new o.FnParam('m', o.DYNAMIC_TYPE)], - // Default imports are always accessed through the `default` property. - o.variable('m').prop(deferredDep.isDefaultImport ? 'default' : deferredDep.symbolName)); - - // Dynamic import, e.g. `import('./a').then(...)`. - const importExpr = - (new o.DynamicImportExpr(deferredDep.importPath!)).prop('then').callFn([innerFn]); - dependencyExp.push(importExpr); - } else { - // Non-deferrable symbol, just use a reference to the type. - dependencyExp.push(deferredDep.type); - } - } - - const depsFnExpr = o.arrowFn([], o.literalArr(dependencyExp)); - - this.constantPool.statements.push(depsFnExpr.toDeclStmt(name, o.StmtModifier.Final)); - - return o.variable(name); - } - - private createDeferTriggerInstructions( - deferredIndex: number, triggers: t.DeferredBlockTriggers, metadata: R3DeferBlockMetadata, - prefetch: boolean) { - const {when, idle, immediate, timer, hover, interaction, viewport} = triggers; - - // `deferWhen(ctx.someValue)` - if (when) { - const value = when.value.visit(this._valueConverter); - this.allocateBindingSlots(value); - this.updateInstructionWithAdvance( - deferredIndex, when.sourceSpan, prefetch ? R3.deferPrefetchWhen : R3.deferWhen, - () => this.convertPropertyBinding(value)); - } - - // Note that we generate an implicit `on idle` if the `deferred` block has no triggers. - // `deferOnIdle()` - if (idle || (!prefetch && Object.keys(triggers).length === 0)) { - this.creationInstruction( - idle?.sourceSpan || null, prefetch ? R3.deferPrefetchOnIdle : R3.deferOnIdle); - } - - // `deferOnImmediate()` - if (immediate) { - this.creationInstruction( - immediate.sourceSpan, prefetch ? R3.deferPrefetchOnImmediate : R3.deferOnImmediate); - } - - // `deferOnTimer(1337)` - if (timer) { - this.creationInstruction( - timer.sourceSpan, prefetch ? R3.deferPrefetchOnTimer : R3.deferOnTimer, - [o.literal(timer.delay)]); - } - - // `deferOnHover(index, walkUpTimes)` - if (hover) { - this.domNodeBasedTrigger( - 'hover', hover, metadata, prefetch ? R3.deferPrefetchOnHover : R3.deferOnHover); - } - - // `deferOnInteraction(index, walkUpTimes)` - if (interaction) { - this.domNodeBasedTrigger( - 'interaction', interaction, metadata, - prefetch ? R3.deferPrefetchOnInteraction : R3.deferOnInteraction); - } - - // `deferOnViewport(index, walkUpTimes)` - if (viewport) { - this.domNodeBasedTrigger( - 'viewport', viewport, metadata, - prefetch ? R3.deferPrefetchOnViewport : R3.deferOnViewport); - } - } - - private domNodeBasedTrigger( - name: string, - trigger: t.InteractionDeferredTrigger|t.HoverDeferredTrigger|t.ViewportDeferredTrigger, - metadata: R3DeferBlockMetadata, instructionRef: o.ExternalReference) { - const triggerEl = metadata.triggerElements.get(trigger); - - // Don't generate anything if a trigger cannot be resolved. - // We'll have template diagnostics to surface these to users. - if (!triggerEl) { - return; - } - - this.creationInstruction(trigger.sourceSpan, instructionRef, () => { - const location = this.elementLocations.get(triggerEl); - - if (!location) { - throw new Error( - `Could not determine location of reference passed into ` + - `'${name}' trigger. Template may not have been fully analyzed.`); - } - - // A negative depth means that the trigger is inside the placeholder. - // Cap it at -1 since we only care whether or not it's negative. - const depth = Math.max(this.level - location.level, -1); - const params = [o.literal(location.index)]; - - // The most common case should be a trigger within the view so we can omit a depth of - // zero. For triggers in parent views and in the placeholder we need to pass it in. - if (depth !== 0) { - params.push(o.literal(depth)); - } - - return params; - }); + throw new Error( + 'Deferred blocks are no longer supported by the obsolete TemplateDefinitionBuilder, ' + + 'Please enable the new template compilation pipeline for your application.'); } /** 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 8c74481ee5a..34c24f6d9a0 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/create.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/create.ts @@ -10,7 +10,6 @@ import {SecurityContext} from '../../../../../core'; 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, I18nContextKind, I18nParamValueFlags, Namespace, OpKind, TemplateKind} from '../enums'; import {SlotHandle} from '../handle'; import {Op, OpList, XrefId} from '../operations'; @@ -838,9 +837,11 @@ export interface DeferOp extends Op, ConsumesSlotOpTrait { loadingConfig: o.Expression|null; /** - * Metadata about this defer block, provided by the parser. + * Depending on the compilation mode, there can be either one dependency resolution function + * per deferred block or one for the entire template. This field contains the function that + * belongs specifically to the current deferred block. */ - metadata: R3DeferBlockMetadata; + ownResolverFn: o.ArrowFunctionExpr|null; /** * After processing, the resolver function for the defer deps will be extracted to the constant @@ -852,7 +853,7 @@ export interface DeferOp extends Op, ConsumesSlotOpTrait { } export function createDeferOp( - xref: XrefId, main: XrefId, mainSlot: SlotHandle, metadata: R3DeferBlockMetadata, + xref: XrefId, main: XrefId, mainSlot: SlotHandle, ownResolverFn: o.ArrowFunctionExpr|null, resolverFn: o.Expression|null, sourceSpan: ParseSourceSpan): DeferOp { return { kind: OpKind.Defer, @@ -871,7 +872,7 @@ export function createDeferOp( placeholderMinimumTime: null, errorView: null, errorSlot: null, - metadata, + ownResolverFn, resolverFn, sourceSpan, ...NEW_OP, diff --git a/packages/compiler/src/template/pipeline/src/compilation.ts b/packages/compiler/src/template/pipeline/src/compilation.ts index ab76bfe9b5e..8e1b02357fe 100644 --- a/packages/compiler/src/template/pipeline/src/compilation.ts +++ b/packages/compiler/src/template/pipeline/src/compilation.ts @@ -8,8 +8,7 @@ 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 {R3DeferMetadata} from '../../../render3/view/api'; import * as ir from '../ir'; export enum CompilationJobKind { @@ -67,8 +66,7 @@ export class ComponentCompilationJob extends CompilationJob { constructor( componentName: string, pool: ConstantPool, compatibility: ir.CompatibilityMode, readonly relativeContextFilePath: string, readonly i18nUseExternalIds: boolean, - readonly deferBlocksMeta: Map, - readonly allDeferrableDepsFn: o.ReadVarExpr|null) { + readonly deferMeta: R3DeferMetadata, readonly allDeferrableDepsFn: o.ReadVarExpr|null) { 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 6dcb863864d..42a82e834e3 100644 --- a/packages/compiler/src/template/pipeline/src/emit.ts +++ b/packages/compiler/src/template/pipeline/src/emit.ts @@ -23,7 +23,7 @@ import {collapseSingletonInterpolations} from './phases/collapse_singleton_inter import {generateConditionalExpressions} from './phases/conditionals'; import {collectElementConsts} from './phases/const_collection'; import {convertI18nBindings} from './phases/convert_i18n_bindings'; -import {createDeferDepsFns} from './phases/create_defer_deps_fns'; +import {resolveDeferDepsFns} from './phases/resolve_defer_deps_fns'; import {createI18nContexts} from './phases/create_i18n_contexts'; import {deduplicateTextBindings} from './phases/deduplicate_text_bindings'; import {configureDeferInstructions} from './phases/defer_configs'; @@ -140,7 +140,7 @@ const phases: Phase[] = [ {kind: Kind.Tmpl, fn: generateAdvance}, {kind: Kind.Both, fn: optimizeVariables}, {kind: Kind.Both, fn: nameFunctionsAndVariables}, - {kind: Kind.Tmpl, fn: createDeferDepsFns}, + {kind: Kind.Tmpl, fn: resolveDeferDepsFns}, {kind: Kind.Tmpl, fn: mergeNextContextExpressions}, {kind: Kind.Tmpl, fn: generateNgContainerOps}, {kind: Kind.Tmpl, fn: collapseEmptyInstructions}, diff --git a/packages/compiler/src/template/pipeline/src/ingest.ts b/packages/compiler/src/template/pipeline/src/ingest.ts index f2a43f32ccb..609e36fb011 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -14,7 +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 {DeferBlockDepsEmitMode, R3DeferMetadata} from '../../../render3/view/api'; import {icuFromI18nMessage, isSingleI18nIcu} from '../../../render3/view/i18n/util'; import {DomElementSchemaRegistry} from '../../../schema/dom_element_schema_registry'; import {BindingParser} from '../../../template_parser/binding_parser'; @@ -38,12 +38,11 @@ const NG_TEMPLATE_TAG_NAME = 'ng-template'; */ export function ingestComponent( componentName: string, template: t.Node[], constantPool: ConstantPool, - relativeContextFilePath: string, i18nUseExternalIds: boolean, - deferBlocksMeta: Map, + relativeContextFilePath: string, i18nUseExternalIds: boolean, deferMeta: R3DeferMetadata, allDeferrableDepsFn: o.ReadVarExpr|null): ComponentCompilationJob { const job = new ComponentCompilationJob( componentName, constantPool, compatibilityMode, relativeContextFilePath, i18nUseExternalIds, - deferBlocksMeta, allDeferrableDepsFn); + deferMeta, allDeferrableDepsFn); ingestNodes(job.root, template); return job; } @@ -443,9 +442,14 @@ 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`); + let ownResolverFn: o.ArrowFunctionExpr|null = null; + + if (unit.job.deferMeta.mode === DeferBlockDepsEmitMode.PerBlock) { + if (!unit.job.deferMeta.blocks.has(deferBlock)) { + throw new Error( + `AssertionError: unable to find a dependency function for this deferred block`); + } + ownResolverFn = unit.job.deferMeta.blocks.get(deferBlock) ?? null; } // Generate the defer main view and all secondary views. @@ -464,7 +468,7 @@ 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, blockMeta, unit.job.allDeferrableDepsFn, + deferXref, main.xref, main.handle, ownResolverFn, unit.job.allDeferrableDepsFn, deferBlock.sourceSpan); deferOp.placeholderView = placeholder?.xref ?? null; deferOp.placeholderSlot = placeholder?.handle ?? null; 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 deleted file mode 100644 index dd2fbd9a425..00000000000 --- a/packages/compiler/src/template/pipeline/src/phases/create_defer_deps_fns.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @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; - } - if (op.resolverFn !== null) { - 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( - // Default imports are always accessed through the `default` property. - [new o.FnParam('m', o.DYNAMIC_TYPE)], - o.variable('m').prop(dep.isDefaultImport ? 'default' : 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'); - } - const fullPathName = unit.fnName?.replace(`_Template`, ``); - op.resolverFn = job.pool.getSharedFunctionReference( - depsFnExpr, `${fullPathName}_Defer_${op.handle.slot}_DepsFn`, - /* Don't use unique names for TDB compatibility */ false); - } - } - } -} diff --git a/packages/compiler/src/template/pipeline/src/phases/resolve_defer_deps_fns.ts b/packages/compiler/src/template/pipeline/src/phases/resolve_defer_deps_fns.ts new file mode 100644 index 00000000000..b1bfd48919c --- /dev/null +++ b/packages/compiler/src/template/pipeline/src/phases/resolve_defer_deps_fns.ts @@ -0,0 +1,36 @@ +/** + * @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 ir from '../../ir'; +import {ComponentCompilationJob} from '../compilation'; + +/** + * Resolve the dependency function of a deferred block. + */ +export function resolveDeferDepsFns(job: ComponentCompilationJob): void { + for (const unit of job.units) { + for (const op of unit.create) { + if (op.kind === ir.OpKind.Defer) { + if (op.resolverFn !== null) { + continue; + } + + if (op.ownResolverFn !== null) { + if (op.handle.slot === null) { + throw new Error( + 'AssertionError: slot must be assigned before extracting defer deps functions'); + } + const fullPathName = unit.fnName?.replace('_Template', ''); + op.resolverFn = job.pool.getSharedFunctionReference( + op.ownResolverFn, `${fullPathName}_Defer_${op.handle.slot}_DepsFn`, + /* Don't use unique names for TDB compatibility */ false); + } + } + } + } +}