refactor(compiler): rework defer block analysis (#54700)

Currently we have the `deferrableDeclToImportDecl`, `deferBlocks`, `deferrableTypes` and `deferBlockDepsEmitMode` fields on the `R3ComponentMetadata` which is incorrect, because the interface is used both for JIT and AOT mode even though the information for those fields is AOT-specific. It will be problematic for partial compilation since the runtime will have a reference to the dependency loading function, but will not be able to provide any of the other information.

These changes make the following refactors:
1. It changes the defer-related information in `R3ComponentMetadata` to include only references to dependency functions which can be provided both in JIT and AOT.
2. Moves the AOT-specific defer analysis into the `ComponentResolutionData`.
3. Moves the construction the defer dependency function into the compilation phase of the `ComponentDecoratorHandler`.
4. Drops support for defer blocks from the `TemplateDefinitionBuilder`. This allows us to clean up some TDB-specific code and shouldn't have an effect on users since the TDB isn't used anymore.

PR Close #54700
This commit is contained in:
Kristiyan Kostadinov 2024-03-06 13:30:37 +01:00 committed by Andrew Scott
parent d888da4606
commit eee620aa00
13 changed files with 270 additions and 496 deletions

View file

@ -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<TStatement, TExpression> 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<TStatement, TExpression> implements
};
}
private createR3DeferredMetadata(boundTarget: BoundTarget<any>):
Map<TmplAstDeferredBlock, R3DeferBlockMetadata> {
private createR3DeferMetadata(boundTarget: BoundTarget<any>): R3DeferMetadata {
const deferredBlocks = boundTarget.getDeferBlocks();
const meta = new Map<TmplAstDeferredBlock, R3DeferBlockMetadata>();
const blocks = new Map<TmplAstDeferredBlock, o.ArrowFunctionExpr|null>();
for (const block of deferredBlocks) {
const triggerElements = new Map<TmplAstDeferredTrigger, TmplAstElement>();
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<any>,
triggerElements: Map<TmplAstDeferredTrigger, TmplAstElement|null>): 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};
}
}

View file

@ -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<Reference<ClassDeclaration>>|null = null;
let providersRequiringFactory: Set<Reference<ClassDeclaration>>|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<UsedDirective, Cycle>();
@ -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<R3TemplateDependency> = {
...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<R3TemplateDependencyMetadata> = {
...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<R3TemplateDependency>;
@ -1240,18 +1240,15 @@ export class ComponentDecoratorHandler implements
* For example, this happens in the local compilation mode.
*/
private locateDeferBlocksWithoutScope(template: ComponentTemplate):
Map<TmplAstDeferredBlock, R3DeferBlockMetadata> {
const deferBlocks = new Map<TmplAstDeferredBlock, R3DeferBlockMetadata>();
Map<TmplAstDeferredBlock, DeferredComponentDependency[]> {
const deferBlocks = new Map<TmplAstDeferredBlock, DeferredComponentDependency[]>();
const directivelessBinder = new R3TargetBinder<DirectiveMeta>(new SelectorMatcher());
const bound = directivelessBinder.bind({template: template.nodes});
const deferredBlocks = bound.getDeferBlocks();
const triggerElements = new Map<TmplAstDeferredTrigger, TmplAstElement|null>();
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<ComponentAnalysisData>,
eagerlyUsedDecls: Set<ClassDeclaration>,
componentBoundTarget: BoundTarget<DirectiveMeta>,
) {
// 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<R3DeferBlockTemplateDependency&{classDeclaration: ts.ClassDeclaration}> =
[];
const triggerElements = new Map<TmplAstDeferredTrigger, TmplAstElement|null>();
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<ts.Identifier>,
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<DirectiveMeta>,
triggerElements: Map<TmplAstDeferredTrigger, TmplAstElement|null>): void {
Object.keys(triggers).forEach(key => {
const trigger = triggers[key as keyof TmplAstDeferredBlockTriggers]!;
triggerElements.set(trigger, componentBoundTarget.getDeferredTriggerTarget(block, trigger));
});
private compileDeferBlocks(resolution: Readonly<Partial<ComponentResolutionData>>):
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<TmplAstDeferredBlock, o.ArrowFunctionExpr|null>();
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<ts.Node>).node, deferrableSymbols);
analysis.classMetadata.decorators = new WrappedNodeExpr(rewrittenDecoratorsNode);
(analysis.classMetadata.decorators as o.WrappedNodeExpr<ts.Node>).node, deferrableSymbols);
analysis.classMetadata.decorators = new o.WrappedNodeExpr(rewrittenDecoratorsNode);
}
}

