refactor(compiler): move symbol extraction to AotCompiler

This commit is contained in:
Tobias Bosch 2016-11-15 13:57:25 -08:00 committed by Chuck Jazdzewski
parent 2235048432
commit b15039d228
5 changed files with 198 additions and 150 deletions

View File

@ -62,11 +62,11 @@ export class CodeGenerator {
return path.join(this.options.genDir, relativePath); return path.join(this.options.genDir, relativePath);
} }
codegen(options: {transitiveModules: boolean}): Promise<any> { codegen(): Promise<any> {
const staticSymbols = return this.compiler
extractProgramSymbols(this.program, this.staticReflector, this.ngHost, this.options); .compileAll(
this.program.getSourceFiles().map(sf => this.ngHost.getCanonicalFileName(sf.fileName)))
return this.compiler.compileModules(staticSymbols, options).then(generatedModules => { .then(generatedModules => {
generatedModules.forEach(generatedModule => { generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl); const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
@ -78,7 +78,14 @@ export class CodeGenerator {
static create( static create(
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program, 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 transFile = cliOptions.i18nFile;
const locale = cliOptions.locale; const locale = cliOptions.locale;
let transContent: string = ''; let transContent: string = '';
@ -93,7 +100,9 @@ export class CodeGenerator {
debug: options.debug === true, debug: options.debug === true,
translations: transContent, translations: transContent,
i18nFormat: cliOptions.i18nFormat, 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); return new CodeGenerator(options, program, compilerHost, reflector, aotCompiler, ngHost);
} }
@ -102,37 +111,10 @@ export class CodeGenerator {
export function extractProgramSymbols( export function extractProgramSymbols(
program: ts.Program, staticReflector: compiler.StaticReflector, ngHost: NgHost, program: ts.Program, staticReflector: compiler.StaticReflector, ngHost: NgHost,
options: AngularCompilerOptions): compiler.StaticSymbol[] { options: AngularCompilerOptions): compiler.StaticSymbol[] {
// Compare with false since the default should be true return compiler.extractProgramSymbols(
const skipFileNames = staticReflector, program.getSourceFiles().map(sf => ngHost.getCanonicalFileName(sf.fileName)),
options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; {
excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES :
const staticSymbols: compiler.StaticSymbol[] = []; GENERATED_FILES
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 staticSymbols;
} }

View File

@ -19,9 +19,7 @@ import {CodeGenerator} from './codegen';
function codegen( function codegen(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program, ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
host: ts.CompilerHost) { host: ts.CompilerHost) {
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({ return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
transitiveModules: true
});
} }
// CLI entry point // CLI entry point

View File

@ -23,100 +23,14 @@ import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser'; import {TemplateParser} from '../template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler'; 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'; import {StaticSymbol} from './static_symbol';
export class SourceModule { 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
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<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>();
// 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 { export class AotCompiler {
private _animationCompiler = new AnimationCompiler(); private _animationCompiler = new AnimationCompiler();
@ -126,14 +40,20 @@ export class AotCompiler {
private _dirWrapperCompiler: DirectiveWrapperCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _localeId: string, private _translationFormat: string, private _localeId: string, private _translationFormat: string,
private _animationParser: AnimationParser) {} private _animationParser: AnimationParser, private _staticReflector: StaticReflector,
private _options: AotCompilerOptions) {}
clearCache() { this._metadataResolver.clearCache(); } clearCache() { this._metadataResolver.clearCache(); }
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}): compileAll(rootFiles: string[]): Promise<SourceModule[]> {
Promise<SourceModule[]> { const options = {
transitiveModules: true,
excludeFilePattern: this._options.excludeFilePattern,
includeFilePattern: this._options.includeFilePattern
};
const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, options);
const {ngModuleByPipeOrDirective, files, ngModules} = const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(staticSymbols, options, this._metadataResolver); analyzeAndValidateNgModules(programSymbols, options, this._metadataResolver);
return loadNgModuleDirectives(ngModules).then(() => { return loadNgModuleDirectives(ngModules).then(() => {
const sourceModules = files.map( const sourceModules = files.map(
file => this._compileSrcFile( file => this._compileSrcFile(
@ -358,11 +278,133 @@ function _splitTypescriptSuffix(path: string): string[] {
return [path, '']; return [path, ''];
} }
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
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<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>();
// 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 // 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 _createNgModules( function _createNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean}, programStaticSymbols: StaticSymbol[],
options: {transitiveModules: boolean, includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
metadataResolver: CompileMetadataResolver): metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} { {ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>(); const ngModules = new Map<any, CompileNgModuleMetadata>();
@ -370,7 +412,9 @@ function _createNgModules(
const ngModulePipesAndDirective = new Set<StaticSymbol>(); const ngModulePipesAndDirective = new Set<StaticSymbol>();
const addNgModule = (staticSymbol: any) => { const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol)) { if (ngModules.has(staticSymbol) ||
!_filterFileByPatterns(
staticSymbol.filePath, options.includeFilePattern, options.excludeFilePattern)) {
return false; return false;
} }
const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false); const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false);
@ -398,3 +442,15 @@ function _createNgModules(
return {ngModules: Array.from(ngModules.values()), symbolsMissingModule}; 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;
}

View File

@ -31,15 +31,11 @@ import {ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompiler} from './compiler'; import {AotCompiler} from './compiler';
import {AotCompilerHost} from './compiler_host'; import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector} from './static_reflector'; 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. * 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 ViewCompiler(config, elementSchemaRegistry),
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console), new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
new NgModuleCompiler(), new TypeScriptEmitter(ngHost), options.locale, options.i18nFormat, new NgModuleCompiler(), new TypeScriptEmitter(ngHost), options.locale, options.i18nFormat,
new AnimationParser(elementSchemaRegistry)); new AnimationParser(elementSchemaRegistry), staticReflector, options);
return {compiler, reflector: staticReflector}; return {compiler, reflector: staticReflector};
} }

View File

@ -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;
}