Skip to content

Commit

Permalink
feat(compiler-cli): add partial compilation support for deferred bloc…
Browse files Browse the repository at this point in the history
…ks (angular#54908)

Builds on top of the previous changes to add support for deferred blocks during partial compilation. To do this, the following changes had to be made:
* The metadata passed into `ɵɵngDeclareComponent` has an additional field called `deferBlockDependencies` which has an array of the dependency loading functions for each defer block in the template. During linking, the dependency functions are loaded by matching their template index to the index in the `deferBlockDependencies` array.
* There's a new `ɵɵngDeclareClassMetadataAsync` function that is created for components that have deferred dependencies. It gets transpiled to `setClassMetadataAsync` and works in the same way by capturing a dependency loading function and setting the metadata after the dependencies are resolved. It also has some extra fields for capturing the version which are standard in linker-generated code.
* Deferred import statements are now stripped in partial compilation mode, similar to full compilation.

PR Close angular#54908
  • Loading branch information
crisbeto authored and dylhunn committed Mar 22, 2024
1 parent 84410f5 commit 5bd188a
Show file tree
Hide file tree
Showing 25 changed files with 448 additions and 144 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {compileOpaqueAsyncClassMetadata, ConstantPool, R3ClassMetadata, R3DeclareClassMetadataAsync} from '@angular/compiler';

import {AstObject, AstValue} from '../../ast/ast_value';
import {FatalLinkerError} from '../../fatal_linker_error';

import {LinkedDefinition, PartialLinker} from './partial_linker';

/**
* A `PartialLinker` that is designed to process `ɵɵngDeclareClassMetadataAsync()` call expressions.
*/
export class PartialClassMetadataAsyncLinkerVersion1<TExpression> implements
PartialLinker<TExpression> {
linkPartialDeclaration(
constantPool: ConstantPool,
metaObj: AstObject<R3DeclareClassMetadataAsync, TExpression>): LinkedDefinition {
const resolveMetadataKey = 'resolveMetadata';
const resolveMetadata =
metaObj.getValue(resolveMetadataKey) as unknown as AstValue<Function, TExpression>;

if (!resolveMetadata.isFunction()) {
throw new FatalLinkerError(
resolveMetadata, `Unsupported \`${resolveMetadataKey}\` value. Expected a function.`);
}

const dependencyResolverFunction = metaObj.getOpaque('resolveDeferredDeps');
const deferredSymbolNames =
resolveMetadata.getFunctionParameters().map(p => p.getSymbolName()!);
const returnValue = resolveMetadata.getFunctionReturnValue<R3ClassMetadata>().getObject();
const metadata: R3ClassMetadata = {
type: metaObj.getOpaque('type'),
decorators: returnValue.getOpaque('decorators'),
ctorParameters: returnValue.getOpaque('ctorParameters'),
propDecorators: returnValue.getOpaque('propDecorators'),
};

return {
expression: compileOpaqueAsyncClassMetadata(
metadata, dependencyResolverFunction, deferredSymbolNames),
statements: [],
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
declarationListEmitMode,
styles: metaObj.has('styles') ? metaObj.getArray('styles').map(entry => entry.getString()) :
[],
defer: this.createR3ComponentDeferMetadata(boundTarget),
defer: this.createR3ComponentDeferMetadata(metaObj, boundTarget),
encapsulation: metaObj.has('encapsulation') ?
parseEncapsulation(metaObj.getValue('encapsulation')) :
ViewEncapsulation.Emulated,
Expand Down Expand Up @@ -257,13 +257,24 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
};
}

private createR3ComponentDeferMetadata(boundTarget: BoundTarget<any>): R3ComponentDeferMetadata {
private createR3ComponentDeferMetadata(
metaObj: AstObject<R3DeclareComponentMetadata, TExpression>,
boundTarget: BoundTarget<any>): R3ComponentDeferMetadata {
const deferredBlocks = boundTarget.getDeferBlocks();
const blocks = new Map<TmplAstDeferredBlock, o.Expression|null>();

for (const block of deferredBlocks) {
// TODO: leaving `deps` empty for now, to be implemented as one of the next steps.
blocks.set(block, null);
const dependencies =
metaObj.has('deferBlockDependencies') ? metaObj.getArray('deferBlockDependencies') : null;

for (let i = 0; i < deferredBlocks.length; i++) {
const matchingDependencyFn = dependencies?.[i];

if (matchingDependencyFn == null) {
blocks.set(deferredBlocks[i], null);
} else {
blocks.set(
deferredBlocks[i],
matchingDependencyFn.isNull() ? null : matchingDependencyFn.getOpaque());
}
}

return {mode: DeferBlockDepsEmitMode.PerBlock, blocks};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {Logger} from '../../../../src/ngtsc/logging';
import {createGetSourceFile} from '../get_source_file';
import {LinkerEnvironment} from '../linker_environment';

import {PartialClassMetadataAsyncLinkerVersion1} from './partial_class_metadata_async_linker_1';
import {PartialClassMetadataLinkerVersion1} from './partial_class_metadata_linker_1';
import {PartialComponentLinkerVersion1} from './partial_component_linker_1';
import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1';
Expand All @@ -31,9 +32,11 @@ export const ɵɵngDeclareInjectable = 'ɵɵngDeclareInjectable';
export const ɵɵngDeclareInjector = 'ɵɵngDeclareInjector';
export const ɵɵngDeclareNgModule = 'ɵɵngDeclareNgModule';
export const ɵɵngDeclarePipe = 'ɵɵngDeclarePipe';
export const ɵɵngDeclareClassMetadataAsync = 'ɵɵngDeclareClassMetadataAsync';
export const declarationFunctions = [
ɵɵngDeclareDirective, ɵɵngDeclareClassMetadata, ɵɵngDeclareComponent, ɵɵngDeclareFactory,
ɵɵngDeclareInjectable, ɵɵngDeclareInjector, ɵɵngDeclareNgModule, ɵɵngDeclarePipe
ɵɵngDeclareInjectable, ɵɵngDeclareInjector, ɵɵngDeclareNgModule, ɵɵngDeclarePipe,
ɵɵngDeclareClassMetadataAsync
];

export interface LinkerRange<TExpression> {
Expand Down Expand Up @@ -74,6 +77,9 @@ export function createLinkerMap<TStatement, TExpression>(
linkers.set(ɵɵngDeclareDirective, [
{range: LATEST_VERSION_RANGE, linker: new PartialDirectiveLinkerVersion1(sourceUrl, code)},
]);
linkers.set(ɵɵngDeclareClassMetadataAsync, [
{range: LATEST_VERSION_RANGE, linker: new PartialClassMetadataAsyncLinkerVersion1()},
]);
linkers.set(ɵɵngDeclareClassMetadata, [
{range: LATEST_VERSION_RANGE, linker: new PartialClassMetadataLinkerVersion1()},
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {AnimationTriggerNames, BoundTarget, compileClassDebugInfo, compileComponentClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, compileDeferResolverFunction, ConstantPool, CssSelector, DeclarationListEmitMode, DeclareComponentTemplateInfo, DEFAULT_INTERPOLATION_CONFIG, DeferBlockDepsEmitMode, DomElementSchemaRegistry, ExternalExpr, FactoryTarget, makeBindingParser, outputAst as o, R3ComponentDeferMetadata, R3ComponentMetadata, R3DeferPerComponentDependency, R3DirectiveDependencyMetadata, R3NgModuleDependencyMetadata, R3PipeDependencyMetadata, R3TargetBinder, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SchemaMetadata, SelectorMatcher, TmplAstDeferredBlock, ViewEncapsulation} from '@angular/compiler';
import {AnimationTriggerNames, BoundTarget, compileClassDebugInfo, compileComponentClassMetadata, compileComponentDeclareClassMetadata, compileComponentFromMetadata, compileDeclareComponentFromMetadata, compileDeferResolverFunction, ConstantPool, CssSelector, DeclarationListEmitMode, DeclareComponentTemplateInfo, DEFAULT_INTERPOLATION_CONFIG, DeferBlockDepsEmitMode, DomElementSchemaRegistry, ExternalExpr, FactoryTarget, makeBindingParser, outputAst as o, R3ComponentDeferMetadata, R3ComponentMetadata, R3DeferPerComponentDependency, R3DirectiveDependencyMetadata, R3NgModuleDependencyMetadata, R3PipeDependencyMetadata, R3TargetBinder, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SchemaMetadata, SelectorMatcher, TmplAstDeferredBlock, ViewEncapsulation} from '@angular/compiler';
import ts from 'typescript';

import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../../cycles';
Expand Down Expand Up @@ -1143,21 +1143,20 @@ export class ComponentDecoratorHandler implements
return [];
}

const deferrableTypes = this.collectDeferredSymbols(resolution);

const perComponentDeferredDeps = this.resolveAllDeferredDependencies(resolution);
const meta: R3ComponentMetadata<R3TemplateDependency> = {
...analysis.meta,
...resolution,
defer: this.compileDeferBlocks(resolution),
};
const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component));

removeDeferrableTypesFromComponentDecorator(analysis, deferrableTypes);
removeDeferrableTypesFromComponentDecorator(analysis, perComponentDeferredDeps);

const def = compileComponentFromMetadata(meta, pool, makeBindingParser());
const inputTransformFields = compileInputTransformFields(analysis.inputs);
const classMetadata = analysis.classMetadata !== null ?
compileComponentClassMetadata(analysis.classMetadata, deferrableTypes).toStmt() :
compileComponentClassMetadata(analysis.classMetadata, perComponentDeferredDeps).toStmt() :
null;
const debugInfo = analysis.classDebugInfo !== null ?
compileClassDebugInfo(analysis.classDebugInfo).toStmt() :
Expand All @@ -1182,6 +1181,7 @@ export class ComponentDecoratorHandler implements
null,
};

const perComponentDeferredDeps = this.resolveAllDeferredDependencies(resolution);
const meta: R3ComponentMetadata<R3TemplateDependencyMetadata> = {
...analysis.meta,
...resolution,
Expand All @@ -1191,11 +1191,11 @@ export class ComponentDecoratorHandler implements
const inputTransformFields = compileInputTransformFields(analysis.inputs);
const def = compileDeclareComponentFromMetadata(meta, analysis.template, templateInfo);
const classMetadata = analysis.classMetadata !== null ?
compileDeclareClassMetadata(analysis.classMetadata).toStmt() :
compileComponentDeclareClassMetadata(analysis.classMetadata, perComponentDeferredDeps)
.toStmt() :
null;

return compileResults(
fac, def, classMetadata, 'ɵcmp', inputTransformFields, null /* deferrableImports */);
const deferrableImports = this.deferredSymbolTracker.getDeferrableImportDecls();
return compileResults(fac, def, classMetadata, 'ɵcmp', inputTransformFields, deferrableImports);
}

compileLocal(
Expand Down Expand Up @@ -1256,7 +1256,8 @@ export class ComponentDecoratorHandler implements
* Computes a list of deferrable symbols based on dependencies from
* the `@Component.imports` field and their usage in `@defer` blocks.
*/
private collectDeferredSymbols(resolution: Readonly<ComponentResolutionData>) {
private resolveAllDeferredDependencies(resolution: Readonly<ComponentResolutionData>):
R3DeferPerComponentDependency[] {
const deferrableTypes: R3DeferPerComponentDependency[] = [];
// Go over all dependencies of all defer blocks and update the value of
// the `isDeferrable` flag and the `importPath` to reflect the current
Expand Down Expand Up @@ -1505,7 +1506,7 @@ export class ComponentDecoratorHandler implements
'Internal error: deferPerComponentDependencies must be present in PerComponent mode');
}
return {
mode: DeferBlockDepsEmitMode.PerComponent,
mode,
dependenciesFn: perComponentDeps.length === 0 ?
null :
compileDeferResolverFunction({mode, dependencies: perComponentDeps})
Expand Down

0 comments on commit 5bd188a

Please sign in to comment.