View file

@ -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<R3TemplateDependencyMetadata>,
'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<R3ComponentMetadata<R3TemplateDependencyMetadata>, 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<ClassDeclaration, ts.ImportDeclaration>;
/**
* Map of `@defer` blocks -> their corresponding metadata.
*/
deferBlockDependencies: Map<TmplAstDeferredBlock, DeferredComponentDependency[]>;
/**
* 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<string, {importPath: string, isDefaultImport: boolean}>;
}
/**
* Describes a dependency used within a `@defer` block.
*/
export interface DeferredComponentDependency {
/**
* Reference to a dependency.
*/
type: Reference<ClassDeclaration>;
/**
* 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;
}

View file

@ -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<R3TemplateDependencyMetadata> {
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<any>):
Map<DeferredBlock, R3DeferBlockMetadata> {
function createR3DeferMetadata(boundTarget: BoundTarget<any>): R3DeferMetadata {
const deferredBlocks = boundTarget.getDeferBlocks();
const meta = new Map<DeferredBlock, R3DeferBlockMetadata>();
const blocks = new Map<DeferredBlock, ArrowFunctionExpr|null>();
for (const block of deferredBlocks) {
const triggerElements = new Map<DeferredTrigger, Element>();
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<any>,
triggerElements: Map<DeferredTrigger, Element|null>): 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(

View file

@ -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<unknown>;
/**
* 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<t.DeferredTrigger, t.Element|null>;
}
/**
* Information needed to compile a component for the render3 runtime.
*/
@ -265,29 +224,8 @@ export interface R3ComponentMetadata<DeclarationT extends R3TemplateDependency>
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<o.Expression, o.Expression>;
/**
* Map of `@defer` blocks -> their corresponding metadata.
*/
deferBlocks: Map<t.DeferredBlock, R3DeferBlockMetadata>;
/**
* 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<string, {importPath: string, isDefaultImport: boolean}>;
/** 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<DeclarationT extends R3TemplateDependency>
useTemplatePipeline: boolean;
}
/**
* Information about the deferred blocks in a component's template.
*/
export type R3DeferMetadata = {
mode: DeferBlockDepsEmitMode.PerBlock,
blocks: Map<t.DeferredBlock, o.ArrowFunctionExpr|null>,
}|{
mode: DeferBlockDepsEmitMode.PerComponent,
dependenciesFn: o.ArrowFunctionExpr | null,
};
/**
* Metadata for an individual input on a directive.
*/

View file

@ -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<string, {importPath: string, isDefaultImport: boolean}>) {
// 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);

View file

@ -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<void>, LocalResolver
private templateIndex: number|null, private templateName: string|null,
private _namespace: o.ExternalReference, relativeContextFilePath: string,
private i18nUseExternalIds: boolean,
private deferBlocks: Map<t.DeferredBlock, R3DeferBlockMetadata>,
private elementLocations: Map<t.Element, {index: number, level: number}>,
private allDeferrableDepsFn: o.ReadVarExpr|null,
private _constants: ComponentDefConsts = createComponentDefConsts()) {
this._bindingScope = parentBindingScope.nestedScope(level);
@ -972,8 +968,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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<void>, 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.');
}
/**

View file

@ -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<CreateOp>, 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<CreateOp>, 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,

View file

@ -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<t.DeferredBlock, R3DeferBlockMetadata>,
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);

View file

@ -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},

View file

@ -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<t.DeferredBlock, R3DeferBlockMetadata>,
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;

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}
}
}
}