refactor(compiler-cli): Track external component resources in ResourceRegistry (#39373)
In addition to the template mapping that already existed, we want to also track the mapping for external style files. We also store the `ts.Expression` in the registry so external tools can look up a resource on a component by expression and avoid reading the value. PR Close #39373
This commit is contained in:
parent
a7518eb631
commit
371fb9a955
|
@ -14,7 +14,7 @@ import {CycleAnalyzer, ImportGraph} from '../../../src/ngtsc/cycles';
|
||||||
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
|
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
|
||||||
import {absoluteFrom, absoluteFromSourceFile, dirname, FileSystem, LogicalFileSystem, resolve} from '../../../src/ngtsc/file_system';
|
import {absoluteFrom, absoluteFromSourceFile, dirname, FileSystem, LogicalFileSystem, resolve} from '../../../src/ngtsc/file_system';
|
||||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports';
|
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports';
|
||||||
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, TemplateMapping} from '../../../src/ngtsc/metadata';
|
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../../src/ngtsc/metadata';
|
||||||
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
|
||||||
import {DecoratorHandler} from '../../../src/ngtsc/transform';
|
import {DecoratorHandler} from '../../../src/ngtsc/transform';
|
||||||
|
@ -94,7 +94,7 @@ export class DecorationAnalyzer {
|
||||||
handlers: DecoratorHandler<unknown, unknown, unknown>[] = [
|
handlers: DecoratorHandler<unknown, unknown, unknown>[] = [
|
||||||
new ComponentDecoratorHandler(
|
new ComponentDecoratorHandler(
|
||||||
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
|
this.reflectionHost, this.evaluator, this.fullRegistry, this.fullMetaReader,
|
||||||
this.scopeRegistry, this.scopeRegistry, new TemplateMapping(), this.isCore,
|
this.scopeRegistry, this.scopeRegistry, new ResourceRegistry(), this.isCore,
|
||||||
this.resourceManager, this.rootDirs, !!this.compilerOptions.preserveWhitespaces,
|
this.resourceManager, this.rootDirs, !!this.compilerOptions.preserveWhitespaces,
|
||||||
/* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat,
|
/* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat,
|
||||||
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
|
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {absoluteFrom, relative, resolve} from '../../file_system';
|
||||||
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||||
import {DependencyTracker} from '../../incremental/api';
|
import {DependencyTracker} from '../../incremental/api';
|
||||||
import {IndexingContext} from '../../indexer';
|
import {IndexingContext} from '../../indexer';
|
||||||
import {ClassPropertyMapping, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, TemplateMapping} from '../../metadata';
|
import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, Resource, ResourceRegistry} from '../../metadata';
|
||||||
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
|
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
|
||||||
|
@ -70,6 +70,8 @@ export interface ComponentAnalysisData {
|
||||||
* require an Angular factory definition at runtime.
|
* require an Angular factory definition at runtime.
|
||||||
*/
|
*/
|
||||||
viewProvidersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
viewProvidersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
||||||
|
|
||||||
|
resources: ComponentResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentResolutionData = Pick<R3ComponentMetadata, ComponentMetadataResolvedFields>;
|
export type ComponentResolutionData = Pick<R3ComponentMetadata, ComponentMetadataResolvedFields>;
|
||||||
|
@ -83,7 +85,7 @@ export class ComponentDecoratorHandler implements
|
||||||
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
||||||
private metaRegistry: MetadataRegistry, private metaReader: MetadataReader,
|
private metaRegistry: MetadataRegistry, private metaReader: MetadataReader,
|
||||||
private scopeReader: ComponentScopeReader, private scopeRegistry: LocalModuleScopeRegistry,
|
private scopeReader: ComponentScopeReader, private scopeRegistry: LocalModuleScopeRegistry,
|
||||||
private templateMapping: TemplateMapping, private isCore: boolean,
|
private resourceRegistry: ResourceRegistry, private isCore: boolean,
|
||||||
private resourceLoader: ResourceLoader, private rootDirs: ReadonlyArray<string>,
|
private resourceLoader: ResourceLoader, private rootDirs: ReadonlyArray<string>,
|
||||||
private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean,
|
private defaultPreserveWhitespaces: boolean, private i18nUseExternalIds: boolean,
|
||||||
private enableI18nLegacyMessageIdFormat: boolean,
|
private enableI18nLegacyMessageIdFormat: boolean,
|
||||||
|
@ -259,6 +261,9 @@ export class ComponentDecoratorHandler implements
|
||||||
template = this._extractInlineTemplate(node, decorator, component, containingFile);
|
template = this._extractInlineTemplate(node, decorator, component, containingFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const templateResource = template.isInline ?
|
||||||
|
null :
|
||||||
|
{path: absoluteFrom(template.templateUrl), expression: template.sourceMapping.node};
|
||||||
|
|
||||||
let diagnostics: ts.Diagnostic[]|undefined = undefined;
|
let diagnostics: ts.Diagnostic[]|undefined = undefined;
|
||||||
|
|
||||||
|
@ -287,6 +292,7 @@ export class ComponentDecoratorHandler implements
|
||||||
// component.
|
// component.
|
||||||
let styles: string[]|null = null;
|
let styles: string[]|null = null;
|
||||||
|
|
||||||
|
const styleResources = this._extractStyleResources(component, containingFile);
|
||||||
const styleUrls = this._extractStyleUrls(component, template.styleUrls);
|
const styleUrls = this._extractStyleUrls(component, template.styleUrls);
|
||||||
if (styleUrls !== null) {
|
if (styleUrls !== null) {
|
||||||
if (styles === null) {
|
if (styles === null) {
|
||||||
|
@ -359,6 +365,10 @@ export class ComponentDecoratorHandler implements
|
||||||
template,
|
template,
|
||||||
providersRequiringFactory,
|
providersRequiringFactory,
|
||||||
viewProvidersRequiringFactory,
|
viewProvidersRequiringFactory,
|
||||||
|
resources: {
|
||||||
|
styles: styleResources,
|
||||||
|
template: templateResource,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
diagnostics,
|
diagnostics,
|
||||||
};
|
};
|
||||||
|
@ -385,10 +395,7 @@ export class ComponentDecoratorHandler implements
|
||||||
...analysis.typeCheckMeta,
|
...analysis.typeCheckMeta,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!analysis.template.isInline) {
|
this.resourceRegistry.registerResources(analysis.resources, node);
|
||||||
this.templateMapping.register(resolve(analysis.template.templateUrl), node);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.injectableRegistry.registerInjectable(node);
|
this.injectableRegistry.registerInjectable(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,6 +664,25 @@ export class ComponentDecoratorHandler implements
|
||||||
return styleUrls as string[];
|
return styleUrls as string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _extractStyleResources(component: Map<string, ts.Expression>, containingFile: string):
|
||||||
|
ReadonlySet<Resource> {
|
||||||
|
const styleUrlsExpr = component.get('styleUrls');
|
||||||
|
// If styleUrls is a literal array of strings, process each resource url individually and
|
||||||
|
// register them. Otherwise, give up and return an empty set.
|
||||||
|
if (styleUrlsExpr === undefined || !ts.isArrayLiteralExpression(styleUrlsExpr) ||
|
||||||
|
!styleUrlsExpr.elements.every(e => ts.isStringLiteralLike(e))) {
|
||||||
|
return new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
const externalStyles = new Set<Resource>();
|
||||||
|
for (const expression of styleUrlsExpr.elements) {
|
||||||
|
const resourceUrl =
|
||||||
|
this.resourceLoader.resolve((expression as ts.StringLiteralLike).text, containingFile);
|
||||||
|
externalStyles.add({path: absoluteFrom(resourceUrl), expression});
|
||||||
|
}
|
||||||
|
return externalStyles;
|
||||||
|
}
|
||||||
|
|
||||||
private _preloadAndParseTemplate(
|
private _preloadAndParseTemplate(
|
||||||
node: ClassDeclaration, decorator: Decorator, component: Map<string, ts.Expression>,
|
node: ClassDeclaration, decorator: Decorator, component: Map<string, ts.Expression>,
|
||||||
containingFile: string): Promise<ParsedTemplate|null> {
|
containingFile: string): Promise<ParsedTemplate|null> {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||||
import {absoluteFrom} from '../../file_system';
|
import {absoluteFrom} from '../../file_system';
|
||||||
import {runInEachFileSystem} from '../../file_system/testing';
|
import {runInEachFileSystem} from '../../file_system/testing';
|
||||||
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||||
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, TemplateMapping} from '../../metadata';
|
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../metadata';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||||
|
@ -69,7 +69,7 @@ runInEachFileSystem(() => {
|
||||||
|
|
||||||
const handler = new ComponentDecoratorHandler(
|
const handler = new ComponentDecoratorHandler(
|
||||||
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, scopeRegistry,
|
reflectionHost, evaluator, metaRegistry, metaReader, scopeRegistry, scopeRegistry,
|
||||||
new TemplateMapping(),
|
new ResourceRegistry(),
|
||||||
/* isCore */ false, new NoopResourceLoader(), /* rootDirs */[''],
|
/* isCore */ false, new NoopResourceLoader(), /* rootDirs */[''],
|
||||||
/* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true,
|
/* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true,
|
||||||
/* enableI18nLegacyMessageIdFormat */ false,
|
/* enableI18nLegacyMessageIdFormat */ false,
|
||||||
|
|
|
@ -17,11 +17,11 @@ import {LogicalFileSystem, resolve} from '../../file_system';
|
||||||
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
|
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
|
||||||
import {IncrementalBuildStrategy, IncrementalDriver} from '../../incremental';
|
import {IncrementalBuildStrategy, IncrementalDriver} from '../../incremental';
|
||||||
import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer';
|
import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer';
|
||||||
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, TemplateMapping} from '../../metadata';
|
import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, ResourceRegistry} from '../../metadata';
|
||||||
import {ModuleWithProvidersScanner} from '../../modulewithproviders';
|
import {ModuleWithProvidersScanner} from '../../modulewithproviders';
|
||||||
import {PartialEvaluator} from '../../partial_evaluator';
|
import {PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {NOOP_PERF_RECORDER, PerfRecorder} from '../../perf';
|
import {NOOP_PERF_RECORDER, PerfRecorder} from '../../perf';
|
||||||
import {DeclarationNode, TypeScriptReflectionHost} from '../../reflection';
|
import {DeclarationNode, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||||
import {AdapterResourceLoader} from '../../resource';
|
import {AdapterResourceLoader} from '../../resource';
|
||||||
import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
|
import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
|
||||||
import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope';
|
||||||
|
@ -52,7 +52,7 @@ interface LazyCompilationState {
|
||||||
aliasingHost: AliasingHost|null;
|
aliasingHost: AliasingHost|null;
|
||||||
refEmitter: ReferenceEmitter;
|
refEmitter: ReferenceEmitter;
|
||||||
templateTypeChecker: TemplateTypeChecker;
|
templateTypeChecker: TemplateTypeChecker;
|
||||||
templateMapping: TemplateMapping;
|
resourceRegistry: ResourceRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -239,8 +239,30 @@ export class NgCompiler {
|
||||||
* Retrieves the `ts.Declaration`s for any component(s) which use the given template file.
|
* Retrieves the `ts.Declaration`s for any component(s) which use the given template file.
|
||||||
*/
|
*/
|
||||||
getComponentsWithTemplateFile(templateFilePath: string): ReadonlySet<DeclarationNode> {
|
getComponentsWithTemplateFile(templateFilePath: string): ReadonlySet<DeclarationNode> {
|
||||||
const {templateMapping} = this.ensureAnalyzed();
|
const {resourceRegistry} = this.ensureAnalyzed();
|
||||||
return templateMapping.getComponentsWithTemplate(resolve(templateFilePath));
|
return resourceRegistry.getComponentsWithTemplate(resolve(templateFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the `ts.Declaration`s for any component(s) which use the given template file.
|
||||||
|
*/
|
||||||
|
getComponentsWithStyleFile(styleFilePath: string): ReadonlySet<DeclarationNode> {
|
||||||
|
const {resourceRegistry} = this.ensureAnalyzed();
|
||||||
|
return resourceRegistry.getComponentsWithStyle(resolve(styleFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves external resources for the given component.
|
||||||
|
*/
|
||||||
|
getComponentResources(classDecl: DeclarationNode): ComponentResources|null {
|
||||||
|
if (!isNamedClassDeclaration(classDecl)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const {resourceRegistry} = this.ensureAnalyzed();
|
||||||
|
const styles = resourceRegistry.getStyles(classDecl);
|
||||||
|
const template = resourceRegistry.getTemplate(classDecl);
|
||||||
|
|
||||||
|
return {styles, template};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -734,13 +756,13 @@ export class NgCompiler {
|
||||||
const isCore = isAngularCorePackage(this.tsProgram);
|
const isCore = isAngularCorePackage(this.tsProgram);
|
||||||
|
|
||||||
const defaultImportTracker = new DefaultImportTracker();
|
const defaultImportTracker = new DefaultImportTracker();
|
||||||
const templateMapping = new TemplateMapping();
|
const resourceRegistry = new ResourceRegistry();
|
||||||
|
|
||||||
// Set up the IvyCompilation, which manages state for the Ivy transformer.
|
// Set up the IvyCompilation, which manages state for the Ivy transformer.
|
||||||
const handlers: DecoratorHandler<unknown, unknown, unknown>[] = [
|
const handlers: DecoratorHandler<unknown, unknown, unknown>[] = [
|
||||||
new ComponentDecoratorHandler(
|
new ComponentDecoratorHandler(
|
||||||
reflector, evaluator, metaRegistry, metaReader, scopeReader, scopeRegistry,
|
reflector, evaluator, metaRegistry, metaReader, scopeReader, scopeRegistry,
|
||||||
templateMapping, isCore, this.resourceManager, this.adapter.rootDirs,
|
resourceRegistry, isCore, this.resourceManager, this.adapter.rootDirs,
|
||||||
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false,
|
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false,
|
||||||
this.options.enableI18nLegacyMessageIdFormat !== false,
|
this.options.enableI18nLegacyMessageIdFormat !== false,
|
||||||
this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer,
|
this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer,
|
||||||
|
@ -797,7 +819,7 @@ export class NgCompiler {
|
||||||
aliasingHost,
|
aliasingHost,
|
||||||
refEmitter,
|
refEmitter,
|
||||||
templateTypeChecker,
|
templateTypeChecker,
|
||||||
templateMapping,
|
resourceRegistry,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,127 @@ runInEachFileSystem(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getComponentsWithStyle', () => {
|
||||||
|
it('should return the component(s) using a style file', () => {
|
||||||
|
const styleFile = _('/style.css');
|
||||||
|
fs.writeFile(styleFile, `/* This is the style, used by components CmpA and CmpC */`);
|
||||||
|
const cmpAFile = _('/cmp-a.ts');
|
||||||
|
fs.writeFile(cmpAFile, `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
@Component({
|
||||||
|
selector: 'cmp-a',
|
||||||
|
template: '',
|
||||||
|
styleUrls: ['./style.css'],
|
||||||
|
})
|
||||||
|
export class CmpA {}
|
||||||
|
`);
|
||||||
|
const cmpBFile = _('/cmp-b.ts');
|
||||||
|
fs.writeFile(cmpBFile, `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
@Component({
|
||||||
|
selector: 'cmp-b',
|
||||||
|
template: '',
|
||||||
|
styles: ['/* CmpB does not use external style */'],
|
||||||
|
})
|
||||||
|
export class CmpB {}
|
||||||
|
`);
|
||||||
|
const cmpCFile = _('/cmp-c.ts');
|
||||||
|
fs.writeFile(cmpCFile, `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
@Component({
|
||||||
|
selector: 'cmp-c',
|
||||||
|
template: '',
|
||||||
|
styleUrls: ['./style.css']
|
||||||
|
})
|
||||||
|
export class CmpC {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const options: NgCompilerOptions = {};
|
||||||
|
|
||||||
|
const baseHost = new NgtscCompilerHost(getFileSystem(), options);
|
||||||
|
const host = NgCompilerHost.wrap(
|
||||||
|
baseHost, [cmpAFile, cmpBFile, cmpCFile], options, /* oldProgram */ null);
|
||||||
|
const program = ts.createProgram({host, options, rootNames: host.inputFiles});
|
||||||
|
const CmpA = getClass(getSourceFileOrError(program, cmpAFile), 'CmpA');
|
||||||
|
const CmpC = getClass(getSourceFileOrError(program, cmpCFile), 'CmpC');
|
||||||
|
const compiler = new NgCompiler(
|
||||||
|
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||||
|
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false);
|
||||||
|
const components = compiler.getComponentsWithStyleFile(styleFile);
|
||||||
|
expect(components).toEqual(new Set([CmpA, CmpC]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getComponentResources', () => {
|
||||||
|
it('should return the component resources', () => {
|
||||||
|
const styleFile = _('/style.css');
|
||||||
|
fs.writeFile(styleFile, `/* This is the template, used by components CmpA */`);
|
||||||
|
const styleFile2 = _('/style2.css');
|
||||||
|
fs.writeFile(styleFile2, `/* This is the template, used by components CmpA */`);
|
||||||
|
const templateFile = _('/template.ng.html');
|
||||||
|
fs.writeFile(templateFile, `This is the template, used by components CmpA`);
|
||||||
|
const cmpAFile = _('/cmp-a.ts');
|
||||||
|
fs.writeFile(cmpAFile, `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
@Component({
|
||||||
|
selector: 'cmp-a',
|
||||||
|
templateUrl: './template.ng.html',
|
||||||
|
styleUrls: ['./style.css', '../../style2.css'],
|
||||||
|
})
|
||||||
|
export class CmpA {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const options: NgCompilerOptions = {};
|
||||||
|
|
||||||
|
const baseHost = new NgtscCompilerHost(getFileSystem(), options);
|
||||||
|
const host = NgCompilerHost.wrap(baseHost, [cmpAFile], options, /* oldProgram */ null);
|
||||||
|
const program = ts.createProgram({host, options, rootNames: host.inputFiles});
|
||||||
|
const CmpA = getClass(getSourceFileOrError(program, cmpAFile), 'CmpA');
|
||||||
|
const compiler = new NgCompiler(
|
||||||
|
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||||
|
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false);
|
||||||
|
const resources = compiler.getComponentResources(CmpA);
|
||||||
|
expect(resources).not.toBeNull();
|
||||||
|
const {template, styles} = resources!;
|
||||||
|
expect(template !.path).toEqual(templateFile);
|
||||||
|
expect(styles.size).toEqual(2);
|
||||||
|
const actualPaths = new Set(Array.from(styles).map(r => r.path));
|
||||||
|
expect(actualPaths).toEqual(new Set([styleFile, styleFile2]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not return component style resources if not an array of strings', () => {
|
||||||
|
const styleFile = _('/style.css');
|
||||||
|
fs.writeFile(styleFile, `/* This is the template, used by components CmpA */`);
|
||||||
|
const styleFile2 = _('/style2.css');
|
||||||
|
fs.writeFile(styleFile2, `/* This is the template, used by components CmpA */`);
|
||||||
|
const cmpAFile = _('/cmp-a.ts');
|
||||||
|
fs.writeFile(cmpAFile, `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
const STYLES = ['./style.css', '../../style2.css'];
|
||||||
|
@Component({
|
||||||
|
selector: 'cmp-a',
|
||||||
|
template: '',
|
||||||
|
styleUrls: STYLES,
|
||||||
|
})
|
||||||
|
export class CmpA {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const options: NgCompilerOptions = {};
|
||||||
|
|
||||||
|
const baseHost = new NgtscCompilerHost(getFileSystem(), options);
|
||||||
|
const host = NgCompilerHost.wrap(baseHost, [cmpAFile], options, /* oldProgram */ null);
|
||||||
|
const program = ts.createProgram({host, options, rootNames: host.inputFiles});
|
||||||
|
const CmpA = getClass(getSourceFileOrError(program, cmpAFile), 'CmpA');
|
||||||
|
const compiler = new NgCompiler(
|
||||||
|
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||||
|
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false);
|
||||||
|
const resources = compiler.getComponentResources(CmpA);
|
||||||
|
expect(resources).not.toBeNull();
|
||||||
|
const {styles} = resources!;
|
||||||
|
expect(styles.size).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getResourceDependencies', () => {
|
describe('getResourceDependencies', () => {
|
||||||
it('should return resource dependencies of a component source file', () => {
|
it('should return resource dependencies of a component source file', () => {
|
||||||
const COMPONENT = _('/cmp.ts');
|
const COMPONENT = _('/cmp.ts');
|
||||||
|
|
|
@ -10,6 +10,6 @@ export * from './src/api';
|
||||||
export {DtsMetadataReader} from './src/dts';
|
export {DtsMetadataReader} from './src/dts';
|
||||||
export {flattenInheritedDirectiveMetadata} from './src/inheritance';
|
export {flattenInheritedDirectiveMetadata} from './src/inheritance';
|
||||||
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
|
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
|
||||||
export {TemplateRegistry as TemplateMapping} from './src/template_mapping';
|
export {ResourceRegistry, Resource, ComponentResources} from './src/resource_registry';
|
||||||
export {extractDirectiveTypeCheckMeta, CompoundMetadataReader} from './src/util';
|
export {extractDirectiveTypeCheckMeta, CompoundMetadataReader} from './src/util';
|
||||||
export {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, InputOrOutput} from './src/property_mapping';
|
export {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, InputOrOutput} from './src/property_mapping';
|
|
@ -0,0 +1,107 @@
|
||||||
|
/**
|
||||||
|
* @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 ts from 'typescript';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath} from '../../file_system';
|
||||||
|
import {ClassDeclaration} from '../../reflection';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an external resource for a component and contains the `AbsoluteFsPath`
|
||||||
|
* to the file which was resolved by evaluating the `ts.Expression` (generally, a relative or
|
||||||
|
* absolute string path to the resource).
|
||||||
|
*/
|
||||||
|
export interface Resource {
|
||||||
|
path: AbsoluteFsPath;
|
||||||
|
expression: ts.Expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the external resources of a component.
|
||||||
|
*
|
||||||
|
* If the component uses an inline template, the template resource will be `null`.
|
||||||
|
* If the component does not have external styles, the `styles` `Set` will be empty.
|
||||||
|
*/
|
||||||
|
export interface ComponentResources {
|
||||||
|
template: Resource|null;
|
||||||
|
styles: ReadonlySet<Resource>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the mapping between external template/style files and the component(s) which use them.
|
||||||
|
*
|
||||||
|
* This information is produced during analysis of the program and is used mainly to support
|
||||||
|
* external tooling, for which such a mapping is challenging to determine without compiler
|
||||||
|
* assistance.
|
||||||
|
*/
|
||||||
|
export class ResourceRegistry {
|
||||||
|
private templateToComponentsMap = new Map<AbsoluteFsPath, Set<ClassDeclaration>>();
|
||||||
|
private componentToTemplateMap = new Map<ClassDeclaration, Resource>();
|
||||||
|
private componentToStylesMap = new Map<ClassDeclaration, Set<Resource>>();
|
||||||
|
private styleToComponentsMap = new Map<AbsoluteFsPath, Set<ClassDeclaration>>();
|
||||||
|
|
||||||
|
getComponentsWithTemplate(template: AbsoluteFsPath): ReadonlySet<ClassDeclaration> {
|
||||||
|
if (!this.templateToComponentsMap.has(template)) {
|
||||||
|
return new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.templateToComponentsMap.get(template)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerResources(resources: ComponentResources, component: ClassDeclaration) {
|
||||||
|
if (resources.template !== null) {
|
||||||
|
this.registerTemplate(resources.template, component);
|
||||||
|
}
|
||||||
|
for (const style of resources.styles) {
|
||||||
|
this.registerStyle(style, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerTemplate(templateResource: Resource, component: ClassDeclaration): void {
|
||||||
|
const {path} = templateResource;
|
||||||
|
if (!this.templateToComponentsMap.has(path)) {
|
||||||
|
this.templateToComponentsMap.set(path, new Set());
|
||||||
|
}
|
||||||
|
this.templateToComponentsMap.get(path)!.add(component);
|
||||||
|
this.componentToTemplateMap.set(component, templateResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTemplate(component: ClassDeclaration): Resource|null {
|
||||||
|
if (!this.componentToTemplateMap.has(component)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.componentToTemplateMap.get(component)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerStyle(styleResource: Resource, component: ClassDeclaration): void {
|
||||||
|
const {path} = styleResource;
|
||||||
|
if (!this.componentToStylesMap.has(component)) {
|
||||||
|
this.componentToStylesMap.set(component, new Set());
|
||||||
|
}
|
||||||
|
if (!this.styleToComponentsMap.has(path)) {
|
||||||
|
this.styleToComponentsMap.set(path, new Set());
|
||||||
|
}
|
||||||
|
this.styleToComponentsMap.get(path)!.add(component);
|
||||||
|
this.componentToStylesMap.get(component)!.add(styleResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStyles(component: ClassDeclaration): Set<Resource> {
|
||||||
|
if (!this.componentToStylesMap.has(component)) {
|
||||||
|
return new Set();
|
||||||
|
}
|
||||||
|
return this.componentToStylesMap.get(component)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
getComponentsWithStyle(styleUrl: AbsoluteFsPath): ReadonlySet<ClassDeclaration> {
|
||||||
|
if (!this.styleToComponentsMap.has(styleUrl)) {
|
||||||
|
return new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.styleToComponentsMap.get(styleUrl)!;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,36 +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 {AbsoluteFsPath} from '../../file_system';
|
|
||||||
import {ClassDeclaration} from '../../reflection';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tracks the mapping between external template files and the component(s) which use them.
|
|
||||||
*
|
|
||||||
* This information is produced during analysis of the program and is used mainly to support
|
|
||||||
* external tooling, for which such a mapping is challenging to determine without compiler
|
|
||||||
* assistance.
|
|
||||||
*/
|
|
||||||
export class TemplateRegistry {
|
|
||||||
private map = new Map<AbsoluteFsPath, Set<ClassDeclaration>>();
|
|
||||||
|
|
||||||
getComponentsWithTemplate(template: AbsoluteFsPath): ReadonlySet<ClassDeclaration> {
|
|
||||||
if (!this.map.has(template)) {
|
|
||||||
return new Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.map.get(template)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
register(template: AbsoluteFsPath, component: ClassDeclaration): void {
|
|
||||||
if (!this.map.has(template)) {
|
|
||||||
this.map.set(template, new Set());
|
|
||||||
}
|
|
||||||
this.map.get(template)!.add(component);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue