refactor(compiler): allows synchronous retrieving of metadata (#12908)

Allows non-normalized metadata to be retrieved synchronously.

Related to #7482
This commit is contained in:
Chuck Jazdzewski 2016-11-16 10:22:11 -08:00 committed by Victor Berchet
parent 8b2dfb2eca
commit 481c9b3258
4 changed files with 234 additions and 161 deletions

View File

@ -34,9 +34,9 @@ export class Extractor {
const programSymbols: StaticSymbol[] = const programSymbols: StaticSymbol[] =
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options); extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
return compiler const {ngModules, files} = compiler.analyzeAndValidateNgModules(
.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver) programSymbols, {transitiveModules: true}, this.metadataResolver);
.then(({files}) => { return compiler.loadNgModuleDirectives(ngModules).then(() => {
const errors: compiler.ParseError[] = []; const errors: compiler.ParseError[] = [];
files.forEach(file => { files.forEach(file => {

View File

@ -589,7 +589,7 @@ export interface CompileNgModuleDirectiveSummary extends CompileSummary {
exportedDirectives: CompileIdentifierMetadata[]; exportedDirectives: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[]; exportedPipes: CompileIdentifierMetadata[];
exportedModules: CompileNgModuleDirectiveSummary[]; exportedModules: CompileNgModuleDirectiveSummary[];
loadingPromises: Promise<any>[]; directiveLoaders: (() => Promise<void>)[];
} }
export type CompileNgModuleSummary = export type CompileNgModuleSummary =
@ -661,7 +661,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
exportedModules: this.exportedModules, exportedModules: this.exportedModules,
exportedDirectives: this.exportedDirectives, exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes, exportedPipes: this.exportedPipes,
loadingPromises: this.transitiveModule.loadingPromises directiveLoaders: this.transitiveModule.directiveLoaders
}; };
} }
@ -682,7 +682,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
exportedDirectives: this.exportedDirectives, exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes, exportedPipes: this.exportedPipes,
exportedModules: this.exportedModules, exportedModules: this.exportedModules,
loadingPromises: this.transitiveModule.loadingPromises directiveLoaders: this.transitiveModule.directiveLoaders
}; };
} }
} }
@ -695,7 +695,7 @@ export class TransitiveCompileNgModuleMetadata {
public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[], public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[],
public entryComponents: CompileIdentifierMetadata[], public entryComponents: CompileIdentifierMetadata[],
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[], public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
public loadingPromises: Promise<any>[]) { public directiveLoaders: (() => Promise<void>)[]) {
directives.forEach(dir => this.directivesSet.add(dir.reference)); directives.forEach(dir => this.directivesSet.add(dir.reference));
pipes.forEach(pipe => this.pipesSet.add(pipe.reference)); pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
} }

View File

