diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index ba7d2da1c7..dd7d6bce9b 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -62,23 +62,30 @@ export class CodeGenerator { return path.join(this.options.genDir, relativePath); } - codegen(options: {transitiveModules: boolean}): Promise { - const staticSymbols = - extractProgramSymbols(this.program, this.staticReflector, this.ngHost, this.options); - - return this.compiler.compileModules(staticSymbols, options).then(generatedModules => { - generatedModules.forEach(generatedModule => { - const sourceFile = this.program.getSourceFile(generatedModule.fileUrl); - const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); - this.host.writeFile( - emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]); - }); - }); + codegen(): Promise { + return this.compiler + .compileAll( + this.program.getSourceFiles().map(sf => this.ngHost.getCanonicalFileName(sf.fileName))) + .then(generatedModules => { + generatedModules.forEach(generatedModule => { + const sourceFile = this.program.getSourceFile(generatedModule.fileUrl); + const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); + this.host.writeFile( + emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]); + }); + }); } static create( options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program, - compilerHost: ts.CompilerHost, ngHost?: NgHost): CodeGenerator { + compilerHost: ts.CompilerHost, ngHostContext?: NgHostContext, + ngHost?: NgHost): CodeGenerator { + if (!ngHost) { + const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0; + ngHost = usePathMapping ? + new PathMappedNgHost(program, compilerHost, options, ngHostContext) : + new NgHost(program, compilerHost, options, ngHostContext); + } const transFile = cliOptions.i18nFile; const locale = cliOptions.locale; let transContent: string = ''; @@ -93,7 +100,9 @@ export class CodeGenerator { debug: options.debug === true, translations: transContent, i18nFormat: cliOptions.i18nFormat, - locale: cliOptions.locale + locale: cliOptions.locale, + excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : + GENERATED_FILES }); return new CodeGenerator(options, program, compilerHost, reflector, aotCompiler, ngHost); } @@ -102,37 +111,10 @@ export class CodeGenerator { export function extractProgramSymbols( program: ts.Program, staticReflector: compiler.StaticReflector, ngHost: NgHost, options: AngularCompilerOptions): compiler.StaticSymbol[] { - // Compare with false since the default should be true - const skipFileNames = - options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; - - const staticSymbols: compiler.StaticSymbol[] = []; - - program.getSourceFiles() - .filter(sourceFile => !skipFileNames.test(sourceFile.fileName)) - .forEach(sourceFile => { - const absSrcPath = ngHost.getCanonicalFileName(sourceFile.fileName); - - const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath); - if (!moduleMetadata) { - console.log(`WARNING: no metadata found for ${absSrcPath}`); - return; - } - - const metadata = moduleMetadata['metadata']; - - if (!metadata) { - return; - } - - for (const symbol of Object.keys(metadata)) { - if (metadata[symbol] && metadata[symbol].__symbolic == 'error') { - // Ignore symbols that are only included to record error information. - continue; - } - staticSymbols.push(staticReflector.findDeclaration(absSrcPath, symbol, absSrcPath)); - } + return compiler.extractProgramSymbols( + staticReflector, program.getSourceFiles().map(sf => ngHost.getCanonicalFileName(sf.fileName)), + { + excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : + GENERATED_FILES }); - - return staticSymbols; } diff --git a/modules/@angular/compiler-cli/src/main.ts b/modules/@angular/compiler-cli/src/main.ts index 73312909ee..265687c6e0 100644 --- a/modules/@angular/compiler-cli/src/main.ts +++ b/modules/@angular/compiler-cli/src/main.ts @@ -19,9 +19,7 @@ import {CodeGenerator} from './codegen'; function codegen( ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program, host: ts.CompilerHost) { - return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({ - transitiveModules: true - }); + return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen(); } // CLI entry point diff --git a/modules/@angular/compiler/src/aot/compiler.ts b/modules/@angular/compiler/src/aot/compiler.ts index 1d05155eee..d58488df9a 100644 --- a/modules/@angular/compiler/src/aot/compiler.ts +++ b/modules/@angular/compiler/src/aot/compiler.ts @@ -23,100 +23,14 @@ import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; import {TemplateParser} from '../template_parser/template_parser'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler'; +import {AotCompilerOptions} from './compiler_options'; +import {StaticReflector} from './static_reflector'; import {StaticSymbol} from './static_symbol'; 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): 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[], - symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules { - const moduleMetasByRef = new Map(); - ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule)); - const ngModuleByPipeOrDirective = new Map(); - const ngModulesByFile = new Map(); - const ngDirectivesByFile = new Map(); - const filePaths = new Set(); - - // Looping over all modules to construct: - // - a map from file to modules `ngModulesByFile`, - // - a map from file to directives `ngDirectivesByFile`, - // - a map from directive/pipe to module `ngModuleByPipeOrDirective`. - ngModuleMetas.forEach((ngModuleMeta) => { - const srcFileUrl = ngModuleMeta.type.reference.filePath; - filePaths.add(srcFileUrl); - ngModulesByFile.set( - srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference)); - - ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => { - const fileUrl = dirIdentifier.reference.filePath; - filePaths.add(fileUrl); - ngDirectivesByFile.set( - fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference)); - ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta); - }); - ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => { - const fileUrl = pipeIdentifier.reference.filePath; - filePaths.add(fileUrl); - ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta); - }); - }); - - const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = []; - - filePaths.forEach((srcUrl) => { - const directives = ngDirectivesByFile.get(srcUrl) || []; - const ngModules = ngModulesByFile.get(srcUrl) || []; - files.push({srcUrl, directives, ngModules}); - }); - - return { - // map directive/pipe to module - ngModuleByPipeOrDirective, - // list modules and directives for every source file - files, - ngModules: ngModuleMetas, symbolsMissingModule - }; -} - export class AotCompiler { private _animationCompiler = new AnimationCompiler(); @@ -126,14 +40,20 @@ export class AotCompiler { private _dirWrapperCompiler: DirectiveWrapperCompiler, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, private _localeId: string, private _translationFormat: string, - private _animationParser: AnimationParser) {} + private _animationParser: AnimationParser, private _staticReflector: StaticReflector, + private _options: AotCompilerOptions) {} clearCache() { this._metadataResolver.clearCache(); } - compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}): - Promise { + compileAll(rootFiles: string[]): Promise { + const options = { + transitiveModules: true, + excludeFilePattern: this._options.excludeFilePattern, + includeFilePattern: this._options.includeFilePattern + }; + const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, options); const {ngModuleByPipeOrDirective, files, ngModules} = - analyzeAndValidateNgModules(staticSymbols, options, this._metadataResolver); + analyzeAndValidateNgModules(programSymbols, options, this._metadataResolver); return loadNgModuleDirectives(ngModules).then(() => { const sourceModules = files.map( file => this._compileSrcFile( @@ -358,11 +278,133 @@ function _splitTypescriptSuffix(path: string): string[] { return [path, '']; } +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, includeFilePattern?: RegExp, excludeFilePattern?: RegExp}, + 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[], + symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules { + const moduleMetasByRef = new Map(); + ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule)); + const ngModuleByPipeOrDirective = new Map(); + const ngModulesByFile = new Map(); + const ngDirectivesByFile = new Map(); + const filePaths = new Set(); + + // Looping over all modules to construct: + // - a map from file to modules `ngModulesByFile`, + // - a map from file to directives `ngDirectivesByFile`, + // - a map from directive/pipe to module `ngModuleByPipeOrDirective`. + ngModuleMetas.forEach((ngModuleMeta) => { + const srcFileUrl = ngModuleMeta.type.reference.filePath; + filePaths.add(srcFileUrl); + ngModulesByFile.set( + srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference)); + + ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => { + const fileUrl = dirIdentifier.reference.filePath; + filePaths.add(fileUrl); + ngDirectivesByFile.set( + fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference)); + ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta); + }); + ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => { + const fileUrl = pipeIdentifier.reference.filePath; + filePaths.add(fileUrl); + ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta); + }); + }); + + const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = []; + + filePaths.forEach((srcUrl) => { + const directives = ngDirectivesByFile.get(srcUrl) || []; + const ngModules = ngModulesByFile.get(srcUrl) || []; + files.push({srcUrl, directives, ngModules}); + }); + + return { + // map directive/pipe to module + ngModuleByPipeOrDirective, + // list modules and directives for every source file + files, + ngModules: ngModuleMetas, symbolsMissingModule + }; +} + +export function extractProgramSymbols( + staticReflector: StaticReflector, files: string[], + options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] { + const staticSymbols: StaticSymbol[] = []; + files + .filter( + fileName => _filterFileByPatterns( + fileName, options.includeFilePattern, options.includeFilePattern)) + .forEach(sourceFile => { + const moduleMetadata = staticReflector.getModuleMetadata(sourceFile); + if (!moduleMetadata) { + console.log(`WARNING: no metadata found for ${sourceFile}`); + return; + } + + const metadata = moduleMetadata['metadata']; + + if (!metadata) { + return; + } + + for (const symbol of Object.keys(metadata)) { + if (metadata[symbol] && metadata[symbol].__symbolic == 'error') { + // Ignore symbols that are only included to record error information. + continue; + } + staticSymbols.push(staticReflector.findDeclaration(sourceFile, symbol, sourceFile)); + } + }); + + return staticSymbols; +} + // Load the NgModules and check // that all directives / pipes that are present in the program // are also declared by a module. function _createNgModules( - programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean}, + programStaticSymbols: StaticSymbol[], + options: {transitiveModules: boolean, includeFilePattern?: RegExp, excludeFilePattern?: RegExp}, metadataResolver: CompileMetadataResolver): {ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} { const ngModules = new Map(); @@ -370,7 +412,9 @@ function _createNgModules( const ngModulePipesAndDirective = new Set(); const addNgModule = (staticSymbol: any) => { - if (ngModules.has(staticSymbol)) { + if (ngModules.has(staticSymbol) || + !_filterFileByPatterns( + staticSymbol.filePath, options.includeFilePattern, options.excludeFilePattern)) { return false; } const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false); @@ -398,3 +442,15 @@ function _createNgModules( return {ngModules: Array.from(ngModules.values()), symbolsMissingModule}; } + +function _filterFileByPatterns( + fileName: string, includeFilePattern: RegExp, excludeFilePattern: RegExp) { + let match = true; + if (includeFilePattern) { + match = match && !!includeFilePattern.exec(fileName); + } + if (excludeFilePattern) { + match = match && !excludeFilePattern.exec(fileName); + } + return match; +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/aot/compiler_factory.ts b/modules/@angular/compiler/src/aot/compiler_factory.ts index 71d3e9e91b..f8eb93a392 100644 --- a/modules/@angular/compiler/src/aot/compiler_factory.ts +++ b/modules/@angular/compiler/src/aot/compiler_factory.ts @@ -31,15 +31,11 @@ import {ViewCompiler} from '../view_compiler/view_compiler'; import {AotCompiler} from './compiler'; import {AotCompilerHost} from './compiler_host'; +import {AotCompilerOptions} from './compiler_options'; import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; import {StaticReflector} from './static_reflector'; -export interface AotCompilerOptions { - debug?: boolean; - locale?: string; - i18nFormat?: string; - translations?: string; -} + /** * Creates a new AotCompiler based on options and a host. @@ -74,6 +70,6 @@ export function createAotCompiler(ngHost: AotCompilerHost, options: AotCompilerO new ViewCompiler(config, elementSchemaRegistry), new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console), new NgModuleCompiler(), new TypeScriptEmitter(ngHost), options.locale, options.i18nFormat, - new AnimationParser(elementSchemaRegistry)); + new AnimationParser(elementSchemaRegistry), staticReflector, options); return {compiler, reflector: staticReflector}; } diff --git a/modules/@angular/compiler/src/aot/compiler_options.ts b/modules/@angular/compiler/src/aot/compiler_options.ts new file mode 100644 index 0000000000..8794d88840 --- /dev/null +++ b/modules/@angular/compiler/src/aot/compiler_options.ts @@ -0,0 +1,16 @@ +/** + * @license + * Copyright Google Inc. 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 + */ + +export interface AotCompilerOptions { + debug?: boolean; + locale?: string; + i18nFormat?: string; + translations?: string; + includeFilePattern?: RegExp; + excludeFilePattern?: RegExp; +}