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