Skip to content

Commit 79e8539

Browse files
AndrewKushnirdylhunn
authored andcommitted
refactor(compiler): extra diagnostics for @defer in local compilation mode (#53899)
This commit adds extra logic to produce a diagnostic in case `@Component.deferredImports` contain types from imports that also bring eager symbols. This would result in retaining a regular import and generating a dynamic import, which would not allow to defer-load dependencies. PR Close #53899
1 parent 6f79507 commit 79e8539

File tree

10 files changed

+361
-91
lines changed

10 files changed

+361
-91
lines changed

packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {BoundTarget, ChangeDetectionStrategy, compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, 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';
8+
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';
99
import semver from 'semver';
1010

1111
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
@@ -183,6 +183,7 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
183183
// Defer blocks are not yet fully supported in partial compilation.
184184
deferrableDeclToImportDecl: new Map(),
185185
deferrableTypes: new Map(),
186+
deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock,
186187

187188
encapsulation: metaObj.has('encapsulation') ?
188189
parseEncapsulation(metaObj.getValue('encapsulation')) :

packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts

Lines changed: 91 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {AnimationTriggerNames, BoundTarget, compileClassDebugInfo, compileComponentClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DeclareComponentTemplateInfo, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, FactoryTarget, makeBindingParser, R3ComponentMetadata, R3DeferBlockMetadata, R3DeferBlockTemplateDependency, R3DirectiveDependencyMetadata, R3NgModuleDependencyMetadata, R3PipeDependencyMetadata, R3TargetBinder, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SchemaMetadata, SelectorMatcher, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstDeferredTrigger, TmplAstElement, ViewEncapsulation, WrappedNodeExpr} from '@angular/compiler';
9+
import {AnimationTriggerNames, BoundTarget, compileClassDebugInfo, compileComponentClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DeclareComponentTemplateInfo, DEFAULT_INTERPOLATION_CONFIG, DeferBlockDepsEmitMode, DomElementSchemaRegistry, Expression, FactoryTarget, makeBindingParser, R3ComponentMetadata, R3DeferBlockMetadata, R3DeferBlockTemplateDependency, R3DirectiveDependencyMetadata, R3NgModuleDependencyMetadata, R3PipeDependencyMetadata, R3TargetBinder, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SchemaMetadata, SelectorMatcher, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstDeferredTrigger, TmplAstElement, ViewEncapsulation, WrappedNodeExpr} from '@angular/compiler';
1010
import ts from 'typescript';
1111

1212
import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../../cycles';
@@ -19,7 +19,7 @@ import {IndexingContext} from '../../../indexer';
1919
import {DirectiveMeta, extractDirectiveTypeCheckMeta, HostDirectivesResolver, MatchSource, MetadataReader, MetadataRegistry, MetaKind, NgModuleMeta, PipeMeta, ResourceRegistry} from '../../../metadata';
2020
import {PartialEvaluator} from '../../../partial_evaluator';
2121
import {PerfEvent, PerfRecorder} from '../../../perf';
22-
import {ClassDeclaration, DeclarationNode, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
22+
import {ClassDeclaration, DeclarationNode, Decorator, Import, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
2323
import {ComponentScopeKind, ComponentScopeReader, DtsModuleScopeResolver, LocalModuleScope, LocalModuleScopeRegistry, makeNotStandaloneDiagnostic, makeUnknownComponentImportDiagnostic, StandaloneScope, TypeCheckScopeRegistry} from '../../../scope';
2424
import {getDiagnosticNode, makeUnknownComponentDeferredImportDiagnostic} from '../../../scope/src/util';
2525
import {AnalysisOutput, CompilationMode, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../../transform';
@@ -477,6 +477,21 @@ export class ComponentDecoratorHandler implements
477477
styles.push(...template.styles);
478478
}
479479

480+
// Collect all explicitly deferred symbols from the `@Component.deferredImports` field
481+
// (if it exists) and populate the `DeferredSymbolTracker` state. These operations are safe
482+
// for the local compilation mode, since they don't require accessing/resolving symbols
483+
// outside of the current source file.
484+
let explicitlyDeferredTypes: Map<string, string>|null = null;
485+
if (metadata.isStandalone && rawDeferredImports !== null) {
486+
const deferredTypes = this.collectExplicitlyDeferredSymbols(rawDeferredImports);
487+
for (const [deferredType, importDetails] of deferredTypes) {
488+
explicitlyDeferredTypes ??= new Map();
489+
explicitlyDeferredTypes.set(importDetails.name, importDetails.from);
490+
this.deferredSymbolTracker.markAsDeferrableCandidate(
491+
deferredType, importDetails.node, node, true /* isExplicitlyDeferred */);
492+
}
493+
}
494+
480495
const output: AnalysisOutput<ComponentAnalysisData> = {
481496
analysis: {
482497
baseClass: readBaseClass(node, this.reflector, this.evaluator),
@@ -524,6 +539,7 @@ export class ComponentDecoratorHandler implements
524539
resolvedImports,
525540
rawDeferredImports,
526541
resolvedDeferredImports,
542+
explicitlyDeferredTypes,
527543
schemas,
528544
decorator: decorator?.node as ts.Decorator | null ?? null,
529545
},
@@ -656,8 +672,43 @@ export class ComponentDecoratorHandler implements
656672
resolve(
657673
node: ClassDeclaration, analysis: Readonly<ComponentAnalysisData>,
658674
symbol: ComponentSymbol): ResolveResult<ComponentResolutionData> {
675+
const metadata = analysis.meta as Readonly<R3ComponentMetadata<R3TemplateDependencyMetadata>>;
676+
const diagnostics: ts.Diagnostic[] = [];
677+
const context = getSourceFile(node);
678+
679+
// Check if there are some import declarations that contain symbols used within
680+
// the `@Component.deferredImports` field, but those imports contain other symbols
681+
// and thus the declaration can not be removed.
682+
const nonRemovableImports =
683+
this.deferredSymbolTracker.getNonRemovableDeferredImports(context, node);
684+
if (nonRemovableImports.length > 0) {
685+
for (const importDecl of nonRemovableImports) {
686+
const diagnostic = makeDiagnostic(
687+
ErrorCode.DEFERRED_DEPENDENCY_IMPORTED_EAGERLY, importDecl,
688+
`This import contains symbols used in the \`@Component.deferredImports\` array ` +
689+
`of the \`${node.name.getText()}\` component, but also some other symbols that ` +
690+
`are not in any \`@Component.deferredImports\` array. This renders all these ` +
691+
`defer imports useless as this import remains and its module is eagerly loaded. ` +
692+
`To fix this, make sure that this import contains *only* symbols ` +
693+
`that are used within \`@Component.deferredImports\` arrays.`);
694+
diagnostics.push(diagnostic);
695+
}
696+
return {diagnostics};
697+
}
698+
659699
if (this.compilationMode === CompilationMode.LOCAL) {
660-
return {};
700+
return {
701+
data: {
702+
declarationListEmitMode: (!analysis.meta.isStandalone || analysis.rawImports !== null) ?
703+
DeclarationListEmitMode.RuntimeResolved :
704+
DeclarationListEmitMode.Direct,
705+
declarations: EMPTY_ARRAY,
706+
deferBlocks: this.locateDeferBlocksWithoutScope(analysis.template),
707+
deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerComponent,
708+
deferrableDeclToImportDecl: new Map(),
709+
deferrableTypes: new Map(),
710+
},
711+
};
661712
}
662713

663714
if (this.semanticDepGraphUpdater !== null && analysis.baseClass instanceof Reference) {
@@ -668,18 +719,14 @@ export class ComponentDecoratorHandler implements
668719
return {};
669720
}
670721

671-
const context = getSourceFile(node);
672-
const metadata = analysis.meta as Readonly<R3ComponentMetadata<R3TemplateDependencyMetadata>>;
673-
674-
675722
const data: ComponentResolutionData = {
676723
declarations: EMPTY_ARRAY,
677724
declarationListEmitMode: DeclarationListEmitMode.Direct,
678725
deferBlocks: new Map(),
726+
deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock,
679727
deferrableDeclToImportDecl: new Map(),
680728
deferrableTypes: new Map(),
681729
};
682-
const diagnostics: ts.Diagnostic[] = [];
683730

684731
const scope = this.scopeReader.getScopeForComponent(node);
685732
if (scope !== null) {
@@ -849,7 +896,7 @@ export class ComponentDecoratorHandler implements
849896
eagerlyUsed.has(decl.ref.node));
850897

851898
// Process information related to defer blocks
852-
this.resolveDeferBlocks(deferBlocks, declarations, data, analysis, eagerlyUsed, bound);
899+
this.resolveDeferBlocks(node, deferBlocks, declarations, data, analysis, eagerlyUsed, bound);
853900

854901
const cyclesFromDirectives = new Map<UsedDirective, Cycle>();
855902
const cyclesFromPipes = new Map<UsedPipe, Cycle>();
@@ -1055,13 +1102,13 @@ export class ComponentDecoratorHandler implements
10551102
return [];
10561103
}
10571104

1058-
// Collect all explicitly deferred symbols from the `@Component.deferredImports` field
1059-
// if it exists. As a part of that process we also populate the `DeferredSymbolTracker` state,
1060-
// which is then used within the `collectDeferredSymbols` call.
1061-
this.collectExplicitlyDeferredSymbols(analysis);
10621105
const deferrableTypes = this.collectDeferredSymbols(resolution);
10631106

1064-
const meta: R3ComponentMetadata<R3TemplateDependency> = {...analysis.meta, ...resolution};
1107+
const meta: R3ComponentMetadata<R3TemplateDependency> = {
1108+
...analysis.meta,
1109+
...resolution,
1110+
deferrableTypes,
1111+
};
10651112
const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component));
10661113

10671114
removeDeferrableTypesFromComponentDecorator(analysis, deferrableTypes);
@@ -1113,19 +1160,20 @@ export class ComponentDecoratorHandler implements
11131160
return [];
11141161
}
11151162

1116-
const deferrableTypes = this.collectExplicitlyDeferredSymbols(analysis);
1117-
const meta: R3ComponentMetadata<R3TemplateDependency> = {
1163+
// In the local compilation mode we can only rely on the information available
1164+
// within the `@Component.deferredImports` array, because in this mode compiler
1165+
// doesn't have information on which dependencies belong to which defer blocks.
1166+
const deferrableTypes = analysis.explicitlyDeferredTypes;
1167+
1168+
const meta = {
11181169
...analysis.meta,
1119-
declarationListEmitMode: (!analysis.meta.isStandalone || analysis.rawImports !== null) ?
1120-
DeclarationListEmitMode.RuntimeResolved :
1121-
DeclarationListEmitMode.Direct,
1122-
declarations: EMPTY_ARRAY,
1123-
deferBlocks: this.locateDeferBlocksWithoutScope(analysis.template),
1124-
deferrableDeclToImportDecl: new Map(),
1125-
deferrableTypes,
1126-
};
1170+
...resolution,
1171+
deferrableTypes: deferrableTypes ?? new Map(),
1172+
} as R3ComponentMetadata<R3TemplateDependency>;
11271173

1128-
removeDeferrableTypesFromComponentDecorator(analysis, deferrableTypes);
1174+
if (analysis.explicitlyDeferredTypes !== null) {
1175+
removeDeferrableTypesFromComponentDecorator(analysis, analysis.explicitlyDeferredTypes);
1176+
}
11291177

11301178
const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component));
11311179
const def = compileComponentFromMetadata(meta, pool, makeBindingParser());
@@ -1191,16 +1239,16 @@ export class ComponentDecoratorHandler implements
11911239
}
11921240

