diff --git a/modules/@angular/compiler-cli/src/extractor.ts b/modules/@angular/compiler-cli/src/extractor.ts index 52977b538a8b6..e73fa40273d4f 100644 --- a/modules/@angular/compiler-cli/src/extractor.ts +++ b/modules/@angular/compiler-cli/src/extractor.ts @@ -34,34 +34,34 @@ export class Extractor { const programSymbols: StaticSymbol[] = extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options); - return compiler - .analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver) - .then(({files}) => { - const errors: compiler.ParseError[] = []; - - files.forEach(file => { - const compMetas: compiler.CompileDirectiveMetadata[] = []; - file.directives.forEach(directiveType => { - const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType); - if (dirMeta && dirMeta.isComponent) { - compMetas.push(dirMeta); - } - }); - compMetas.forEach(compMeta => { - const html = compMeta.template.template; - const interpolationConfig = - compiler.InterpolationConfig.fromArray(compMeta.template.interpolation); - errors.push( - ...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig)); - }); - }); - - if (errors.length) { - throw new Error(errors.map(e => e.toString()).join('\n')); + const {ngModules, files} = compiler.analyzeAndValidateNgModules( + programSymbols, {transitiveModules: true}, this.metadataResolver); + return compiler.loadNgModuleDirectives(ngModules).then(() => { + const errors: compiler.ParseError[] = []; + + files.forEach(file => { + const compMetas: compiler.CompileDirectiveMetadata[] = []; + file.directives.forEach(directiveType => { + const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType); + if (dirMeta && dirMeta.isComponent) { + compMetas.push(dirMeta); } - - return this.messageBundle; }); + compMetas.forEach(compMeta => { + const html = compMeta.template.template; + const interpolationConfig = + compiler.InterpolationConfig.fromArray(compMeta.template.interpolation); + errors.push( + ...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig)); + }); + }); + + if (errors.length) { + throw new Error(errors.map(e => e.toString()).join('\n')); + } + + return this.messageBundle; + }); } static create( diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index 231ed42d7186c..b41604d5c5208 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -589,7 +589,7 @@ export interface CompileNgModuleDirectiveSummary extends CompileSummary { exportedDirectives: CompileIdentifierMetadata[]; exportedPipes: CompileIdentifierMetadata[]; exportedModules: CompileNgModuleDirectiveSummary[]; - loadingPromises: Promise[]; + directiveLoaders: (() => Promise)[]; } export type CompileNgModuleSummary = @@ -661,7 +661,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { exportedModules: this.exportedModules, exportedDirectives: this.exportedDirectives, exportedPipes: this.exportedPipes, - loadingPromises: this.transitiveModule.loadingPromises + directiveLoaders: this.transitiveModule.directiveLoaders }; } @@ -682,7 +682,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { exportedDirectives: this.exportedDirectives, exportedPipes: this.exportedPipes, 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 entryComponents: CompileIdentifierMetadata[], public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[], - public loadingPromises: Promise[]) { + public directiveLoaders: (() => Promise)[]) { directives.forEach(dir => this.directivesSet.add(dir.reference)); pipes.forEach(pipe => this.pipesSet.add(pipe.reference)); } diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index d14416123337b..e873c80cd2209 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -146,75 +146,68 @@ export class CompileMetadataResolver { return; } directiveType = resolveForwardRef(directiveType); - const dirMeta = this._directiveResolver.resolve(directiveType); - if (!dirMeta) { - return null; - } - let moduleUrl = staticTypeModuleUrl(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; + }; - const createDirectiveMetadata = (templateMeta: cpl.CompileTemplateMetadata) => { - let changeDetectionStrategy: ChangeDetectionStrategy = null; - let viewProviders: Array = []; - let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = []; - let selector = dirMeta.selector; - - if (dirMeta instanceof Component) { - // Component - changeDetectionStrategy = dirMeta.changeDetection; - if (dirMeta.viewProviders) { - viewProviders = this._getProvidersMetadata( - dirMeta.viewProviders, entryComponentMetadata, - `viewProviders for "${stringify(directiveType)}"`); - } - if (dirMeta.entryComponents) { - entryComponentMetadata = - flattenAndDedupeArray(dirMeta.entryComponents) - .map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type))) - .concat(entryComponentMetadata); - } - if (!selector) { - selector = this._schemaRegistry.getDefaultComponentElementName(); - } + 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 { - // Directive - if (!selector) { - throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`); + if (isSync) { + throw new ComponentStillLoadingError(directiveType); } + return templateMeta.asyncResult.then(createDirectiveMetadata); } + } else { + // directive + createDirectiveMetadata(null); + return null; + } + } - let providers: Array = []; - if (isPresent(dirMeta.providers)) { - providers = this._getProvidersMetadata( - dirMeta.providers, entryComponentMetadata, - `providers for "${stringify(directiveType)}"`); - } - let queries: cpl.CompileQueryMetadata[] = []; - let viewQueries: cpl.CompileQueryMetadata[] = []; - if (isPresent(dirMeta.queries)) { - queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType); - viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType); - } - - const meta = cpl.CompileDirectiveMetadata.create({ - selector: selector, - exportAs: dirMeta.exportAs, - isComponent: !!templateMeta, - type: this._getTypeMetadata(directiveType, moduleUrl), - template: templateMeta, - changeDetection: changeDetectionStrategy, - inputs: dirMeta.inputs, - outputs: dirMeta.outputs, - host: dirMeta.host, - providers: providers, - viewProviders: viewProviders, - queries: queries, - viewQueries: viewQueries, - entryComponents: entryComponentMetadata - }); - this._directiveCache.set(directiveType, meta); - this._directiveSummaryCache.set(directiveType, meta.toSummary()); - return meta; - }; + getNonNormalizedDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata { + directiveType = resolveForwardRef(directiveType); + const dirMeta = this._directiveResolver.resolve(directiveType); + if (!dirMeta) { + return null; + } + let moduleUrl = staticTypeModuleUrl(directiveType); + let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata; if (dirMeta instanceof Component) { // component @@ -227,9 +220,7 @@ export class CompileMetadataResolver { dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) : null; - const templateMeta = this._directiveNormalizer.normalizeTemplate({ - componentType: directiveType, - moduleUrl: moduleUrl, + nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({ encapsulation: dirMeta.encapsulation, template: dirMeta.template, templateUrl: dirMeta.templateUrl, @@ -238,20 +229,65 @@ export class CompileMetadataResolver { 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); + } + + let changeDetectionStrategy: ChangeDetectionStrategy = null; + let viewProviders: Array = []; + let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = []; + let selector = dirMeta.selector; + + if (dirMeta instanceof Component) { + // Component + changeDetectionStrategy = dirMeta.changeDetection; + if (dirMeta.viewProviders) { + viewProviders = this._getProvidersMetadata( + dirMeta.viewProviders, entryComponentMetadata, + `viewProviders for "${stringify(directiveType)}"`); + } + if (dirMeta.entryComponents) { + entryComponentMetadata = + flattenAndDedupeArray(dirMeta.entryComponents) + .map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type))) + .concat(entryComponentMetadata); + } + if (!selector) { + selector = this._schemaRegistry.getDefaultComponentElementName(); } } else { - // directive - createDirectiveMetadata(null); - return null; + // Directive + if (!selector) { + throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`); + } } + + let providers: Array = []; + if (isPresent(dirMeta.providers)) { + providers = this._getProvidersMetadata( + dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`); + } + let queries: cpl.CompileQueryMetadata[] = []; + let viewQueries: cpl.CompileQueryMetadata[] = []; + if (isPresent(dirMeta.queries)) { + queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType); + viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType); + } + + return cpl.CompileDirectiveMetadata.create({ + selector: selector, + exportAs: dirMeta.exportAs, + isComponent: !!nonNormalizedTemplateMetadata, + type: this._getTypeMetadata(directiveType, moduleUrl), + template: nonNormalizedTemplateMetadata, + changeDetection: changeDetectionStrategy, + inputs: dirMeta.inputs, + outputs: dirMeta.outputs, + host: dirMeta.host, + providers: providers, + viewProviders: viewProviders, + queries: queries, + viewQueries: viewQueries, + entryComponents: entryComponentMetadata + }); } /** @@ -309,11 +345,20 @@ export class CompileMetadataResolver { loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true): {ngModule: cpl.CompileNgModuleMetadata, loading: Promise} { const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound); - const loading = - ngModule ? Promise.all(ngModule.transitiveModule.loadingPromises) : Promise.resolve(null); + const loading = ngModule ? + Promise.all(ngModule.transitiveModule.directiveLoaders.map(loader => loader())) : + Promise.resolve(null); 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): cpl.CompileNgModuleMetadata { moduleType = resolveForwardRef(moduleType); @@ -396,10 +441,8 @@ export class CompileMetadataResolver { transitiveModule.directives.push(declaredIdentifier); declaredDirectives.push(declaredIdentifier); this._addTypeToModule(declaredType, moduleType); - const loadingPromise = this._loadDirectiveMetadata(declaredType, isSync); - if (loadingPromise) { - transitiveModule.loadingPromises.push(loadingPromise); - } + transitiveModule.directiveLoaders.push( + () => this._loadDirectiveMetadata(declaredType, isSync)); } else if (this._pipeResolver.isPipe(declaredType)) { transitiveModule.pipesSet.add(declaredType); transitiveModule.pipes.push(declaredIdentifier); @@ -525,10 +568,10 @@ export class CompileMetadataResolver { const directives = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives)); const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes)); - const loadingPromises = - ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.loadingPromises)); + const directiveLoaders = + ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.directiveLoaders)); return new cpl.TransitiveCompileNgModuleMetadata( - transitiveModules, providers, entryComponents, directives, pipes, loadingPromises); + transitiveModules, providers, entryComponents, directives, pipes, directiveLoaders); } private _getIdentifierMetadata(type: Type, moduleUrl: string): @@ -584,20 +627,26 @@ export class CompileMetadataResolver { return pipeSummary; } - private _loadPipeMetadata(pipeType: Type): void { - pipeType = resolveForwardRef(pipeType); - const pipeMeta = this._pipeResolver.resolve(pipeType); + getOrLoadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata { + let pipeMeta = this._pipeCache.get(pipeType); if (!pipeMeta) { - return null; + pipeMeta = this._loadPipeMetadata(pipeType); } + return pipeMeta; + } + + private _loadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata { + pipeType = resolveForwardRef(pipeType); + const pipeAnnotation = this._pipeResolver.resolve(pipeType); - const meta = new cpl.CompilePipeMetadata({ + const pipeMeta = new cpl.CompilePipeMetadata({ type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)), - name: pipeMeta.name, - pure: pipeMeta.pure + name: pipeAnnotation.name, + pure: pipeAnnotation.pure }); - this._pipeCache.set(pipeType, meta); - this._pipeSummaryCache.set(pipeType, meta.toSummary()); + this._pipeCache.set(pipeType, pipeMeta); + this._pipeSummaryCache.set(pipeType, pipeMeta.toSummary()); + return pipeMeta; } private _getDependenciesMetadata(typeOrFunc: Type|Function, dependencies: any[]): diff --git a/modules/@angular/compiler/src/offline_compiler.ts b/modules/@angular/compiler/src/offline_compiler.ts index 6a2737aa4096c..9cae0a423ff7b 100644 --- a/modules/@angular/compiler/src/offline_compiler.ts +++ b/modules/@angular/compiler/src/offline_compiler.ts @@ -27,17 +27,46 @@ export class SourceModule { constructor(public fileUrl: string, public moduleUrl: string, public source: string) {} } +export interface NgAnalyzedModules { + ngModules: CompileNgModuleMetadata[]; + ngModuleByPipeOrDirective: Map; + files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>; + symbolsMissingModule?: StaticSymbol[]; +} + // Returns all the source files and a mapping from modules to directives export function analyzeNgModules( programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean}, - metadataResolver: CompileMetadataResolver): Promise<{ - ngModuleByPipeOrDirective: Map, - files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}> -}> { - return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules); + metadataResolver: CompileMetadataResolver): NgAnalyzedModules { + const {ngModules, symbolsMissingModule} = + _createNgModules(programStaticSymbols, options, metadataResolver); + return _analyzeNgModules(ngModules, symbolsMissingModule); +} + + +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[]) { +function _analyzeNgModules( + ngModuleMetas: CompileNgModuleMetadata[], + symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules { const moduleMetasByRef = new Map(); ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule)); const ngModuleByPipeOrDirective = new Map(); @@ -78,10 +107,11 @@ function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) { }); return { - // map directive/pipe to module - ngModuleByPipeOrDirective, - // list modules and directives for every source file - files, + // map directive/pipe to module + ngModuleByPipeOrDirective, + // list modules and directives for every source file + files, + ngModules: ngModuleMetas, symbolsMissingModule }; } @@ -100,13 +130,14 @@ export class OfflineCompiler { compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}): Promise { - return analyzeNgModules(staticSymbols, options, this._metadataResolver) - .then(({ngModuleByPipeOrDirective, files}) => { - const sourceModules = files.map( - file => this._compileSrcFile( - file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules)); - return ListWrapper.flatten(sourceModules); - }); + const {ngModuleByPipeOrDirective, files, ngModules} = + analyzeAndValidateNgModules(staticSymbols, options, this._metadataResolver); + return loadNgModuleDirectives(ngModules).then(() => { + const sourceModules = files.map( + file => this._compileSrcFile( + file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules)); + return ListWrapper.flatten(sourceModules); + }); } private _compileSrcFile( @@ -328,22 +359,21 @@ function _splitTypescriptSuffix(path: string): string[] { // Load the NgModules and check // that all directives / pipes that are present in the program // are also declared by a module. -function _loadNgModules( +function _createNgModules( programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean}, - metadataResolver: CompileMetadataResolver): Promise { + metadataResolver: CompileMetadataResolver): + {ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} { const ngModules = new Map(); const programPipesAndDirectives: StaticSymbol[] = []; const ngModulePipesAndDirective = new Set(); - const loadingPromises: Promise[] = []; const addNgModule = (staticSymbol: any) => { if (ngModules.has(staticSymbol)) { return false; } - const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false); + const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false); if (ngModule) { ngModules.set(ngModule.type.reference, ngModule); - loadingPromises.push(loading); ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference)); ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference)); if (options.transitiveModules) { @@ -364,11 +394,5 @@ function _loadNgModules( const symbolsMissingModule = programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s)); - if (symbolsMissingModule.length) { - 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())); + return {ngModules: Array.from(ngModules.values()), symbolsMissingModule}; }