mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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
This commit is contained in:
committed by
Dylan Hunn
parent
6f79507ea7
commit
79e8539ce2
+2
-1
@@ -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, 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, R3DeferBlockMetadata, R3DirectiveDependencyMetadata, R3PartialDeclaration, R3TargetBinder, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SelectorMatcher, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstDeferredTrigger, TmplAstElement, ViewEncapsulation} from '@angular/compiler';
|
||||
import semver from 'semver';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
|
||||
@@ -183,6 +183,7 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
||||
// Defer blocks are not yet fully supported in partial compilation.
|
||||
deferrableDeclToImportDecl: new Map(),
|
||||
deferrableTypes: new Map(),
|
||||
deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock,
|
||||
|
||||
encapsulation: metaObj.has('encapsulation') ?
|
||||
parseEncapsulation(metaObj.getValue('encapsulation')) :
|
||||
|
||||
@@ -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, 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';
|
||||
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';
|
||||
import ts from 'typescript';
|
||||
|
||||
import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../../cycles';
|
||||
@@ -19,7 +19,7 @@ import {IndexingContext} from '../../../indexer';
|
||||
import {DirectiveMeta, extractDirectiveTypeCheckMeta, HostDirectivesResolver, MatchSource, MetadataReader, MetadataRegistry, MetaKind, NgModuleMeta, PipeMeta, ResourceRegistry} from '../../../metadata';
|
||||
import {PartialEvaluator} from '../../../partial_evaluator';
|
||||
import {PerfEvent, PerfRecorder} from '../../../perf';
|
||||
import {ClassDeclaration, DeclarationNode, Decorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
|
||||
import {ClassDeclaration, DeclarationNode, Decorator, Import, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
|
||||
import {ComponentScopeKind, ComponentScopeReader, DtsModuleScopeResolver, LocalModuleScope, LocalModuleScopeRegistry, makeNotStandaloneDiagnostic, makeUnknownComponentImportDiagnostic, StandaloneScope, TypeCheckScopeRegistry} from '../../../scope';
|
||||
import {getDiagnosticNode, makeUnknownComponentDeferredImportDiagnostic} from '../../../scope/src/util';
|
||||
import {AnalysisOutput, CompilationMode, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence, ResolveResult} from '../../../transform';
|
||||
@@ -477,6 +477,21 @@ export class ComponentDecoratorHandler implements
|
||||
styles.push(...template.styles);
|
||||
}
|
||||
|
||||
// Collect all explicitly deferred symbols from the `@Component.deferredImports` field
|
||||
// (if it exists) and populate the `DeferredSymbolTracker` state. These operations are safe
|
||||
// for the local compilation mode, since they don't require accessing/resolving symbols
|
||||
// outside of the current source file.
|
||||
let explicitlyDeferredTypes: Map<string, string>|null = null;
|
||||
if (metadata.isStandalone && rawDeferredImports !== null) {
|
||||
const deferredTypes = this.collectExplicitlyDeferredSymbols(rawDeferredImports);
|
||||
for (const [deferredType, importDetails] of deferredTypes) {
|
||||
explicitlyDeferredTypes ??= new Map();
|
||||
explicitlyDeferredTypes.set(importDetails.name, importDetails.from);
|
||||
this.deferredSymbolTracker.markAsDeferrableCandidate(
|
||||
deferredType, importDetails.node, node, true /* isExplicitlyDeferred */);
|
||||
}
|
||||
}
|
||||
|
||||
const output: AnalysisOutput<ComponentAnalysisData> = {
|
||||
analysis: {
|
||||
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
||||
@@ -524,6 +539,7 @@ export class ComponentDecoratorHandler implements
|
||||
resolvedImports,
|
||||
rawDeferredImports,
|
||||
resolvedDeferredImports,
|
||||
explicitlyDeferredTypes,
|
||||
schemas,
|
||||
decorator: decorator?.node as ts.Decorator | null ?? null,
|
||||
},
|
||||
@@ -656,8 +672,43 @@ export class ComponentDecoratorHandler implements
|
||||
resolve(
|
||||
node: ClassDeclaration, analysis: Readonly<ComponentAnalysisData>,
|
||||
symbol: ComponentSymbol): ResolveResult<ComponentResolutionData> {
|
||||
const metadata = analysis.meta as Readonly<R3ComponentMetadata<R3TemplateDependencyMetadata>>;
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
const context = getSourceFile(node);
|
||||
|
||||
// Check if there are some import declarations that contain symbols used within
|
||||
// the `@Component.deferredImports` field, but those imports contain other symbols
|
||||
// and thus the declaration can not be removed.
|
||||
const nonRemovableImports =
|
||||
this.deferredSymbolTracker.getNonRemovableDeferredImports(context, node);
|
||||
if (nonRemovableImports.length > 0) {
|
||||
for (const importDecl of nonRemovableImports) {
|
||||
const diagnostic = makeDiagnostic(
|
||||
ErrorCode.DEFERRED_DEPENDENCY_IMPORTED_EAGERLY, importDecl,
|
||||
`This import contains symbols used in the \`@Component.deferredImports\` array ` +
|
||||
`of the \`${node.name.getText()}\` component, but also some other symbols that ` +
|
||||
`are not in any \`@Component.deferredImports\` array. This renders all these ` +
|
||||
`defer imports useless as this import remains and its module is eagerly loaded. ` +
|
||||
`To fix this, make sure that this import contains *only* symbols ` +
|
||||
`that are used within \`@Component.deferredImports\` arrays.`);
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
return {diagnostics};
|
||||
}
|
||||
|
||||
if (this.compilationMode === CompilationMode.LOCAL) {
|
||||
return {};
|
||||
return {
|
||||
data: {
|
||||
declarationListEmitMode: (!analysis.meta.isStandalone || analysis.rawImports !== null) ?
|
||||
DeclarationListEmitMode.RuntimeResolved :
|
||||
DeclarationListEmitMode.Direct,
|
||||
declarations: EMPTY_ARRAY,
|
||||
deferBlocks: this.locateDeferBlocksWithoutScope(analysis.template),
|
||||
deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerComponent,
|
||||
deferrableDeclToImportDecl: new Map(),
|
||||
deferrableTypes: new Map(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (this.semanticDepGraphUpdater !== null && analysis.baseClass instanceof Reference) {
|
||||
@@ -668,18 +719,14 @@ export class ComponentDecoratorHandler implements
|
||||
return {};
|
||||
}
|
||||
|
||||
const context = getSourceFile(node);
|
||||
const metadata = analysis.meta as Readonly<R3ComponentMetadata<R3TemplateDependencyMetadata>>;
|
||||
|
||||
|
||||
const data: ComponentResolutionData = {
|
||||
declarations: EMPTY_ARRAY,
|
||||
declarationListEmitMode: DeclarationListEmitMode.Direct,
|
||||
deferBlocks: new Map(),
|
||||
deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock,
|
||||
deferrableDeclToImportDecl: new Map(),
|
||||
deferrableTypes: new Map(),
|
||||
};
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
const scope = this.scopeReader.getScopeForComponent(node);
|
||||
if (scope !== null) {
|
||||
@@ -849,7 +896,7 @@ export class ComponentDecoratorHandler implements
|
||||
eagerlyUsed.has(decl.ref.node));
|
||||
|
||||
// Process information related to defer blocks
|
||||
this.resolveDeferBlocks(deferBlocks, declarations, data, analysis, eagerlyUsed, bound);
|
||||
this.resolveDeferBlocks(node, deferBlocks, declarations, data, analysis, eagerlyUsed, bound);
|
||||
|
||||
const cyclesFromDirectives = new Map<UsedDirective, Cycle>();
|
||||
const cyclesFromPipes = new Map<UsedPipe, Cycle>();
|
||||
@@ -1055,13 +1102,13 @@ export class ComponentDecoratorHandler implements
|
||||
return [];
|
||||
}
|
||||
|
||||
// Collect all explicitly deferred symbols from the `@Component.deferredImports` field
|
||||
// if it exists. As a part of that process we also populate the `DeferredSymbolTracker` state,
|
||||
// which is then used within the `collectDeferredSymbols` call.
|
||||
this.collectExplicitlyDeferredSymbols(analysis);
|
||||
const deferrableTypes = this.collectDeferredSymbols(resolution);
|
||||
|
||||
const meta: R3ComponentMetadata<R3TemplateDependency> = {...analysis.meta, ...resolution};
|
||||
const meta: R3ComponentMetadata<R3TemplateDependency> = {
|
||||
...analysis.meta,
|
||||
...resolution,
|
||||
deferrableTypes,
|
||||
};
|
||||
const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component));
|
||||
|
||||
removeDeferrableTypesFromComponentDecorator(analysis, deferrableTypes);
|
||||
@@ -1113,19 +1160,20 @@ export class ComponentDecoratorHandler implements
|
||||
return [];
|
||||
}
|
||||
|
||||
const deferrableTypes = this.collectExplicitlyDeferredSymbols(analysis);
|
||||
const meta: R3ComponentMetadata<R3TemplateDependency> = {
|
||||
...analysis.meta,
|
||||
declarationListEmitMode: (!analysis.meta.isStandalone || analysis.rawImports !== null) ?
|
||||
DeclarationListEmitMode.RuntimeResolved :
|
||||
DeclarationListEmitMode.Direct,
|
||||
declarations: EMPTY_ARRAY,
|
||||
deferBlocks: this.locateDeferBlocksWithoutScope(analysis.template),
|
||||
deferrableDeclToImportDecl: new Map(),
|
||||
deferrableTypes,
|
||||
};
|
||||
// In the local compilation mode we can only rely on the information available
|
||||
// within the `@Component.deferredImports` array, because in this mode compiler
|
||||
// doesn't have information on which dependencies belong to which defer blocks.
|
||||
const deferrableTypes = analysis.explicitlyDeferredTypes;
|
||||
|
||||
removeDeferrableTypesFromComponentDecorator(analysis, deferrableTypes);
|
||||
const meta = {
|
||||
...analysis.meta,
|
||||
...resolution,
|
||||
deferrableTypes: deferrableTypes ?? new Map(),
|
||||
} as R3ComponentMetadata<R3TemplateDependency>;
|
||||
|
||||
if (analysis.explicitlyDeferredTypes !== null) {
|
||||
removeDeferrableTypesFromComponentDecorator(analysis, analysis.explicitlyDeferredTypes);
|
||||
}
|
||||
|
||||
const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component));
|
||||
const def = compileComponentFromMetadata(meta, pool, makeBindingParser());
|
||||
@@ -1191,16 +1239,16 @@ export class ComponentDecoratorHandler implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects a list of deferrable symbols based on the `@Component.deferredImports` field.
|
||||
* Collects deferrable symbols from the `@Component.deferredImports` field.
|
||||
*/
|
||||
private collectExplicitlyDeferredSymbols(analysis: Readonly<ComponentAnalysisData>):
|
||||
Map<string, string> {
|
||||
const deferrableTypes = new Map<string, string>();
|
||||
if (!analysis.meta.isStandalone || analysis.rawDeferredImports === null ||
|
||||
!ts.isArrayLiteralExpression(analysis.rawDeferredImports))
|
||||
return deferrableTypes;
|
||||
private collectExplicitlyDeferredSymbols(rawDeferredImports: ts.Expression):
|
||||
Map<ts.Identifier, Import> {
|
||||
const deferredTypes = new Map<ts.Identifier, Import>();
|
||||
if (!ts.isArrayLiteralExpression(rawDeferredImports)) {
|
||||
return deferredTypes;
|
||||
}
|
||||
|
||||
for (const element of analysis.rawDeferredImports.elements) {
|
||||
for (const element of rawDeferredImports.elements) {
|
||||
const node = tryUnwrapForwardRef(element, this.reflector) || element;
|
||||
|
||||
if (!ts.isIdentifier(node)) {
|
||||
@@ -1210,11 +1258,10 @@ export class ComponentDecoratorHandler implements
|
||||
|
||||
const imp = this.reflector.getImportOfIdentifier(node);
|
||||
if (imp !== null) {
|
||||
deferrableTypes.set(imp.name, imp.from);
|
||||
this.deferredSymbolTracker.markAsExplicitlyDeferred(imp.node);
|
||||
deferredTypes.set(node, imp);
|
||||
}
|
||||
}
|
||||
return deferrableTypes;
|
||||
return deferredTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1248,6 +1295,7 @@ export class ComponentDecoratorHandler implements
|
||||
* available for the final `compile` step.
|
||||
*/
|
||||
private resolveDeferBlocks(
|
||||
componentClassDecl: ClassDeclaration,
|
||||
deferBlocks: Map<TmplAstDeferredBlock, BoundTarget<DirectiveMeta>>,
|
||||
deferrableDecls: Map<ClassDeclaration, AnyUsedType>,
|
||||
resolutionData: ComponentResolutionData,
|
||||
@@ -1306,13 +1354,13 @@ export class ComponentDecoratorHandler implements
|
||||
if (analysisData.meta.isStandalone) {
|
||||
if (analysisData.rawImports !== null) {
|
||||
this.registerDeferrableCandidates(
|
||||
analysisData.rawImports, false /* isDeferredImport */, allDeferredDecls,
|
||||
eagerlyUsedDecls, resolutionData);
|
||||
componentClassDecl, analysisData.rawImports, false /* isDeferredImport */,
|
||||
allDeferredDecls, eagerlyUsedDecls, resolutionData);
|
||||
}
|
||||
if (analysisData.rawDeferredImports !== null) {
|
||||
this.registerDeferrableCandidates(
|
||||
analysisData.rawDeferredImports, true /* isDeferredImport */, allDeferredDecls,
|
||||
eagerlyUsedDecls, resolutionData);
|
||||
componentClassDecl, analysisData.rawDeferredImports, true /* isDeferredImport */,
|
||||
allDeferredDecls, eagerlyUsedDecls, resolutionData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1323,7 +1371,7 @@ export class ComponentDecoratorHandler implements
|
||||
* candidates.
|
||||
*/
|
||||
private registerDeferrableCandidates(
|
||||
importsExpr: ts.Expression, isDeferredImport: boolean,
|
||||
componentClassDecl: ClassDeclaration, importsExpr: ts.Expression, isDeferredImport: boolean,
|
||||
allDeferredDecls: Set<ClassDeclaration>, eagerlyUsedDecls: Set<ClassDeclaration>,
|
||||
resolutionData: ComponentResolutionData) {
|
||||
if (!ts.isArrayLiteralExpression(importsExpr)) {
|
||||
@@ -1388,11 +1436,8 @@ export class ComponentDecoratorHandler implements
|
||||
resolutionData.deferrableDeclToImportDecl.set(
|
||||
decl.node as unknown as Expression, imp.node as unknown as Expression);
|
||||
|
||||
if (isDeferredImport) {
|
||||
this.deferredSymbolTracker.markAsExplicitlyDeferred(imp.node);
|
||||
} else {
|
||||
this.deferredSymbolTracker.markAsDeferrableCandidate(node, imp.node);
|
||||
}
|
||||
this.deferredSymbolTracker.markAsDeferrableCandidate(
|
||||
node, imp.node, componentClassDecl, isDeferredImport);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import {ParsedTemplateWithSource, StyleUrlMeta} from './resources';
|
||||
export type ComponentMetadataResolvedFields = SubsetOfKeys<
|
||||
R3ComponentMetadata<R3TemplateDependencyMetadata>,
|
||||
'declarations'|'declarationListEmitMode'|'deferBlocks'|'deferrableDeclToImportDecl'|
|
||||
'deferrableTypes'>;
|
||||
'deferrableTypes'|'deferBlockDepsEmitMode'>;
|
||||
|
||||
export interface ComponentAnalysisData {
|
||||
/**
|
||||
@@ -74,6 +74,11 @@ export interface ComponentAnalysisData {
|
||||
rawDeferredImports: ts.Expression|null;
|
||||
resolvedDeferredImports: Reference<ClassDeclaration>[]|null;
|
||||
|
||||
/**
|
||||
* Map of symbol name -> import path for types from `@Component.deferredImports` field.
|
||||
*/
|
||||
explicitlyDeferredTypes: Map<string, string>|null;
|
||||
|
||||
schemas: SchemaMetadata[]|null;
|
||||
|
||||
decorator: ts.Decorator|null;
|
||||
|
||||
@@ -8,18 +8,12 @@
|
||||
|
||||
import ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
import {getContainingImportDeclaration} from '../../reflection/src/typescript';
|
||||
|
||||
const AssumeEager = 'AssumeEager';
|
||||
type AssumeEager = typeof AssumeEager;
|
||||
|
||||
/**
|
||||
* A marker indicating that a symbol from an import declaration
|
||||
* was referenced in a `@Component.deferredImports` list.
|
||||
*/
|
||||
const ExplicitlyDeferred = 'ExplicitlyDeferred';
|
||||
type ExplicitlyDeferred = typeof ExplicitlyDeferred;
|
||||
|
||||
/**
|
||||
* Maps imported symbol name to a set of locations where the symbols is used
|
||||
* in a source file.
|
||||
@@ -34,7 +28,13 @@ type SymbolMap = Map<string, Set<ts.Identifier>|AssumeEager>;
|
||||
* in favor of using a dynamic import for cases when defer blocks are used.
|
||||
*/
|
||||
export class DeferredSymbolTracker {
|
||||
private readonly imports = new Map<ts.ImportDeclaration, ExplicitlyDeferred|SymbolMap>();
|
||||
private readonly imports = new Map<ts.ImportDeclaration, SymbolMap>();
|
||||
|
||||
/**
|
||||
* Map of a component class -> all import declarations that bring symbols
|
||||
* used within `@Component.deferredImports` field.
|
||||
*/
|
||||
private readonly explicitlyDeferredImports = new Map<ClassDeclaration, ts.ImportDeclaration[]>();
|
||||
|
||||
constructor(
|
||||
private readonly typeChecker: ts.TypeChecker,
|
||||
@@ -86,32 +86,47 @@ export class DeferredSymbolTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a given import declaration as explicitly deferred, since it's
|
||||
* used in the `@Component.deferredImports` field.
|
||||
* Retrieves a list of import declarations that contain symbols used within
|
||||
* `@Component.deferredImports` of a specific component class, but those imports
|
||||
* can not be removed, since there are other symbols imported alongside deferred
|
||||
* components.
|
||||
*/
|
||||
markAsExplicitlyDeferred(importDecl: ts.ImportDeclaration): void {
|
||||
this.imports.set(importDecl, ExplicitlyDeferred);
|
||||
getNonRemovableDeferredImports(sourceFile: ts.SourceFile, classDecl: ClassDeclaration):
|
||||
ts.ImportDeclaration[] {
|
||||
const affectedImports: ts.ImportDeclaration[] = [];
|
||||
const importDecls = this.explicitlyDeferredImports.get(classDecl) ?? [];
|
||||
for (const importDecl of importDecls) {
|
||||
if (importDecl.getSourceFile() === sourceFile && !this.canDefer(importDecl)) {
|
||||
affectedImports.push(importDecl);
|
||||
}
|
||||
}
|
||||
return affectedImports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a given identifier and an associated import declaration as a candidate
|
||||
* for defer loading.
|
||||
*/
|
||||
markAsDeferrableCandidate(identifier: ts.Identifier, importDecl: ts.ImportDeclaration): void {
|
||||
if (this.onlyExplicitDeferDependencyImports) {
|
||||
markAsDeferrableCandidate(
|
||||
identifier: ts.Identifier, importDecl: ts.ImportDeclaration,
|
||||
componentClassDecl: ClassDeclaration, isExplicitlyDeferred: boolean): void {
|
||||
if (this.onlyExplicitDeferDependencyImports && !isExplicitlyDeferred) {
|
||||
// Ignore deferrable candidates when only explicit deferred imports mode is enabled.
|
||||
// In that mode only dependencies from the `@Component.deferredImports` field are
|
||||
// defer-loadable.
|
||||
return;
|
||||
}
|
||||
|
||||
let symbolMap = this.imports.get(importDecl);
|
||||
|
||||
// Do we come across this import as a part of `@Component.deferredImports` already?
|
||||
if (symbolMap === ExplicitlyDeferred) {
|
||||
return;
|
||||
if (isExplicitlyDeferred) {
|
||||
if (this.explicitlyDeferredImports.has(componentClassDecl)) {
|
||||
this.explicitlyDeferredImports.get(componentClassDecl)!.push(importDecl);
|
||||
} else {
|
||||
this.explicitlyDeferredImports.set(componentClassDecl, [importDecl]);
|
||||
}
|
||||
}
|
||||
|
||||
let symbolMap = this.imports.get(importDecl);
|
||||
|
||||
// Do we come across this import for the first time?
|
||||
if (!symbolMap) {
|
||||
symbolMap = this.extractImportedSymbols(importDecl);
|
||||
@@ -147,10 +162,6 @@ export class DeferredSymbolTracker {
|
||||
}
|
||||
|
||||
const symbolsMap = this.imports.get(importDecl)!;
|
||||
if (symbolsMap === ExplicitlyDeferred) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const [symbol, refs] of symbolsMap) {
|
||||
if (refs === AssumeEager || refs.size > 0) {
|
||||
// There may be still eager references to this symbol.
|
||||
|
||||
@@ -25,7 +25,7 @@ import {setWrapHostForTest} from '../../src/transformers/compiler_host';
|
||||
|
||||
type TsConfigOptionsValue =
|
||||
string|boolean|number|null|TsConfigOptionsValue[]|{[key: string]: TsConfigOptionsValue};
|
||||
type TsConfigOptions = {
|
||||
export type TsConfigOptions = {
|
||||
[key: string]: TsConfigOptionsValue;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
|
||||
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
||||
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
|
||||
|
||||
import {NgtscTestEnvironment} from './env';
|
||||
import {NgtscTestEnvironment, TsConfigOptions} from './env';
|
||||
|
||||
const testFiles = loadStandardTestFiles();
|
||||
|
||||
@@ -20,8 +20,7 @@ runInEachFileSystem(() => {
|
||||
describe('local compilation', () => {
|
||||
let env!: NgtscTestEnvironment;
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
function tsconfig(extraOpts: TsConfigOptions = {}) {
|
||||
const tsconfig: {[key: string]: any} = {
|
||||
extends: '../tsconfig-base.json',
|
||||
compilerOptions: {
|
||||
@@ -30,9 +29,15 @@ runInEachFileSystem(() => {
|
||||
},
|
||||
angularCompilerOptions: {
|
||||
compilationMode: 'experimental-local',
|
||||
...extraOpts,
|
||||
},
|
||||
};
|
||||
env.write('tsconfig.json', JSON.stringify(tsconfig, null, 2));
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
tsconfig();
|
||||
});
|
||||
|
||||
it('should produce no TS semantic diagnostics', () => {
|
||||
@@ -460,8 +465,8 @@ runInEachFileSystem(() => {
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
|
||||
// If there is no style, don't generate css selectors on elements by setting encapsulation
|
||||
// to none (=2)
|
||||
// If there is no style, don't generate css selectors on elements by setting
|
||||
// encapsulation to none (=2)
|
||||
expect(jsContents).toContain('encapsulation: 2');
|
||||
});
|
||||
});
|
||||
@@ -1148,6 +1153,10 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
|
||||
describe('@defer', () => {
|
||||
beforeEach(() => {
|
||||
tsconfig({onlyExplicitDeferDependencyImports: true});
|
||||
});
|
||||
|
||||
it('should handle `@Component.deferredImports` field', () => {
|
||||
env.write('deferred-a.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
@@ -1358,6 +1367,179 @@ runInEachFileSystem(() => {
|
||||
'import("./deferred-b").then(m => m.DeferredCmpB)], ' +
|
||||
'(DeferredCmpA, DeferredCmpB) => {');
|
||||
});
|
||||
|
||||
it('should support importing multiple deferrable deps from a single file ' +
|
||||
'and use them within `@Component.deferrableImports` field',
|
||||
() => {
|
||||
env.write('deferred-deps.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'deferred-cmp-a',
|
||||
template: 'DeferredCmpA contents',
|
||||
})
|
||||
export class DeferredCmpA {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'deferred-cmp-b',
|
||||
template: 'DeferredCmpB contents',
|
||||
})
|
||||
export class DeferredCmpB {
|
||||
}
|
||||
`);
|
||||
|
||||
env.write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
// This import brings multiple symbols, but all of them are
|
||||
// used within @Component.deferredImports, thus this import
|
||||
// can be removed in favor of dynamic imports.
|
||||
import {DeferredCmpA, DeferredCmpB} from './deferred-deps';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
deferredImports: [DeferredCmpA],
|
||||
template: \`
|
||||
@defer {
|
||||
<deferred-cmp-a />
|
||||
}
|
||||
\`,
|
||||
})
|
||||
export class AppCmpA {}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
deferredImports: [DeferredCmpB],
|
||||
template: \`
|
||||
@defer {
|
||||
<deferred-cmp-b />
|
||||
}
|
||||
\`,
|
||||
})
|
||||
export class AppCmpB {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
|
||||
// Expect that we generate 2 different defer functions
|
||||
// (one for each component).
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'const AppCmpA_DeferFn = () => [' +
|
||||
'import("./deferred-deps").then(m => m.DeferredCmpA)]');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'const AppCmpB_DeferFn = () => [' +
|
||||
'import("./deferred-deps").then(m => m.DeferredCmpB)]');
|
||||
|
||||
// Make sure there are no eager imports present in the output.
|
||||
expect(jsContents).not.toContain(`from './deferred-deps'`);
|
||||
|
||||
// Defer instructions use per-component dependency function.
|
||||
expect(jsContents).toContain('ɵɵdefer(1, 0, AppCmpA_DeferFn)');
|
||||
expect(jsContents).toContain('ɵɵdefer(1, 0, AppCmpB_DeferFn)');
|
||||
|
||||
// Expect `ɵsetClassMetadataAsync` to contain dynamic imports too.
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'ɵsetClassMetadataAsync(AppCmpA, () => [' +
|
||||
'import("./deferred-deps").then(m => m.DeferredCmpA)]');
|
||||
expect(jsContents)
|
||||
.toContain(
|
||||
'ɵsetClassMetadataAsync(AppCmpB, () => [' +
|
||||
'import("./deferred-deps").then(m => m.DeferredCmpB)]');
|
||||
});
|
||||
|
||||
it('should produce a diagnostic in case imports with symbols used ' +
|
||||
'in `deferredImports` can not be removed',
|
||||
() => {
|
||||
env.write('deferred-deps.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'deferred-cmp-a',
|
||||
template: 'DeferredCmpA contents',
|
||||
})
|
||||
export class DeferredCmpA {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'deferred-cmp-b',
|
||||
template: 'DeferredCmpB contents',
|
||||
})
|
||||
export class DeferredCmpB {
|
||||
}
|
||||
|
||||
export function utilityFn() {}
|
||||
`);
|
||||
|
||||
env.write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
// This import can not be removed, since it'd contain
|
||||
// 'utilityFn' symbol once we remove 'DeferredCmpA' and
|
||||
// 'DeferredCmpB' and generate a dynamic import for it.
|
||||
// In this situation compiler produces a diagnostic to
|
||||
// indicate that.
|
||||
import {DeferredCmpA, DeferredCmpB, utilityFn} from './deferred-deps';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
deferredImports: [DeferredCmpA],
|
||||
template: \`
|
||||
@defer {
|
||||
<deferred-cmp-a />
|
||||
}
|
||||
\`,
|
||||
})
|
||||
export class AppCmpA {
|
||||
ngOnInit() {
|
||||
utilityFn();
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
deferredImports: [DeferredCmpB],
|
||||
template: \`
|
||||
@defer {
|
||||
<deferred-cmp-b />
|
||||
}
|
||||
\`,
|
||||
})
|
||||
export class AppCmpB {}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: 'Component without any dependencies'
|
||||
})
|
||||
export class ComponentWithoutDeps {}
|
||||
`);
|
||||
|
||||
const diags = env.driveDiagnostics();
|
||||
|
||||
// Expect 2 diagnostics: one for each component `AppCmpA` and `AppCmpB`,
|
||||
// since both of them refer to symbols from an import declaration that
|
||||
// can not be removed.
|
||||
expect(diags.length).toBe(2);
|
||||
|
||||
const components = ['AppCmpA', 'AppCmpB'];
|
||||
for (let i = 0; i < components.length; i++) {
|
||||
const component = components[i];
|
||||
const {code, messageText} = diags[i];
|
||||
expect(code).toBe(ngErrorCode(ErrorCode.DEFERRED_DEPENDENCY_IMPORTED_EAGERLY));
|
||||
expect(messageText)
|
||||
.toContain(
|
||||
'This import contains symbols used in the `@Component.deferredImports` ' +
|
||||
`array of the \`${component}\` component`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ 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, R3ComponentMetadata, R3DeferBlockMetadata, R3DirectiveDependencyMetadata, R3DirectiveMetadata, R3HostDirectiveMetadata, R3HostMetadata, R3InputMetadata, R3PipeDependencyMetadata, R3QueryMetadata, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata} from './render3/view/api';
|
||||
import {DeclarationListEmitMode, DeferBlockDepsEmitMode, R3ComponentMetadata, R3DeferBlockMetadata, 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';
|
||||
@@ -195,6 +195,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||
deferBlocks,
|
||||
deferrableTypes: new Map(),
|
||||
deferrableDeclToImportDecl: new Map(),
|
||||
deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock,
|
||||
|
||||
styles: [...facade.styles, ...template.styles],
|
||||
encapsulation: facade.encapsulation,
|
||||
@@ -477,6 +478,7 @@ function convertDeclareComponentFacadeToMetadata(
|
||||
deferBlocks,
|
||||
deferrableTypes: new Map(),
|
||||
deferrableDeclToImportDecl: new Map(),
|
||||
deferBlockDepsEmitMode: DeferBlockDepsEmitMode.PerBlock,
|
||||
|
||||
changeDetection: decl.changeDetection ?? ChangeDetectionStrategy.Default,
|
||||
encapsulation: decl.encapsulation ?? ViewEncapsulation.Emulated,
|
||||
|
||||
@@ -72,8 +72,8 @@ export function compileClassMetadata(metadata: R3ClassMetadata): o.Expression {
|
||||
* check to tree-shake away this code in production mode.
|
||||
*/
|
||||
export function compileComponentClassMetadata(
|
||||
metadata: R3ClassMetadata, deferrableTypes: Map<string, string>): o.Expression {
|
||||
if (deferrableTypes.size === 0) {
|
||||
metadata: R3ClassMetadata, deferrableTypes: Map<string, string>|null): o.Expression {
|
||||
if (deferrableTypes === null || deferrableTypes.size === 0) {
|
||||
// If there are no deferrable symbols - just generate a regular `setClassMetadata` call.
|
||||
return compileClassMetadata(metadata);
|
||||
}
|
||||
|
||||
@@ -125,6 +125,31 @@ export interface R3DirectiveMetadata {
|
||||
hostDirectives: R3HostDirectiveMetadata[]|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how dynamic imports for deferred dependencies should be emitted in the
|
||||
* generated output:
|
||||
* - 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)
|
||||
*/
|
||||
export const enum DeferBlockDepsEmitMode {
|
||||
/**
|
||||
* Dynamic imports are grouped on per-block basis.
|
||||
*
|
||||
* This is used in full compilation mode, when compiler has more information
|
||||
* about particular dependencies that belong to this block.
|
||||
*/
|
||||
PerBlock,
|
||||
|
||||
/**
|
||||
* Dynamic imports are grouped on per-component basis.
|
||||
*
|
||||
* In local compilation, compiler doesn't have enough information to determine
|
||||
* which deferred dependencies belong to which block. In this case we group all
|
||||
* dynamic imports into a single file on per-component basis.
|
||||
*/
|
||||
PerComponent,
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies how a list of declaration type references should be emitted into the generated code.
|
||||
*/
|
||||
@@ -247,14 +272,15 @@ export interface R3ComponentMetadata<DeclarationT extends R3TemplateDependency>
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* This map is populated **only** in local compilation mode and used by the
|
||||
* TemplateDefinitionBuilder to produce a defer function that loads
|
||||
* all dependencies. In full compilation mode this information is defined
|
||||
* on a `@defer` block level instead and dependency function is generated
|
||||
* on per-block level.
|
||||
*/
|
||||
deferrableTypes: Map<string, string>;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import {BoundEvent} from '../r3_ast';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, R3CompiledExpression, typeWithParameters} from '../util';
|
||||
|
||||
import {DeclarationListEmitMode, R3ComponentMetadata, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata, R3TemplateDependency} from './api';
|
||||
import {DeclarationListEmitMode, DeferBlockDepsEmitMode, R3ComponentMetadata, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata, R3TemplateDependency} from './api';
|
||||
import {MIN_STYLING_BINDING_SLOTS_REQUIRED, StylingBuilder, StylingInstructionCall} from './styling_builder';
|
||||
import {BindingScope, makeBindingParser, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn, TemplateDefinitionBuilder, ValueConverter} from './template';
|
||||
import {asLiteral, conditionallyCreateDirectiveBindingLiteral, CONTEXT_NAME, DefinitionMap, getInstructionStatements, getQueryPredicate, Instruction, RENDER_FLAGS, TEMPORARY_NAME, temporaryAllocator} from './util';
|
||||
@@ -219,11 +219,9 @@ export function compileComponentFromMetadata(
|
||||
// This is the main path currently used in compilation, which compiles the template with the
|
||||
// legacy `TemplateDefinitionBuilder`.
|
||||
|
||||
// `deferrableTypes` become available only when local compilation mode is
|
||||
// activated, in which case we generate a single function with all deferred
|
||||
// dependencies.
|
||||
let allDeferrableDepsFn: o.ReadVarExpr|null = null;
|
||||
if (meta.deferBlocks.size > 0 && meta.deferrableTypes.size > 0) {
|
||||
if (meta.deferBlocks.size > 0 && meta.deferrableTypes.size > 0 &&
|
||||
meta.deferBlockDepsEmitMode === DeferBlockDepsEmitMode.PerComponent) {
|
||||
const fnName = `${templateTypeName}_DeferFn`;
|
||||
allDeferrableDepsFn = createDeferredDepsFunction(constantPool, fnName, meta.deferrableTypes);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user