From 614a35d53982d6d15b3a2f53904f03c7840f8822 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Tue, 29 Nov 2016 15:36:33 -0800 Subject: [PATCH] feat(compiler): read and write `.ngsummary.json` files When compiling libraries, this feature extracts the minimal information from the directives/pipes/modules of the library into `.ngsummary.json` files, so that applications that use this library only need to be recompiled if one of the summary files change, but not on every change of the libraries (e.g. one of the templates). Only works if individual codegen for libraries is enabled, see the `generateCodeForLibraries: false` option. Closes #12787 --- .../integrationtest/src/module.ts | 4 +- .../integrationtest/test/test_summaries.ts | 100 +++++++++++++++++ .../integrationtest/tsconfig-build.json | 1 + modules/@angular/compiler-cli/src/codegen.ts | 10 +- .../compiler-cli/src/compiler_host.ts | 6 + modules/@angular/compiler/index.ts | 1 + modules/@angular/compiler/src/aot/compiler.ts | 87 +++++++------- .../compiler/src/aot/compiler_factory.ts | 9 +- .../compiler/src/aot/compiler_host.ts | 7 +- .../compiler/src/aot/generated_file.ts | 11 ++ .../compiler/src/aot/summary_resolver.ts | 106 ++++++++++++++++++ modules/@angular/compiler/src/aot/utils.ts | 19 ++++ .../@angular/compiler/src/compile_metadata.ts | 14 ++- .../@angular/compiler/src/i18n/extractor.ts | 6 +- modules/@angular/compiler/src/jit/compiler.ts | 10 +- .../compiler/src/jit/compiler_factory.ts | 2 + .../compiler/src/metadata_resolver.ts | 88 +++++++++------ .../@angular/compiler/src/summary_resolver.ts | 14 +++ .../test/aot/static_reflector_spec.ts | 2 +- .../test/aot/summary_resolver_spec.ts | 93 +++++++++++++++ .../compiler/test/metadata_resolver_spec.ts | 29 +---- .../language-service/src/typescript_host.ts | 5 +- scripts/ci-lite/offline_compiler_test.sh | 2 + 23 files changed, 500 insertions(+), 126 deletions(-) create mode 100644 modules/@angular/compiler-cli/integrationtest/test/test_summaries.ts create mode 100644 modules/@angular/compiler/src/aot/generated_file.ts create mode 100644 modules/@angular/compiler/src/aot/summary_resolver.ts create mode 100644 modules/@angular/compiler/src/aot/utils.ts create mode 100644 modules/@angular/compiler/src/summary_resolver.ts create mode 100644 modules/@angular/compiler/test/aot/summary_resolver_spec.ts diff --git a/modules/@angular/compiler-cli/integrationtest/src/module.ts b/modules/@angular/compiler-cli/integrationtest/src/module.ts index a690eb686d..6772466f42 100644 --- a/modules/@angular/compiler-cli/integrationtest/src/module.ts +++ b/modules/@angular/compiler-cli/integrationtest/src/module.ts @@ -11,7 +11,9 @@ import {FormsModule} from '@angular/forms'; import {ServerModule} from '@angular/platform-server'; import {MdButtonModule} from '@angular2-material/button'; -import {ThirdpartyModule} from '../third_party_src/module'; +// Note: don't refer to third_party_src as we want to test that +// we can compile components from node_modules! +import {ThirdpartyModule} from 'third_party/module'; import {MultipleComponentsMyComp, NextComp} from './a/multiple_components'; import {AnimateCmp} from './animate'; diff --git a/modules/@angular/compiler-cli/integrationtest/test/test_summaries.ts b/modules/@angular/compiler-cli/integrationtest/test/test_summaries.ts new file mode 100644 index 0000000000..faaa19cef3 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/test/test_summaries.ts @@ -0,0 +1,100 @@ +#!/usr/bin/env node +/** + * @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 + */ +/* tslint:disable:no-console */ + +// Must be imported first, because angular2 decorators throws on load. +import 'reflect-metadata'; + +import * as path from 'path'; +import * as ts from 'typescript'; +import * as assert from 'assert'; +import {tsc} from '@angular/tsc-wrapped/src/tsc'; +import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext} from '@angular/compiler-cli'; + +/** + * Main method. + * Standalone program that executes the real codegen and tests that + * ngsummary.json files are used for libraries. + */ +function main() { + console.log(`testing usage of ngsummary.json files in libraries...`); + const basePath = path.resolve(__dirname, '..'); + const project = path.resolve(basePath, 'tsconfig-build.json'); + const readFiles: string[] = []; + + class AssertingHostContext extends NodeCompilerHostContext { + readFile(fileName: string): string { + readFiles.push(path.relative(basePath, fileName)); + return super.readFile(fileName); + } + readResource(s: string): Promise { + readFiles.push(path.relative(basePath, s)); + return super.readResource(s); + } + } + + const config = tsc.readConfiguration(project, basePath); + config.ngOptions.basePath = basePath; + // This flag tells ngc do not recompile libraries. + config.ngOptions.generateCodeForLibraries = false; + + console.log(`>>> running codegen for ${project}`); + codegen(config, (host) => new AssertingHostContext()) + .then((exitCode: any) => { + console.log(`>>> codegen done, asserting read files`); + assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/); + assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/); + assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/); + + assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/); + assertSomeFileMatch(readFiles, /^src\/.*\.html$/); + assertSomeFileMatch(readFiles, /^src\/.*\.css$/); + console.log(`done, no errors.`); + process.exit(exitCode); + }) + .catch((e: any) => { + console.error(e.stack); + console.error('Compilation failed'); + process.exit(1); + }); +} + +/** + * Simple adaption of tsc-wrapped main to just run codegen with a CompilerHostContext + */ +function codegen( + config: {parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions}, + hostContextFactory: (host: ts.CompilerHost) => CompilerHostContext) { + const host = ts.createCompilerHost(config.parsed.options, true); + + // HACK: patch the realpath to solve symlink issue here: + // https://github.com/Microsoft/TypeScript/issues/9552 + // todo(misko): remove once facade symlinks are removed + host.realpath = (path) => path; + + const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host); + + return CodeGenerator.create(config.ngOptions, { + } as any, program, host, hostContextFactory(host)).codegen(); +} + +function assertSomeFileMatch(fileNames: string[], pattern: RegExp) { + assert( + fileNames.some(fileName => pattern.test(fileName)), + `Expected some read files match ${pattern}`); +} + +function assertNoFileMatch(fileNames: string[], pattern: RegExp) { + const matches = fileNames.filter(fileName => pattern.test(fileName)); + assert( + matches.length === 0, + `Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`); +} + +main(); diff --git a/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json b/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json index 3072122976..260f3090b3 100644 --- a/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json +++ b/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json @@ -21,6 +21,7 @@ "src/module", "src/bootstrap", "test/all_spec", + "test/test_summaries", "benchmarks/src/tree/ng2/index_aot.ts", "benchmarks/src/tree/ng2_switch/index_aot.ts", "benchmarks/src/largetable/ng2/index_aot.ts", diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index 6c143e7356..e3d45942b8 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -22,6 +22,7 @@ import {PathMappedCompilerHost} from './path_mapped_compiler_host'; import {Console} from './private_import_core'; const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; +const GENERATED_META_FILES = /\.json$/; const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; const PREAMBLE = `/** @@ -68,10 +69,11 @@ export class CodeGenerator { sf => this.ngCompilerHost.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]); + const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl); + const emitPath = this.calculateEmitPath(generatedModule.genFileUrl); + const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source : + PREAMBLE + generatedModule.source; + this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]); }); }); } diff --git a/modules/@angular/compiler-cli/src/compiler_host.ts b/modules/@angular/compiler-cli/src/compiler_host.ts index b20d8aadb5..59641fc6e9 100644 --- a/modules/@angular/compiler-cli/src/compiler_host.ts +++ b/modules/@angular/compiler-cli/src/compiler_host.ts @@ -209,6 +209,12 @@ export class CompilerHost implements AotCompilerHost { } loadResource(filePath: string): Promise { return this.context.readResource(filePath); } + + loadSummary(filePath: string): string { return this.context.readFile(filePath); } + + getOutputFileName(sourceFilePath: string): string { + return sourceFilePath.replace(EXT, '') + '.d.ts'; + } } export class CompilerHostContextAdapter { diff --git a/modules/@angular/compiler/index.ts b/modules/@angular/compiler/index.ts index a9997670b1..473d9b8268 100644 --- a/modules/@angular/compiler/index.ts +++ b/modules/@angular/compiler/index.ts @@ -36,6 +36,7 @@ export * from './src/aot/compiler_host'; export * from './src/aot/static_reflector'; export * from './src/aot/static_reflection_capabilities'; export * from './src/aot/static_symbol'; +export * from './src/aot/summary_resolver'; export {JitCompiler} from './src/jit/compiler'; export * from './src/jit/compiler_factory'; export * from './src/url_resolver'; diff --git a/modules/@angular/compiler/src/aot/compiler.ts b/modules/@angular/compiler/src/aot/compiler.ts index 5043339fcf..6d54136a77 100644 --- a/modules/@angular/compiler/src/aot/compiler.ts +++ b/modules/@angular/compiler/src/aot/compiler.ts @@ -10,7 +10,7 @@ import {SchemaMetadata} from '@angular/core'; import {AnimationCompiler} from '../animation/animation_compiler'; import {AnimationParser} from '../animation/animation_parser'; -import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata'; +import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata'; import {DirectiveNormalizer} from '../directive_normalizer'; import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {ListWrapper} from '../facade/collection'; @@ -24,12 +24,11 @@ import {TemplateParser} from '../template_parser/template_parser'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler'; import {AotCompilerOptions} from './compiler_options'; +import {GeneratedFile} from './generated_file'; import {StaticReflector} from './static_reflector'; import {StaticSymbol} from './static_symbol'; - -export class SourceModule { - constructor(public fileUrl: string, public moduleUrl: string, public source: string) {} -} +import {AotSummaryResolver} from './summary_resolver'; +import {filterFileByPatterns} from './utils'; export class AotCompiler { private _animationCompiler = new AnimationCompiler(); @@ -39,13 +38,13 @@ export class AotCompiler { private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, - private _localeId: string, private _translationFormat: string, - private _animationParser: AnimationParser, private _staticReflector: StaticReflector, - private _options: AotCompilerOptions) {} + private _summaryResolver: AotSummaryResolver, private _localeId: string, + private _translationFormat: string, private _animationParser: AnimationParser, + private _staticReflector: StaticReflector, private _options: AotCompilerOptions) {} clearCache() { this._metadataResolver.clearCache(); } - compileAll(rootFiles: string[]): Promise { + compileAll(rootFiles: string[]): Promise { const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options); const {ngModuleByPipeOrDirective, files, ngModules} = analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver); @@ -56,18 +55,28 @@ export class AotCompiler { .then(() => { const sourceModules = files.map( file => this._compileSrcFile( - file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules)); + file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, + file.ngModules)); return ListWrapper.flatten(sourceModules); }); } private _compileSrcFile( srcFileUrl: string, ngModuleByPipeOrDirective: Map, - directives: StaticSymbol[], ngModules: StaticSymbol[]): SourceModule[] { + directives: StaticSymbol[], pipes: StaticSymbol[], + ngModules: StaticSymbol[]): GeneratedFile[] { const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1]; const statements: o.Statement[] = []; const exportedVars: string[] = []; - const outputSourceModules: SourceModule[] = []; + const generatedFiles: GeneratedFile[] = []; + + // write summary files + const summaries: CompileTypeSummary[] = [ + ...pipes.map(ref => this._metadataResolver.getPipeSummary(ref)), + ...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)), + ...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)) + ]; + generatedFiles.push(this._summaryResolver.serializeSummaries(srcFileUrl, summaries)); // compile all ng modules exportedVars.push( @@ -94,7 +103,7 @@ export class AotCompiler { // compile styles const stylesCompileResults = this._styleCompiler.compileComponent(compMeta); stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => { - outputSourceModules.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix)); + generatedFiles.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix)); }); // compile components @@ -107,9 +116,9 @@ export class AotCompiler { if (statements.length > 0) { const srcModule = this._codegenSourceModule( srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars); - outputSourceModules.unshift(srcModule); + generatedFiles.unshift(srcModule); } - return outputSourceModules; + return generatedFiles; } private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string { @@ -205,7 +214,7 @@ export class AotCompiler { } private _codgenStyles( - fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule { + fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile { _resolveStyleStatements(this._staticReflector, stylesCompileResult, fileSuffix); return this._codegenSourceModule( fileUrl, _stylesModuleUrl( @@ -214,11 +223,11 @@ export class AotCompiler { } private _codegenSourceModule( - fileUrl: string, moduleUrl: string, statements: o.Statement[], - exportedVars: string[]): SourceModule { - return new SourceModule( - fileUrl, moduleUrl, - this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars)); + srcFileUrl: string, genFileUrl: string, statements: o.Statement[], + exportedVars: string[]): GeneratedFile { + return new GeneratedFile( + srcFileUrl, genFileUrl, + this._outputEmitter.emitStatements(genFileUrl, statements, exportedVars)); } } @@ -290,7 +299,12 @@ function _splitTypescriptSuffix(path: string): string[] { export interface NgAnalyzedModules { ngModules: CompileNgModuleMetadata[]; ngModuleByPipeOrDirective: Map; - files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>; + files: Array<{ + srcUrl: string, + directives: StaticSymbol[], + pipes: StaticSymbol[], + ngModules: StaticSymbol[] + }>; symbolsMissingModule?: StaticSymbol[]; } @@ -325,11 +339,13 @@ function _analyzeNgModules( const ngModuleByPipeOrDirective = new Map(); const ngModulesByFile = new Map(); const ngDirectivesByFile = new Map(); + const ngPipesByFile = 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 file to pipes `ngPipesByFile`, // - a map from directive/pipe to module `ngModuleByPipeOrDirective`. ngModuleMetas.forEach((ngModuleMeta) => { const srcFileUrl = ngModuleMeta.type.reference.filePath; @@ -347,16 +363,23 @@ function _analyzeNgModules( ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => { const fileUrl = pipeIdentifier.reference.filePath; filePaths.add(fileUrl); + ngPipesByFile.set( + fileUrl, (ngPipesByFile.get(fileUrl) || []).concat(pipeIdentifier.reference)); ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta); }); }); - const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = []; + const files: + {srcUrl: string, + directives: StaticSymbol[], + pipes: StaticSymbol[], + ngModules: StaticSymbol[]}[] = []; filePaths.forEach((srcUrl) => { const directives = ngDirectivesByFile.get(srcUrl) || []; + const pipes = ngPipesByFile.get(srcUrl) || []; const ngModules = ngModulesByFile.get(srcUrl) || []; - files.push({srcUrl, directives, ngModules}); + files.push({srcUrl, directives, pipes, ngModules}); }); return { @@ -372,7 +395,7 @@ export function extractProgramSymbols( staticReflector: StaticReflector, files: string[], options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] { const staticSymbols: StaticSymbol[] = []; - files.filter(fileName => _filterFileByPatterns(fileName, options)).forEach(sourceFile => { + files.filter(fileName => filterFileByPatterns(fileName, options)).forEach(sourceFile => { const moduleMetadata = staticReflector.getModuleMetadata(sourceFile); if (!moduleMetadata) { console.error(`WARNING: no metadata found for ${sourceFile}`); @@ -410,7 +433,7 @@ function _createNgModules( const ngModulePipesAndDirective = new Set(); const addNgModule = (staticSymbol: any) => { - if (ngModules.has(staticSymbol) || !_filterFileByPatterns(staticSymbol.filePath, options)) { + if (ngModules.has(staticSymbol) || !filterFileByPatterns(staticSymbol.filePath, options)) { return false; } const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false); @@ -436,15 +459,3 @@ function _createNgModules( return {ngModules: Array.from(ngModules.values()), symbolsMissingModule}; } - -function _filterFileByPatterns( - fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) { - let match = true; - if (options.includeFilePattern) { - match = match && !!options.includeFilePattern.exec(fileName); - } - if (options.excludeFilePattern) { - match = match && !options.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 d22b13fc2d..1fa7b3a7a9 100644 --- a/modules/@angular/compiler/src/aot/compiler_factory.ts +++ b/modules/@angular/compiler/src/aot/compiler_factory.ts @@ -34,8 +34,7 @@ import {AotCompilerHost} from './compiler_host'; import {AotCompilerOptions} from './compiler_options'; import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; import {StaticReflector} from './static_reflector'; - - +import {AotSummaryResolver} from './summary_resolver'; /** * Creates a new AotCompiler based on options and a host. @@ -61,15 +60,17 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom const console = new Console(); const tmplParser = new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []); + const summaryResolver = new AotSummaryResolver(compilerHost, staticReflector, options); const resolver = new CompileMetadataResolver( new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), - new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector); + new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer, + staticReflector); // TODO(vicb): do not pass options.i18nFormat here const compiler = new AotCompiler( resolver, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config, elementSchemaRegistry), new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console), - new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), options.locale, + new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale, options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options); return {compiler, reflector: staticReflector}; } diff --git a/modules/@angular/compiler/src/aot/compiler_host.ts b/modules/@angular/compiler/src/aot/compiler_host.ts index 2d108578ef..f9adcf2b52 100644 --- a/modules/@angular/compiler/src/aot/compiler_host.ts +++ b/modules/@angular/compiler/src/aot/compiler_host.ts @@ -10,15 +10,16 @@ import {ImportResolver} from '../output/path_util'; import {StaticReflectorHost} from './static_reflector'; import {StaticSymbol} from './static_symbol'; - +import {AotSummaryResolverHost} from './summary_resolver'; /** * The host of the AotCompiler disconnects the implementation from TypeScript / other language * services and from underlying file systems. */ -export interface AotCompilerHost extends StaticReflectorHost, ImportResolver { +export interface AotCompilerHost extends StaticReflectorHost, ImportResolver, + AotSummaryResolverHost { /** * Loads a resource (e.g. html / css) */ loadResource(path: string): Promise; -} \ No newline at end of file +} diff --git a/modules/@angular/compiler/src/aot/generated_file.ts b/modules/@angular/compiler/src/aot/generated_file.ts new file mode 100644 index 0000000000..b698397e84 --- /dev/null +++ b/modules/@angular/compiler/src/aot/generated_file.ts @@ -0,0 +1,11 @@ +/** + * @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 class GeneratedFile { + constructor(public srcFileUrl: string, public genFileUrl: string, public source: string) {} +} diff --git a/modules/@angular/compiler/src/aot/summary_resolver.ts b/modules/@angular/compiler/src/aot/summary_resolver.ts new file mode 100644 index 0000000000..c0d14a1058 --- /dev/null +++ b/modules/@angular/compiler/src/aot/summary_resolver.ts @@ -0,0 +1,106 @@ +/** + * @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 + */ +import {CompileIdentifierMetadata, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata'; +import {SummaryResolver} from '../summary_resolver'; + +import {GeneratedFile} from './generated_file'; +import {StaticReflector} from './static_reflector'; +import {StaticSymbol, isStaticSymbol} from './static_symbol'; +import {filterFileByPatterns} from './utils'; + +const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; + +export interface AotSummaryResolverHost { + /** + * Loads an NgModule/Directive/Pipe summary file + */ + loadSummary(filePath: string): string; + + /** + * Returns the output file path of a source file. + * E.g. + * `some_file.ts` -> `some_file.d.ts` + */ + getOutputFileName(sourceFilePath: string): string; +} + +export interface AotSummaryResolverOptions { + includeFilePattern?: RegExp; + excludeFilePattern?: RegExp; +} + +export class AotSummaryResolver implements SummaryResolver { + private summaryCache: {[srcFilePath: string]: CompileTypeSummary[]} = {}; + + constructor( + private host: AotSummaryResolverHost, private staticReflector: StaticReflector, + private options: AotSummaryResolverOptions) {} + + serializeSummaries(srcFileUrl: string, summaries: CompileTypeSummary[]): GeneratedFile { + const jsonReplacer = (key: string, value: any) => { + if (key === 'reference' && isStaticSymbol(value)) { + // We convert the source filenames into output filenames, + // as the generated summary file will be used when the current + // compilation unit is used as a library + return { + '__symbolic__': 'symbol', + 'name': value.name, + 'path': this.host.getOutputFileName(value.filePath), + 'members': value.members + }; + } + return value; + }; + + return new GeneratedFile( + srcFileUrl, summaryFileName(srcFileUrl), JSON.stringify(summaries, jsonReplacer)); + } + + resolveSummary(staticSymbol: StaticSymbol): any { + const filePath = staticSymbol.filePath; + const name = staticSymbol.name; + if (!filterFileByPatterns(filePath, this.options)) { + let summaries = this.summaryCache[filePath]; + const summaryFilePath = summaryFileName(filePath); + if (!summaries) { + try { + const jsonReviver = (key: string, value: any) => { + if (key === 'reference' && value && value['__symbolic__'] === 'symbol') { + // Note: We can't use staticReflector.findDeclaration here: + // Summary files can contain symbols of transitive compilation units + // (via the providers), and findDeclaration needs .metadata.json / .d.ts files, + // but we don't want to depend on these for transitive dependencies. + return this.staticReflector.getStaticSymbol( + value['path'], value['name'], value['members']); + } else { + return value; + } + }; + summaries = JSON.parse(this.host.loadSummary(summaryFilePath), jsonReviver); + } catch (e) { + console.error(`Error loading summary file ${summaryFilePath}`); + throw e; + } + this.summaryCache[filePath] = summaries; + } + const result = summaries.find((summary) => summary.type.reference === staticSymbol); + if (!result) { + throw new Error( + `Could not find the symbol ${name} in the summary file ${summaryFilePath}!`); + } + return result; + } else { + return null; + } + } +} + +function summaryFileName(fileName: string): string { + const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, ''); + return `${fileNameWithoutSuffix}.ngsummary.json`; +} diff --git a/modules/@angular/compiler/src/aot/utils.ts b/modules/@angular/compiler/src/aot/utils.ts new file mode 100644 index 0000000000..07bcaffa4e --- /dev/null +++ b/modules/@angular/compiler/src/aot/utils.ts @@ -0,0 +1,19 @@ +/** + * @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 function filterFileByPatterns( + fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) { + let match = true; + if (options.includeFilePattern) { + match = match && !!options.includeFilePattern.exec(fileName); + } + if (options.excludeFilePattern) { + match = match && !options.excludeFilePattern.exec(fileName); + } + return match; +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index 594730b851..4ab56f5858 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -123,6 +123,8 @@ export interface CompileSummary { isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */; } +export interface CompileTypeSummary extends CompileSummary { type: CompileTypeMetadata; } + export interface CompileDiDependencyMetadata { isAttribute?: boolean; isSelf?: boolean; @@ -262,7 +264,7 @@ export class CompileTemplateMetadata { // Note: This should only use interfaces as nested data types // as we need to be able to serialize this from/to JSON! -export interface CompileDirectiveSummary extends CompileSummary { +export interface CompileDirectiveSummary extends CompileTypeSummary { isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */; type: CompileTypeMetadata; isComponent: boolean; @@ -470,7 +472,7 @@ export function createHostComponentMeta( }); } -export interface CompilePipeSummary extends CompileSummary { +export interface CompilePipeSummary extends CompileTypeSummary { isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */; type: CompileTypeMetadata; name: string; @@ -499,7 +501,7 @@ export class CompilePipeMetadata { // Note: This should only use interfaces as nested data types // as we need to be able to serialize this from/to JSON! -export interface CompileNgModuleSummary { +export interface CompileNgModuleSummary extends CompileTypeSummary { isSummary: boolean /* TODO: `true` when we drop TS 1.8 support */; type: CompileTypeMetadata; @@ -510,9 +512,9 @@ export interface CompileNgModuleSummary { // Note: This is transitive. entryComponents: CompileIdentifierMetadata[]; // Note: This is transitive. - providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[], - // Note: This is transitive. - modules: CompileTypeMetadata[]; + providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[]; + // Note: This is transitive. + modules: CompileTypeMetadata[]; } /** diff --git a/modules/@angular/compiler/src/i18n/extractor.ts b/modules/@angular/compiler/src/i18n/extractor.ts index 8ac6e4cca7..fce0f41208 100644 --- a/modules/@angular/compiler/src/i18n/extractor.ts +++ b/modules/@angular/compiler/src/i18n/extractor.ts @@ -15,6 +15,7 @@ import {ViewEncapsulation} from '@angular/core'; import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler'; import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities'; import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector'; +import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver'; import {CompileDirectiveMetadata} from '../compile_metadata'; import {CompilerConfig} from '../config'; import {DirectiveNormalizer} from '../directive_normalizer'; @@ -41,7 +42,7 @@ export interface ExtractorOptions { * The host of the Extractor disconnects the implementation from TypeScript / other language * services and from underlying file systems. */ -export interface ExtractorHost extends StaticReflectorHost { +export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHost { /** * Loads a resource (e.g. html / css) */ @@ -110,7 +111,8 @@ export class Extractor { const elementSchemaRegistry = new DomElementSchemaRegistry(); const resolver = new CompileMetadataResolver( new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), - new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector); + new PipeResolver(staticReflector), new AotSummaryResolver(host, staticReflector, options), + elementSchemaRegistry, normalizer, staticReflector); // TODO(vicb): implicit tags & attributes const messageBundle = new MessageBundle(htmlParser, [], {}); diff --git a/modules/@angular/compiler/src/jit/compiler.ts b/modules/@angular/compiler/src/jit/compiler.ts index 7a9c261990..5ef6c022f9 100644 --- a/modules/@angular/compiler/src/jit/compiler.ts +++ b/modules/@angular/compiler/src/jit/compiler.ts @@ -101,18 +101,14 @@ export class JitCompiler implements Compiler { private _loadModules(mainModule: any, isSync: boolean): Promise { const loadingPromises: Promise[] = []; - const {ngModule, loading} = - this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(mainModule, isSync); - loadingPromises.push(loading); + const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule); // Note: the loadingPromise for a module only includes the loading of the exported directives // of imported modules. // However, for runtime compilation, we want to transitively compile all modules, // so we also need to call loadNgModuleMetadata for all nested modules. ngModule.transitiveModule.modules.forEach((localModuleMeta) => { - loadingPromises.push( - this._metadataResolver - .loadNgModuleDirectiveAndPipeMetadata(localModuleMeta.reference, isSync) - .loading); + loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata( + localModuleMeta.reference, isSync)); }); return Promise.all(loadingPromises); } diff --git a/modules/@angular/compiler/src/jit/compiler_factory.ts b/modules/@angular/compiler/src/jit/compiler_factory.ts index 81e72de7df..a7c09cbb6b 100644 --- a/modules/@angular/compiler/src/jit/compiler_factory.ts +++ b/modules/@angular/compiler/src/jit/compiler_factory.ts @@ -26,6 +26,7 @@ import {ResourceLoader} from '../resource_loader'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {StyleCompiler} from '../style_compiler'; +import {SummaryResolver} from '../summary_resolver'; import {TemplateParser} from '../template_parser/template_parser'; import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from '../url_resolver'; import {ViewCompiler} from '../view_compiler/view_compiler'; @@ -46,6 +47,7 @@ export const COMPILER_PROVIDERS: Array|{[k: string]: any}|any[]> = {provide: Reflector, useValue: reflector}, {provide: ReflectorReader, useExisting: Reflector}, {provide: ResourceLoader, useValue: _NO_RESOURCE_LOADER}, + SummaryResolver, Console, Lexer, Parser, diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index bb5c42d6b3..a16efc65cc 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -21,6 +21,7 @@ import {NgModuleResolver} from './ng_module_resolver'; import {PipeResolver} from './pipe_resolver'; import {ComponentStillLoadingError, LIFECYCLE_HOOKS_VALUES, ReflectorReader, reflector} from './private_import_core'; import {ElementSchemaRegistry} from './schema/element_schema_registry'; +import {SummaryResolver} from './summary_resolver'; import {getUrlScheme} from './url_resolver'; import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, visitValue} from './util'; @@ -39,12 +40,14 @@ export class CompileMetadataResolver { private _directiveSummaryCache = new Map, cpl.CompileDirectiveSummary>(); private _pipeCache = new Map, cpl.CompilePipeMetadata>(); private _pipeSummaryCache = new Map, cpl.CompilePipeSummary>(); + private _ngModuleSummaryCache = new Map, cpl.CompileNgModuleSummary>(); private _ngModuleCache = new Map, cpl.CompileNgModuleMetadata>(); private _ngModuleOfTypes = new Map, Type>(); constructor( private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, - private _pipeResolver: PipeResolver, private _schemaRegistry: ElementSchemaRegistry, + private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver, + private _schemaRegistry: ElementSchemaRegistry, private _directiveNormalizer: DirectiveNormalizer, private _reflector: ReflectorReader = reflector) {} @@ -54,6 +57,7 @@ export class CompileMetadataResolver { this._directiveSummaryCache.delete(type); this._pipeCache.delete(type); this._pipeSummaryCache.delete(type); + this._ngModuleSummaryCache.delete(type); this._ngModuleOfTypes.delete(type); // Clear all of the NgModule as they contain transitive information! this._ngModuleCache.clear(); @@ -69,6 +73,7 @@ export class CompileMetadataResolver { this._pipeSummaryCache.clear(); this._ngModuleCache.clear(); this._ngModuleOfTypes.clear(); + this._ngModuleSummaryCache.clear(); this._directiveNormalizer.clearCache(); } @@ -126,7 +131,7 @@ export class CompileMetadataResolver { return null; } - private _loadDirectiveMetadata(directiveType: any, isSync: boolean): () => Promise { + private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise { if (this._directiveCache.has(directiveType)) { return; } @@ -176,7 +181,7 @@ export class CompileMetadataResolver { if (isSync) { throw new ComponentStillLoadingError(directiveType); } - return () => templateMeta.asyncResult.then(createDirectiveMetadata); + return templateMeta.asyncResult.then(createDirectiveMetadata); } } else { // directive @@ -288,10 +293,15 @@ export class CompileMetadataResolver { } getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary { - const dirSummary = this._directiveSummaryCache.get(dirType); + let dirSummary = this._directiveSummaryCache.get(dirType); if (!dirSummary) { - throw new Error( - `Illegal state: getDirectiveSummary can only be called after loadNgModuleMetadata for a module that imports it. Directive ${stringify(dirType)}.`); + dirSummary = this._summaryResolver.resolveSummary(dirType); + if (dirSummary) { + this._directiveSummaryCache.set(dirType, dirSummary); + } else { + throw new Error( + `Illegal state: Could not load the summary for directive ${stringify(dirType)}.`); + } } return dirSummary; } @@ -300,33 +310,38 @@ export class CompileMetadataResolver { isPipe(type: any) { return this._pipeResolver.isPipe(type); } - private _getNgModuleSummary(moduleType: any): cpl.CompileNgModuleSummary { - // TODO(tbosch): add logic to read summary files! - // - needs to add directive / pipe summaries to this._directiveSummaryCache / - // this._pipeSummaryCache as well! - const moduleMeta = this.getNgModuleMetadata(moduleType, false); - return moduleMeta ? moduleMeta.toSummary() : null; + getNgModuleSummary(moduleType: any): cpl.CompileNgModuleSummary { + let moduleSummary = this._ngModuleSummaryCache.get(moduleType); + if (!moduleSummary) { + moduleSummary = this._summaryResolver.resolveSummary(moduleType); + if (!moduleSummary) { + const moduleMeta = this.getNgModuleMetadata(moduleType, false); + moduleSummary = moduleMeta ? moduleMeta.toSummary() : null; + } + if (moduleSummary) { + this._ngModuleSummaryCache.set(moduleType, moduleSummary); + } + } + return moduleSummary; } /** - * Loads an NgModule and all of its directives. This includes loading the exported directives of - * imported modules, - * but not private directives of imported modules. + * Loads the declared directives and pipes of an NgModule. */ loadNgModuleDirectiveAndPipeMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true): - {ngModule: cpl.CompileNgModuleMetadata, loading: Promise} { + Promise { const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound); - let loading: Promise; + const loading: Promise[] = []; if (ngModule) { - loading = Promise.all([ - ...ngModule.transitiveModule.directives.map( - (id) => this._loadDirectiveMetadata(id.reference, isSync)), - ...ngModule.transitiveModule.pipes.map((id) => this._loadPipeMetadata(id.reference)), - ]); - } else { - loading = Promise.resolve(null); + ngModule.declaredDirectives.forEach((id) => { + const promise = this._loadDirectiveMetadata(id.reference, isSync); + if (promise) { + loading.push(promise); + } + }); + ngModule.declaredPipes.forEach((id) => this._loadPipeMetadata(id.reference)); } - return {ngModule, loading}; + return Promise.all(loading); } getNgModuleMetadata(moduleType: any, throwIfNotFound = true): cpl.CompileNgModuleMetadata { @@ -365,7 +380,7 @@ export class CompileMetadataResolver { } if (importedModuleType) { - const importedModuleSummary = this._getNgModuleSummary(importedModuleType); + const importedModuleSummary = this.getNgModuleSummary(importedModuleType); if (!importedModuleSummary) { throw new Error( `Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`); @@ -384,7 +399,7 @@ export class CompileMetadataResolver { throw new Error( `Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`); } - const exportedModuleSummary = this._getNgModuleSummary(exportedType); + const exportedModuleSummary = this.getNgModuleSummary(exportedType); if (exportedModuleSummary) { exportedModules.push(exportedModuleSummary); } else { @@ -543,6 +558,7 @@ export class CompileMetadataResolver { entryComponents.push(comp); } }); + const addedTokens = new Set(); modSummary.providers.forEach((entry) => { const tokenRef = cpl.tokenReference(entry.provider.token); let prevModules = modulesByToken.get(tokenRef); @@ -551,8 +567,11 @@ export class CompileMetadataResolver { modulesByToken.set(tokenRef, prevModules); } const moduleRef = entry.module.reference; - if (!prevModules.has(moduleRef)) { + // Note: the providers of one module may still contain multiple providers + // per token (e.g. for multi providers), and we need to preserve these. + if (addedTokens.has(tokenRef) || !prevModules.has(moduleRef)) { prevModules.add(moduleRef); + addedTokens.add(tokenRef); providers.push(entry); } }); @@ -575,7 +594,7 @@ export class CompileMetadataResolver { visitedModules.add(ngModule.type.reference); targetModules.push(ngModule); this._getTransitiveExportedModules( - ngModule.exportedModules.map(id => this._getNgModuleSummary(id.reference)), + ngModule.exportedModules.map(id => this.getNgModuleSummary(id.reference)), targetModules, visitedModules); } }); @@ -617,10 +636,15 @@ export class CompileMetadataResolver { } getPipeSummary(pipeType: any): cpl.CompilePipeSummary { - const pipeSummary = this._pipeSummaryCache.get(pipeType); + let pipeSummary = this._pipeSummaryCache.get(pipeType); if (!pipeSummary) { - throw new Error( - `Illegal state: getPipeSummary can only be called after loadNgModuleMetadata for a module that imports it. Pipe ${stringify(pipeType)}.`); + pipeSummary = this._summaryResolver.resolveSummary(pipeType); + if (pipeSummary) { + this._pipeSummaryCache.set(pipeType, pipeSummary); + } else { + throw new Error( + `Illegal state: Could not load the summary for pipe ${stringify(pipeType)}.`); + } } return pipeSummary; } diff --git a/modules/@angular/compiler/src/summary_resolver.ts b/modules/@angular/compiler/src/summary_resolver.ts new file mode 100644 index 0000000000..a64651ace2 --- /dev/null +++ b/modules/@angular/compiler/src/summary_resolver.ts @@ -0,0 +1,14 @@ +/** + * @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 + */ +import {Injectable} from '@angular/core'; +import {CompileSummary} from './compile_metadata'; + +@Injectable() +export class SummaryResolver { + resolveSummary(reference: any): CompileSummary { return null; } +} diff --git a/modules/@angular/compiler/test/aot/static_reflector_spec.ts b/modules/@angular/compiler/test/aot/static_reflector_spec.ts index 35cf5d5190..21ee4f0f4c 100644 --- a/modules/@angular/compiler/test/aot/static_reflector_spec.ts +++ b/modules/@angular/compiler/test/aot/static_reflector_spec.ts @@ -694,7 +694,7 @@ describe('StaticReflector', () => { }); -class MockStaticReflectorHost implements StaticReflectorHost { +export class MockStaticReflectorHost implements StaticReflectorHost { private collector = new MetadataCollector(); constructor(private data: {[key: string]: any}) {} diff --git a/modules/@angular/compiler/test/aot/summary_resolver_spec.ts b/modules/@angular/compiler/test/aot/summary_resolver_spec.ts new file mode 100644 index 0000000000..b45277f5a5 --- /dev/null +++ b/modules/@angular/compiler/test/aot/summary_resolver_spec.ts @@ -0,0 +1,93 @@ +/** + * @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 + */ + +import {AotSummaryResolver, AotSummaryResolverHost, CompileTypeSummary, StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; +import * as path from 'path'; + +import {MockStaticReflectorHost} from './static_reflector_spec'; + +const EXT = /\.ts$|.d.ts$/; + +export function main() { + describe('AotSummaryResolver', () => { + let resolver: AotSummaryResolver; + let staticReflector: StaticReflector; + + function init(summaries: {[filePath: string]: string} = {}) { + // Note: We don't give the static reflector metadata files, + // so that we can test that we can deserialize summary files + // without reading metadata files. This is important + // as summary files can contain references to files of transitive compilation + // dependencies, and we don't want to read their metadata files. + staticReflector = new StaticReflector(new MockStaticReflectorHost({})); + const host = new MockAotSummaryResolverHost(summaries); + resolver = new AotSummaryResolver(host, staticReflector, {excludeFilePattern: /\.d\.ts$/}); + } + + it('should add .ngsummary.json to the filename', () => { + init(); + expect(resolver.serializeSummaries('a.ts', []).genFileUrl).toBe('a.ngsummary.json'); + expect(resolver.serializeSummaries('a.d.ts', []).genFileUrl).toBe('a.ngsummary.json'); + expect(resolver.serializeSummaries('a.js', []).genFileUrl).toBe('a.ngsummary.json'); + }); + + it('should serialize plain data', () => { + init(); + const data = [{a: 'b'}]; + expect(JSON.parse(resolver.serializeSummaries('someSourceFile', data).source)).toEqual(data); + }); + + it('should serialize summary for .ts files and deserialize based on .d.ts files', () => { + init(); + const serializedData = resolver.serializeSummaries( + '/tmp/some_class.ts', [{ + isSummary: true, + type: { + reference: staticReflector.getStaticSymbol('/tmp/some_class.ts', 'SomeClass'), + diDeps: [], + lifecycleHooks: [] + } + }]); + + // Note: this creates a new staticReflector! + init({[serializedData.genFileUrl]: serializedData.source}); + + expect(resolver.resolveSummary( + staticReflector.getStaticSymbol('/tmp/some_class.d.ts', 'SomeClass'))) + .toEqual({ + isSummary: true, + type: { + reference: staticReflector.getStaticSymbol('/tmp/some_class.d.ts', 'SomeClass'), + diDeps: [], + lifecycleHooks: [] + } + }); + }); + + }); +} + +class MockAotSummaryResolverHost implements AotSummaryResolverHost { + constructor(private summaries: {[fileName: string]: string}) {} + + loadSummary(filePath: string): string { + const result = this.summaries[filePath]; + if (!result) { + throw new Error(`Could not find summary for ${filePath}`); + } + return result; + } + + fileNameToModuleName(fileName: string): string { + return './' + path.basename(fileName).replace(EXT, ''); + } + + getOutputFileName(sourceFileName: string): string { + return sourceFileName.replace(EXT, '') + '.d.ts'; + } +} \ No newline at end of file diff --git a/modules/@angular/compiler/test/metadata_resolver_spec.ts b/modules/@angular/compiler/test/metadata_resolver_spec.ts index 087618eb55..caaf40e42a 100644 --- a/modules/@angular/compiler/test/metadata_resolver_spec.ts +++ b/modules/@angular/compiler/test/metadata_resolver_spec.ts @@ -84,7 +84,7 @@ export function main() { } resourceLoader.when('someTemplateUrl', 'someTemplate'); - resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, false).loading.then(() => { + resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, false).then(() => { const meta = resolver.getDirectiveMetadata(ComponentWithExternalResources); expect(meta.selector).toEqual('someSelector'); expect(meta.template.styleUrls).toEqual(['someStyleUrl']); @@ -94,29 +94,6 @@ export function main() { resourceLoader.flush(); }))); - it('should wait for external resources of imported modules', - async(inject( - [CompileMetadataResolver, ResourceLoader], - (resolver: CompileMetadataResolver, resourceLoader: MockResourceLoader) => { - @NgModule({ - declarations: [ComponentWithExternalResources], - exports: [ComponentWithExternalResources] - }) - class SomeImportedModule { - } - - @NgModule({imports: [SomeImportedModule]}) - class SomeModule { - } - - resourceLoader.when('someTemplateUrl', 'someTemplate'); - resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, false).loading.then(() => { - const meta = resolver.getDirectiveMetadata(ComponentWithExternalResources); - expect(meta.selector).toEqual('someSelector'); - }); - resourceLoader.flush(); - }))); - it('should use `./` as base url for templates during runtime compilation if no moduleId is given', async(inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { @Component({selector: 'someComponent', templateUrl: 'someUrl'}) @@ -128,7 +105,7 @@ export function main() { class SomeModule { } - resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, false).loading.then(() => { + resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, false).then(() => { const value: string = resolver.getDirectiveMetadata(ComponentWithoutModuleId).template.templateUrl; const expectedEndValue = './someUrl'; @@ -329,7 +306,7 @@ export function main() { class MyModule { } - const modMeta = resolver.loadNgModuleDirectiveAndPipeMetadata(MyModule, true).ngModule; + const modMeta = resolver.getNgModuleMetadata(MyModule); expect(modMeta.declaredDirectives.length).toBe(1); expect(modMeta.declaredDirectives[0].reference).toBe(MyComp); })); diff --git a/modules/@angular/language-service/src/typescript_host.ts b/modules/@angular/language-service/src/typescript_host.ts index 70245b5f43..b4e3df6cdb 100644 --- a/modules/@angular/language-service/src/typescript_host.ts +++ b/modules/@angular/language-service/src/typescript_host.ts @@ -18,6 +18,7 @@ import {NgModuleResolver} from '@angular/compiler/src/ng_module_resolver'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {ResourceLoader} from '@angular/compiler/src/resource_loader'; import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry'; +import {SummaryResolver} from '@angular/compiler/src/summary_resolver'; import {UrlResolver} from '@angular/compiler/src/url_resolver'; import {Type, ViewEncapsulation} from '@angular/core'; import * as fs from 'fs'; @@ -124,8 +125,8 @@ export class TypeScriptServiceHost implements LanguageServiceHost { new DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config); result = this._resolver = new CompileMetadataResolver( - moduleResolver, directiveResolver, pipeResolver, elementSchemaRegistry, - directiveNormalizer, this.reflector); + moduleResolver, directiveResolver, pipeResolver, new SummaryResolver(), + elementSchemaRegistry, directiveNormalizer, this.reflector); } return result; } diff --git a/scripts/ci-lite/offline_compiler_test.sh b/scripts/ci-lite/offline_compiler_test.sh index d2698efbd9..dcb056b15d 100755 --- a/scripts/ci-lite/offline_compiler_test.sh +++ b/scripts/ci-lite/offline_compiler_test.sh @@ -50,6 +50,8 @@ cp -v package.json $TMP ./node_modules/.bin/ng-xi18n -p tsconfig-build.json --i18nFormat=xlf ./node_modules/.bin/ng-xi18n -p tsconfig-build.json --i18nFormat=xmb + node test/test_summaries.js + ./node_modules/.bin/jasmine init # Run compiler-cli integration tests in node ./node_modules/.bin/webpack ./webpack.config.js