@ -145,14 +145,92 @@ export class CompileMetadataResolver {
if (this._directiveCache.has(directiveType)) { if (this._directiveCache.has(directiveType)) {
return; return;
} }
directiveType = resolveForwardRef(directiveType);
const nonNormalizedMetadata = this.getNonNormalizedDirectiveMetadata(directiveType);
const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata) => {
const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
type: nonNormalizedMetadata.type,
isComponent: nonNormalizedMetadata.isComponent,
selector: nonNormalizedMetadata.selector,
exportAs: nonNormalizedMetadata.exportAs,
changeDetection: nonNormalizedMetadata.changeDetection,
inputs: nonNormalizedMetadata.inputs,
outputs: nonNormalizedMetadata.outputs,
hostListeners: nonNormalizedMetadata.hostListeners,
hostProperties: nonNormalizedMetadata.hostProperties,
hostAttributes: nonNormalizedMetadata.hostAttributes,
providers: nonNormalizedMetadata.providers,
viewProviders: nonNormalizedMetadata.viewProviders,
queries: nonNormalizedMetadata.queries,
viewQueries: nonNormalizedMetadata.viewQueries,
entryComponents: nonNormalizedMetadata.entryComponents,
template: templateMetadata
});
this._directiveCache.set(directiveType, normalizedDirMeta);
this._directiveSummaryCache.set(directiveType, normalizedDirMeta.toSummary());
return normalizedDirMeta;
};
if (nonNormalizedMetadata.isComponent) {
const templateMeta = this._directiveNormalizer.normalizeTemplate({
componentType: directiveType,
moduleUrl: nonNormalizedMetadata.type.moduleUrl,
encapsulation: nonNormalizedMetadata.template.encapsulation,
template: nonNormalizedMetadata.template.template,
templateUrl: nonNormalizedMetadata.template.templateUrl,
styles: nonNormalizedMetadata.template.styles,
styleUrls: nonNormalizedMetadata.template.styleUrls,
animations: nonNormalizedMetadata.template.animations,
interpolation: nonNormalizedMetadata.template.interpolation
});
if (templateMeta.syncResult) {
createDirectiveMetadata(templateMeta.syncResult);
return null;
} else {
if (isSync) {
throw new ComponentStillLoadingError(directiveType);
}
return templateMeta.asyncResult.then(createDirectiveMetadata);
}
} else {
// directive
createDirectiveMetadata(null);
return null;
}
}
getNonNormalizedDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
directiveType = resolveForwardRef(directiveType); directiveType = resolveForwardRef(directiveType);
const dirMeta = this._directiveResolver.resolve(directiveType); const dirMeta = this._directiveResolver.resolve(directiveType);
if (!dirMeta) { if (!dirMeta) {
return null; return null;
} }
let moduleUrl = staticTypeModuleUrl(directiveType); let moduleUrl = staticTypeModuleUrl(directiveType);
let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata;
if (dirMeta instanceof Component) {
// component
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
assertArrayOfStrings('styles', dirMeta.styles);
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
const animations = dirMeta.animations ?
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null;
nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({
encapsulation: dirMeta.encapsulation,
template: dirMeta.template,
templateUrl: dirMeta.templateUrl,
styles: dirMeta.styles,
styleUrls: dirMeta.styleUrls,
animations: animations,
interpolation: dirMeta.interpolation
});
}
const createDirectiveMetadata = (templateMeta: cpl.CompileTemplateMetadata) => {
let changeDetectionStrategy: ChangeDetectionStrategy = null; let changeDetectionStrategy: ChangeDetectionStrategy = null;
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = []; let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
@ -185,8 +263,7 @@ export class CompileMetadataResolver {
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = []; let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
if (isPresent(dirMeta.providers)) { if (isPresent(dirMeta.providers)) {
providers = this._getProvidersMetadata( providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata, dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`);
`providers for "${stringify(directiveType)}"`);
} }
let queries: cpl.CompileQueryMetadata[] = []; let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = []; let viewQueries: cpl.CompileQueryMetadata[] = [];
@ -195,12 +272,12 @@ export class CompileMetadataResolver {
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType); viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
} }
const meta = cpl.CompileDirectiveMetadata.create({ return cpl.CompileDirectiveMetadata.create({
selector: selector, selector: selector,
exportAs: dirMeta.exportAs, exportAs: dirMeta.exportAs,
isComponent: !!templateMeta, isComponent: !!nonNormalizedTemplateMetadata,
type: this._getTypeMetadata(directiveType, moduleUrl), type: this._getTypeMetadata(directiveType, moduleUrl),
template: templateMeta, template: nonNormalizedTemplateMetadata,
changeDetection: changeDetectionStrategy, changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs, inputs: dirMeta.inputs,
outputs: dirMeta.outputs, outputs: dirMeta.outputs,
@ -211,47 +288,6 @@ export class CompileMetadataResolver {
viewQueries: viewQueries, viewQueries: viewQueries,
entryComponents: entryComponentMetadata entryComponents: entryComponentMetadata
}); });
this._directiveCache.set(directiveType, meta);
this._directiveSummaryCache.set(directiveType, meta.toSummary());
return meta;
};
if (dirMeta instanceof Component) {
// component
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
assertArrayOfStrings('styles', dirMeta.styles);
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
const animations = dirMeta.animations ?
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null;
const templateMeta = this._directiveNormalizer.normalizeTemplate({
componentType: directiveType,
moduleUrl: moduleUrl,
encapsulation: dirMeta.encapsulation,
template: dirMeta.template,
templateUrl: dirMeta.templateUrl,
styles: dirMeta.styles,
styleUrls: dirMeta.styleUrls,
animations: animations,
interpolation: dirMeta.interpolation
});
if (templateMeta.syncResult) {
createDirectiveMetadata(templateMeta.syncResult);
return null;
} else {
if (isSync) {
throw new ComponentStillLoadingError(directiveType);
}
return templateMeta.asyncResult.then(createDirectiveMetadata);
}
} else {
// directive
createDirectiveMetadata(null);
return null;
}
} }
/** /**
@ -309,11 +345,20 @@ export class CompileMetadataResolver {
loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true): loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
{ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} { {ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} {
const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound); const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
const loading = const loading = ngModule ?
ngModule ? Promise.all(ngModule.transitiveModule.loadingPromises) : Promise.resolve(null); Promise.all(ngModule.transitiveModule.directiveLoaders.map(loader => loader())) :
Promise.resolve(null);
return {ngModule, loading}; return {ngModule, loading};
} }
/**
* Get the NgModule metadata without loading the directives.
*/
getUnloadedNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
cpl.CompileNgModuleMetadata {
return this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
}
private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true): private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
cpl.CompileNgModuleMetadata { cpl.CompileNgModuleMetadata {
moduleType = resolveForwardRef(moduleType); moduleType = resolveForwardRef(moduleType);
@ -396,10 +441,8 @@ export class CompileMetadataResolver {
transitiveModule.directives.push(declaredIdentifier); transitiveModule.directives.push(declaredIdentifier);
declaredDirectives.push(declaredIdentifier); declaredDirectives.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType); this._addTypeToModule(declaredType, moduleType);
const loadingPromise = this._loadDirectiveMetadata(declaredType, isSync); transitiveModule.directiveLoaders.push(
if (loadingPromise) { () => this._loadDirectiveMetadata(declaredType, isSync));
transitiveModule.loadingPromises.push(loadingPromise);
}
} else if (this._pipeResolver.isPipe(declaredType)) { } else if (this._pipeResolver.isPipe(declaredType)) {
transitiveModule.pipesSet.add(declaredType); transitiveModule.pipesSet.add(declaredType);
transitiveModule.pipes.push(declaredIdentifier); transitiveModule.pipes.push(declaredIdentifier);
@ -525,10 +568,10 @@ export class CompileMetadataResolver {
const directives = const directives =
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives)); flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes)); const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
const loadingPromises = const directiveLoaders =
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.loadingPromises)); ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.directiveLoaders));
return new cpl.TransitiveCompileNgModuleMetadata( return new cpl.TransitiveCompileNgModuleMetadata(
transitiveModules, providers, entryComponents, directives, pipes, loadingPromises); transitiveModules, providers, entryComponents, directives, pipes, directiveLoaders);
} }
private _getIdentifierMetadata(type: Type<any>, moduleUrl: string): private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
@ -584,20 +627,26 @@ export class CompileMetadataResolver {
return pipeSummary; return pipeSummary;
} }
private _loadPipeMetadata(pipeType: Type<any>): void { getOrLoadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
pipeType = resolveForwardRef(pipeType); let pipeMeta = this._pipeCache.get(pipeType);
const pipeMeta = this._pipeResolver.resolve(pipeType);
if (!pipeMeta) { if (!pipeMeta) {
return null; pipeMeta = this._loadPipeMetadata(pipeType);
}
return pipeMeta;
} }
const meta = new cpl.CompilePipeMetadata({ private _loadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
pipeType = resolveForwardRef(pipeType);
const pipeAnnotation = this._pipeResolver.resolve(pipeType);
const pipeMeta = new cpl.CompilePipeMetadata({
type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)), type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
name: pipeMeta.name, name: pipeAnnotation.name,
pure: pipeMeta.pure pure: pipeAnnotation.pure
}); });
this._pipeCache.set(pipeType, meta); this._pipeCache.set(pipeType, pipeMeta);
this._pipeSummaryCache.set(pipeType, meta.toSummary()); this._pipeSummaryCache.set(pipeType, pipeMeta.toSummary());
return pipeMeta;
} }
private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]): private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):

View File

@ -27,17 +27,46 @@ export class SourceModule {
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {} constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
} }
export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>;
symbolsMissingModule?: StaticSymbol[];
}
// Returns all the source files and a mapping from modules to directives // Returns all the source files and a mapping from modules to directives
export function analyzeNgModules( export function analyzeNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean}, programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): Promise<{ metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>, const {ngModules, symbolsMissingModule} =
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}> _createNgModules(programStaticSymbols, options, metadataResolver);
}> { return _analyzeNgModules(ngModules, symbolsMissingModule);
return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules);
} }
function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
const messages = result.symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
return result;
}
// Wait for the directives in the given modules have been loaded
export function loadNgModuleDirectives(ngModules: CompileNgModuleMetadata[]) {
return Promise
.all(ListWrapper.flatten(ngModules.map(
(ngModule) => ngModule.transitiveModule.directiveLoaders.map(loader => loader()))))
.then(() => {});
}
function _analyzeNgModules(
ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>(); const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule)); ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>(); const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
@ -82,6 +111,7 @@ function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
ngModuleByPipeOrDirective, ngModuleByPipeOrDirective,
// list modules and directives for every source file // list modules and directives for every source file
files, files,
ngModules: ngModuleMetas, symbolsMissingModule
}; };
} }
@ -100,8 +130,9 @@ export class OfflineCompiler {
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}): compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
Promise<SourceModule[]> { Promise<SourceModule[]> {
return analyzeNgModules(staticSymbols, options, this._metadataResolver) const {ngModuleByPipeOrDirective, files, ngModules} =
.then(({ngModuleByPipeOrDirective, files}) => { analyzeAndValidateNgModules(staticSymbols, options, this._metadataResolver);
return loadNgModuleDirectives(ngModules).then(() => {
const sourceModules = files.map( const sourceModules = files.map(
file => this._compileSrcFile( file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules)); file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
@ -328,22 +359,21 @@ function _splitTypescriptSuffix(path: string): string[] {
// Load the NgModules and check // Load the NgModules and check
// that all directives / pipes that are present in the program // that all directives / pipes that are present in the program
// are also declared by a module. // are also declared by a module.
function _loadNgModules( function _createNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean}, programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): Promise<CompileNgModuleMetadata[]> { metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>(); const ngModules = new Map<any, CompileNgModuleMetadata>();
const programPipesAndDirectives: StaticSymbol[] = []; const programPipesAndDirectives: StaticSymbol[] = [];
const ngModulePipesAndDirective = new Set<StaticSymbol>(); const ngModulePipesAndDirective = new Set<StaticSymbol>();
const loadingPromises: Promise<any>[] = [];
const addNgModule = (staticSymbol: any) => { const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol)) { if (ngModules.has(staticSymbol)) {
return false; return false;
} }
const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false); const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false);
if (ngModule) { if (ngModule) {
ngModules.set(ngModule.type.reference, ngModule); ngModules.set(ngModule.type.reference, ngModule);
loadingPromises.push(loading);
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference)); ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference)); ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
if (options.transitiveModules) { if (options.transitiveModules) {
@ -364,11 +394,5 @@ function _loadNgModules(
const symbolsMissingModule = const symbolsMissingModule =
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s)); programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
if (symbolsMissingModule.length) { return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
const messages = symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
return Promise.all(loadingPromises).then(() => Array.from(ngModules.values()));
} }