11931241
/**
1194-
* Collects a list of deferrable symbols based on the `@Component.deferredImports` field.
1242+
* Collects deferrable symbols from the `@Component.deferredImports` field.
11951243
*/
1196-
private collectExplicitlyDeferredSymbols(analysis: Readonly<ComponentAnalysisData>):
1197-
Map<string, string> {
1198-
const deferrableTypes = new Map<string, string>();
1199-
if (!analysis.meta.isStandalone || analysis.rawDeferredImports === null ||
1200-
!ts.isArrayLiteralExpression(analysis.rawDeferredImports))
1201-
return deferrableTypes;
1244+
private collectExplicitlyDeferredSymbols(rawDeferredImports: ts.Expression):
1245+
Map<ts.Identifier, Import> {
1246+
const deferredTypes = new Map<ts.Identifier, Import>();
1247+
if (!ts.isArrayLiteralExpression(rawDeferredImports)) {
1248+
return deferredTypes;
1249+
}
12021250

1203-
for (const element of analysis.rawDeferredImports.elements) {
1251+
for (const element of rawDeferredImports.elements) {
12041252
const node = tryUnwrapForwardRef(element, this.reflector) || element;
12051253

12061254
if (!ts.isIdentifier(node)) {
@@ -1210,11 +1258,10 @@ export class ComponentDecoratorHandler implements
12101258

12111259
const imp = this.reflector.getImportOfIdentifier(node);
12121260
if (imp !== null) {
1213-
deferrableTypes.set(imp.name, imp.from);
1214-
this.deferredSymbolTracker.markAsExplicitlyDeferred(imp.node);
1261+
deferredTypes.set(node, imp);
12151262
}
12161263
}
1217-
return deferrableTypes;
1264+
return deferredTypes;
12181265
}
12191266

12201267
/**
@@ -1248,6 +1295,7 @@ export class ComponentDecoratorHandler implements
12481295
* available for the final `compile` step.
12491296
*/
12501297
private resolveDeferBlocks(
1298+
componentClassDecl: ClassDeclaration,
12511299
deferBlocks: Map<TmplAstDeferredBlock, BoundTarget<DirectiveMeta>>,
12521300
deferrableDecls: Map<ClassDeclaration, AnyUsedType>,
12531301
resolutionData: ComponentResolutionData,
@@ -1306,13 +1354,13 @@ export class ComponentDecoratorHandler implements
13061354
if (analysisData.meta.isStandalone) {
13071355
if (analysisData.rawImports !== null) {
13081356
this.registerDeferrableCandidates(
1309-
analysisData.rawImports, false /* isDeferredImport */, allDeferredDecls,
1310-
eagerlyUsedDecls, resolutionData);
1357+
componentClassDecl, analysisData.rawImports, false /* isDeferredImport */,
1358+
allDeferredDecls, eagerlyUsedDecls, resolutionData);
13111359
}
13121360
if (analysisData.rawDeferredImports !== null) {
13131361
this.registerDeferrableCandidates(
1314-
analysisData.rawDeferredImports, true /* isDeferredImport */, allDeferredDecls,
1315-
eagerlyUsedDecls, resolutionData);
1362+
componentClassDecl, analysisData.rawDeferredImports, true /* isDeferredImport */,
1363+
allDeferredDecls, eagerlyUsedDecls, resolutionData);
13161364
}
13171365
}
13181366
}
@@ -1323,7 +1371,7 @@ export class ComponentDecoratorHandler implements
13231371
* candidates.
13241372
*/
13251373
private registerDeferrableCandidates(
1326-
importsExpr: ts.Expression, isDeferredImport: boolean,
1374+
componentClassDecl: ClassDeclaration, importsExpr: ts.Expression, isDeferredImport: boolean,
13271375
allDeferredDecls: Set<ClassDeclaration>, eagerlyUsedDecls: Set<ClassDeclaration>,
13281376
resolutionData: ComponentResolutionData) {
13291377
if (!ts.isArrayLiteralExpression(importsExpr)) {
@@ -1388,11 +1436,8 @@ export class ComponentDecoratorHandler implements
13881436
resolutionData.deferrableDeclToImportDecl.set(
13891437
decl.node as unknown as Expression, imp.node as unknown as Expression);
13901438

1391-
if (isDeferredImport) {
1392-
this.deferredSymbolTracker.markAsExplicitlyDeferred(imp.node);
1393-
} else {
1394-
this.deferredSymbolTracker.markAsDeferrableCandidate(node, imp.node);
1395-
}
1439+
this.deferredSymbolTracker.markAsDeferrableCandidate(
1440+
node, imp.node, componentClassDecl, isDeferredImport);
13961441
}
13971442
}
13981443

packages/compiler-cli/src/ngtsc/annotations/component/src/metadata.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {ParsedTemplateWithSource, StyleUrlMeta} from './resources';
2525
export type ComponentMetadataResolvedFields = SubsetOfKeys<
2626
R3ComponentMetadata<R3TemplateDependencyMetadata>,
2727
'declarations'|'declarationListEmitMode'|'deferBlocks'|'deferrableDeclToImportDecl'|
28-
'deferrableTypes'>;
28+
'deferrableTypes'|'deferBlockDepsEmitMode'>;
2929

3030
export interface ComponentAnalysisData {
3131
/**
@@ -74,6 +74,11 @@ export interface ComponentAnalysisData {
7474
rawDeferredImports: ts.Expression|null;
7575
resolvedDeferredImports: Reference<ClassDeclaration>[]|null;
7676

77+
/**
78+
* Map of symbol name -> import path for types from `@Component.deferredImports` field.
79+
*/
80+
explicitlyDeferredTypes: Map<string, string>|null;
81+
7782
schemas: SchemaMetadata[]|null;
7883

7984
decorator: ts.Decorator|null;

0 commit comments

Comments
 (0)