From 17e4cfc748cdd2471be4acb0111f361f716ec89b Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Tue, 28 Jun 2016 09:54:42 -0700 Subject: [PATCH] feat(core): introduce `@AppModule` Main part for #9726 Closes #9730 --- modules/@angular/compiler-cli/README.md | 3 + .../integrationtest/src/app_module.ts | 53 ++ .../integrationtest/test/app_module_spec.ts | 75 +++ modules/@angular/compiler-cli/src/codegen.ts | 103 ++-- .../compiler-cli/src/compiler_private.ts | 3 + .../@angular/compiler-cli/src/extract_i18n.ts | 53 +- .../compiler-cli/src/static_reflector.ts | 4 +- modules/@angular/compiler/compiler.ts | 2 +- modules/@angular/compiler/core_private.ts | 1 + modules/@angular/compiler/private_export.ts | 4 + .../compiler/src/app_module_compiler.ts | 215 ++++++++ .../@angular/compiler/src/compile_metadata.ts | 69 ++- modules/@angular/compiler/src/compiler.ts | 18 +- .../compiler/src/directive_normalizer.ts | 17 +- modules/@angular/compiler/src/identifiers.ts | 19 +- .../compiler/src/metadata_resolver.ts | 101 +++- .../@angular/compiler/src/offline_compiler.ts | 226 ++++++--- .../compiler/src/output/interpretive_view.ts | 86 ---- .../compiler/src/output/output_ast.ts | 35 +- .../compiler/src/output/output_interpreter.ts | 220 ++------ modules/@angular/compiler/src/parse_util.ts | 60 ++- .../@angular/compiler/src/provider_parser.ts | 112 +++- .../@angular/compiler/src/runtime_compiler.ts | 172 +++++-- modules/@angular/compiler/src/util.ts | 23 +- .../src/view_compiler/compile_element.ts | 33 +- .../src/view_compiler/compile_view.ts | 3 +- .../compiler/src/view_compiler/util.ts | 13 +- .../src/view_compiler/view_builder.ts | 3 +- .../test/offline_compiler_codegen_typed.ts | 35 -- .../test/offline_compiler_codegen_untyped.ts | 31 -- .../test/offline_compiler_compa.css.shim.ts | 10 - .../compiler/test/offline_compiler_spec.ts | 65 --- .../compiler/test/offline_compiler_util.ts | 94 ---- .../compiler/test/output/js_emitter_spec.ts | 2 +- .../output/output_emitter_codegen_typed.ts | 3 +- .../output/output_emitter_codegen_untyped.ts | 3 +- .../test/output/output_emitter_spec.ts | 5 +- .../test/output/output_emitter_util.ts | 25 +- .../compiler/test/output/ts_emitter_spec.ts | 2 +- .../compiler/test/runtime_compiler_spec.ts | 52 +- modules/@angular/core/private_export.ts | 3 + modules/@angular/core/src/di/injector.ts | 13 +- modules/@angular/core/src/linker.ts | 1 + .../core/src/linker/app_module_factory.ts | 90 ++++ modules/@angular/core/src/linker/compiler.ts | 34 +- modules/@angular/core/src/metadata.ts | 42 ++ .../@angular/core/src/metadata/app_module.ts | 113 +++++ .../@angular/core/test/di/injector_spec.ts | 27 + .../linker/app_module_integration_spec.ts | 478 ++++++++++++++++++ .../tsconfig-output_emitter_codegen.json | 4 +- tools/public_api_guard/core/index.d.ts | 72 ++- tools/tsc-watch/index.ts | 6 +- 52 files changed, 2085 insertions(+), 851 deletions(-) create mode 100644 modules/@angular/compiler-cli/integrationtest/src/app_module.ts create mode 100644 modules/@angular/compiler-cli/integrationtest/test/app_module_spec.ts create mode 100644 modules/@angular/compiler/src/app_module_compiler.ts delete mode 100644 modules/@angular/compiler/src/output/interpretive_view.ts delete mode 100644 modules/@angular/compiler/test/offline_compiler_codegen_typed.ts delete mode 100644 modules/@angular/compiler/test/offline_compiler_codegen_untyped.ts delete mode 100644 modules/@angular/compiler/test/offline_compiler_compa.css.shim.ts delete mode 100644 modules/@angular/compiler/test/offline_compiler_spec.ts delete mode 100644 modules/@angular/compiler/test/offline_compiler_util.ts create mode 100644 modules/@angular/core/src/linker/app_module_factory.ts create mode 100644 modules/@angular/core/src/metadata/app_module.ts create mode 100644 modules/@angular/core/test/di/injector_spec.ts create mode 100644 modules/@angular/core/test/linker/app_module_integration_spec.ts diff --git a/modules/@angular/compiler-cli/README.md b/modules/@angular/compiler-cli/README.md index 1d78cb044c..b4f8fc673c 100644 --- a/modules/@angular/compiler-cli/README.md +++ b/modules/@angular/compiler-cli/README.md @@ -107,6 +107,9 @@ $ cp tools/@angular/tsc-wrapped/package.json dist/tools/@angular/tsc-wrapped $ ./scripts/ci-lite/offline_compiler_test.sh # Keep a package fresh in watch mode ./node_modules/.bin/tsc -p modules/@angular/compiler/tsconfig-es5.json -w +# Recompile @angular/core module (needs to use tsc-ext to keep the metadata) +export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools +node dist/tools/@angular/tsc-wrapped/src/main -p modules/@angular/core/tsconfig-es5.json # Iterate on the test cd /tmp/wherever/e2e_test.1464388257/ ./node_modules/.bin/ngc diff --git a/modules/@angular/compiler-cli/integrationtest/src/app_module.ts b/modules/@angular/compiler-cli/integrationtest/src/app_module.ts new file mode 100644 index 0000000000..4c2c794e5a --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/src/app_module.ts @@ -0,0 +1,53 @@ +/** + * @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 {LowerCasePipe, NgIf} from '@angular/common'; +import {AppModule, Component, ComponentFactoryResolver, Injectable} from '@angular/core'; + +@Injectable() +export class SomeService { + public prop = 'someValue'; +} + +@Injectable() +export class NestedService { +} + +@Component({ + selector: 'cmp', + template: `
` +}) +export class SomeComp { + constructor() {} +} + +@Component({selector: 'parent', template: ``, directives: [SomeComp]}) +export class ParentComp { +} + +@AppModule({providers: [NestedService]}) +export class NestedModule { +} + +@AppModule({ + directives: [NgIf], + pipes: [LowerCasePipe], + providers: [SomeService], + precompile: [SomeComp], + modules: [NestedModule] +}) +export class SomeModule { +} + +@AppModule({ + directives: [NgIf], + pipes: [LowerCasePipe], + precompile: [ParentComp], +}) +export class SomeModuleUsingParentComp { +} diff --git a/modules/@angular/compiler-cli/integrationtest/test/app_module_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/app_module_spec.ts new file mode 100644 index 0000000000..49c4637713 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/test/app_module_spec.ts @@ -0,0 +1,75 @@ +/** + * @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 {ComponentFactoryResolver, DebugElement, ReflectiveInjector, getDebugNode, lockRunMode} from '@angular/core'; +import {BROWSER_APP_PROVIDERS, By} from '@angular/platform-browser'; +import {serverPlatform} from '@angular/platform-server'; + +import {NestedModule, NestedService, ParentComp, SomeComp, SomeModule, SomeService} from '../src/app_module'; +import {SomeModuleNgFactory, SomeModuleUsingParentCompNgFactory} from '../src/app_module.ngfactory'; + + +// Need to lock the mode explicitely as this test is not using Angular's testing framework. +lockRunMode(); + +describe('AppModule', () => { + it('should support providers', () => { + var moduleRef = SomeModuleNgFactory.create(); + expect(moduleRef.instance instanceof SomeModule).toBe(true); + expect(moduleRef.injector.get(SomeModule) instanceof SomeModule).toBe(true); + expect(moduleRef.injector.get(SomeService) instanceof SomeService).toBe(true); + }); + + it('should support precompile components', () => { + const appInjector = + ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, serverPlatform().injector); + var moduleRef = SomeModuleNgFactory.create(appInjector); + var cf = moduleRef.injector.get(ComponentFactoryResolver).resolveComponentFactory(SomeComp); + expect(cf.componentType).toBe(SomeComp); + var comp = cf.create(moduleRef.injector); + }); + + it('should support module directives and pipes', () => { + const appInjector = + ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, serverPlatform().injector); + var moduleRef = SomeModuleNgFactory.create(appInjector); + var cf = moduleRef.injector.get(ComponentFactoryResolver).resolveComponentFactory(SomeComp); + var comp = cf.create(moduleRef.injector); + var debugElement = getDebugNode(comp.location.nativeElement); + + // NgIf should work, is being used as module directive + expect(debugElement.children.length).toBe(1); + comp.changeDetectorRef.detectChanges(); + expect(debugElement.children.length).toBe(2); + expect(debugElement.children[0].properties['title']).toBe('hello'); + }); + + it('should support module directives and pipes on nested components', () => { + const appInjector = + ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, serverPlatform().injector); + var moduleRef = SomeModuleUsingParentCompNgFactory.create(appInjector); + var cf = moduleRef.injector.get(ComponentFactoryResolver).resolveComponentFactory(ParentComp); + var comp = cf.create(moduleRef.injector); + var debugElement = getDebugNode(comp.location.nativeElement); + + debugElement = debugElement.children[0]; + // NgIf should work, is being used as module directive + expect(debugElement.children.length).toBe(1); + comp.changeDetectorRef.detectChanges(); + expect(debugElement.children.length).toBe(2); + expect(debugElement.children[0].properties['title']).toBe('hello'); + }); + + it('should support child moduless', () => { + var moduleRef = SomeModuleNgFactory.create(); + expect(moduleRef.instance instanceof SomeModule).toBe(true); + expect(moduleRef.injector.get(NestedModule) instanceof NestedModule).toBe(true); + expect(moduleRef.injector.get(NestedService) instanceof NestedService).toBe(true); + }); + +}); diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index 950086bfa7..7354087736 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -11,15 +11,15 @@ * Intended to be used in a build step. */ import * as compiler from '@angular/compiler'; -import {ViewEncapsulation, lockRunMode} from '@angular/core'; +import {AppModuleMetadata, ComponentMetadata, ViewEncapsulation, lockRunMode} from '@angular/core'; import {AngularCompilerOptions} from '@angular/tsc-wrapped'; import * as path from 'path'; import * as ts from 'typescript'; -import {CompileMetadataResolver, DirectiveNormalizer, DomElementSchemaRegistry, HtmlParser, Lexer, Parser, StyleCompiler, TemplateParser, TypeScriptEmitter, ViewCompiler} from './compiler_private'; +import {AppModuleCompiler, CompileMetadataResolver, DirectiveNormalizer, DomElementSchemaRegistry, HtmlParser, Lexer, Parser, StyleCompiler, TemplateParser, TypeScriptEmitter, ViewCompiler} from './compiler_private'; import {ReflectorHost, ReflectorHostContext} from './reflector_host'; import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; -import {StaticReflector} from './static_reflector'; +import {StaticReflector, StaticSymbol} from './static_reflector'; const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; @@ -40,26 +40,9 @@ export class CodeGenerator { lockRunMode(); } - private generateSource(metadatas: compiler.CompileDirectiveMetadata[]) { - const normalize = (metadata: compiler.CompileDirectiveMetadata) => { - const directiveType = metadata.type.runtime; - const directives = this.resolver.getViewDirectivesMetadata(directiveType); - return Promise.all(directives.map(d => this.compiler.normalizeDirectiveMetadata(d))) - .then(normalizedDirectives => { - const pipes = this.resolver.getViewPipesMetadata(directiveType); - return new compiler.NormalizedComponentWithViewDirectives( - metadata, normalizedDirectives, pipes); - }); - }; - return Promise.all(metadatas.map(normalize)) - .then( - normalizedCompWithDirectives => - this.compiler.compileTemplates(normalizedCompWithDirectives)); - } - - private readComponents(absSourcePath: string) { - const result: Promise[] = []; + private readFileMetadata(absSourcePath: string): FileMetadata { const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath); + const result: FileMetadata = {components: [], appModules: [], fileUrl: absSourcePath}; if (!moduleMetadata) { console.log(`WARNING: no metadata found for ${absSourcePath}`); return result; @@ -75,13 +58,14 @@ export class CodeGenerator { continue; } const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath); - let directive: compiler.CompileDirectiveMetadata; - directive = this.resolver.maybeGetDirectiveMetadata(staticType); - - if (!directive || !directive.isComponent) { - continue; - } - result.push(this.compiler.normalizeDirectiveMetadata(directive)); + const annotations = this.staticReflector.annotations(staticType); + annotations.forEach((annotation) => { + if (annotation instanceof AppModuleMetadata) { + result.appModules.push(staticType); + } else if (annotation instanceof ComponentMetadata) { + result.components.push(staticType); + } + }); } return result; } @@ -102,30 +86,31 @@ export class CodeGenerator { } codegen(): Promise { - const generateOneFile = (absSourcePath: string) => - Promise.all(this.readComponents(absSourcePath)) - .then((metadatas: compiler.CompileDirectiveMetadata[]) => { - if (!metadatas || !metadatas.length) { - return; - } - return this.generateSource(metadatas); - }) - .then(generatedModules => { - if (generatedModules) { - generatedModules.forEach((generatedModule) => { - const sourceFile = this.program.getSourceFile(absSourcePath); - const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); - this.host.writeFile( - emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]); - }); - } - }) - .catch((e) => { console.error(e.stack); }); - var compPromises = this.program.getSourceFiles() - .map(sf => sf.fileName) - .filter(f => !GENERATED_FILES.test(f)) - .map(generateOneFile); - return Promise.all(compPromises); + let filePaths = + this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.test(f)); + let fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath)); + let appModules = fileMetas.reduce((appModules, fileMeta) => { + appModules.push(...fileMeta.appModules); + return appModules; + }, []); + let analyzedAppModules = this.compiler.analyzeModules(appModules); + return Promise + .all(fileMetas.map( + (fileMeta) => this.compiler + .compile( + fileMeta.fileUrl, analyzedAppModules, fileMeta.components, + fileMeta.appModules) + .then((generatedModules) => { + generatedModules.forEach((generatedModule) => { + const sourceFile = this.program.getSourceFile(fileMeta.fileUrl); + const emitPath = + this.calculateEmitPath(generatedModule.moduleUrl); + this.host.writeFile( + emitPath, PREAMBLE + generatedModule.source, false, () => {}, + [sourceFile]); + }); + }))) + .catch((e) => { console.error(e.stack); }); } static create( @@ -158,14 +143,20 @@ export class CodeGenerator { const tmplParser = new TemplateParser( parser, new DomElementSchemaRegistry(), htmlParser, /*console*/ null, []); - const offlineCompiler = new compiler.OfflineCompiler( - normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config), - new TypeScriptEmitter(reflectorHost)); const resolver = new CompileMetadataResolver( new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), new compiler.ViewResolver(staticReflector), config, staticReflector); + const offlineCompiler = new compiler.OfflineCompiler( + resolver, normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config), + new AppModuleCompiler(), new TypeScriptEmitter(reflectorHost)); return new CodeGenerator( options, program, compilerHost, staticReflector, resolver, offlineCompiler, reflectorHost); } } + +interface FileMetadata { + fileUrl: string; + components: StaticSymbol[]; + appModules: StaticSymbol[]; +} \ No newline at end of file diff --git a/modules/@angular/compiler-cli/src/compiler_private.ts b/modules/@angular/compiler-cli/src/compiler_private.ts index 16e745d1b9..06617f3c16 100644 --- a/modules/@angular/compiler-cli/src/compiler_private.ts +++ b/modules/@angular/compiler-cli/src/compiler_private.ts @@ -61,5 +61,8 @@ export var StyleCompiler: typeof _c.StyleCompiler = _c.StyleCompiler; export type ViewCompiler = _c.ViewCompiler; export var ViewCompiler: typeof _c.ViewCompiler = _c.ViewCompiler; +export type AppModuleCompiler = _c.AppModuleCompiler; +export var AppModuleCompiler: typeof _c.AppModuleCompiler = _c.AppModuleCompiler; + export type TypeScriptEmitter = _c.TypeScriptEmitter; export var TypeScriptEmitter: typeof _c.TypeScriptEmitter = _c.TypeScriptEmitter; diff --git a/modules/@angular/compiler-cli/src/extract_i18n.ts b/modules/@angular/compiler-cli/src/extract_i18n.ts index 8106f8ccb0..ca42ddc000 100644 --- a/modules/@angular/compiler-cli/src/extract_i18n.ts +++ b/modules/@angular/compiler-cli/src/extract_i18n.ts @@ -22,7 +22,7 @@ import * as compiler from '@angular/compiler'; import {ViewEncapsulation, lockRunMode} from '@angular/core'; import {StaticReflector} from './static_reflector'; -import {CompileMetadataResolver, HtmlParser, DirectiveNormalizer, Lexer, Parser, TemplateParser, DomElementSchemaRegistry, StyleCompiler, ViewCompiler, TypeScriptEmitter, MessageExtractor, removeDuplicates, ExtractionResult, Message, ParseError, serializeXmb,} from './compiler_private'; +import {CompileMetadataResolver, HtmlParser, DirectiveNormalizer, Lexer, Parser, DomElementSchemaRegistry, TypeScriptEmitter, MessageExtractor, removeDuplicates, ExtractionResult, Message, ParseError, serializeXmb,} from './compiler_private'; import {ReflectorHost} from './reflector_host'; import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; @@ -40,42 +40,27 @@ class Extractor { constructor( private _options: tsc.AngularCompilerOptions, private _program: ts.Program, public host: ts.CompilerHost, private staticReflector: StaticReflector, - private _resolver: CompileMetadataResolver, private _compiler: compiler.OfflineCompiler, + private _resolver: CompileMetadataResolver, private _normalizer: DirectiveNormalizer, private _reflectorHost: ReflectorHost, private _extractor: MessageExtractor) { lockRunMode(); } - private _extractCmpMessages(metadatas: compiler.CompileDirectiveMetadata[]): - Promise { - if (!metadatas || !metadatas.length) { + private _extractCmpMessages(components: compiler.CompileDirectiveMetadata[]): ExtractionResult { + if (!components || !components.length) { return null; } - const normalize = (metadata: compiler.CompileDirectiveMetadata) => { - const directiveType = metadata.type.runtime; - const directives = this._resolver.getViewDirectivesMetadata(directiveType); - return Promise.all(directives.map(d => this._compiler.normalizeDirectiveMetadata(d))) - .then(normalizedDirectives => { - const pipes = this._resolver.getViewPipesMetadata(directiveType); - return new compiler.NormalizedComponentWithViewDirectives( - metadata, normalizedDirectives, pipes); - }); - }; + let messages: Message[] = []; + let errors: ParseError[] = []; + components.forEach(metadata => { + let url = _dirPaths.get(metadata); + let result = this._extractor.extract(metadata.template.template, url); + errors = errors.concat(result.errors); + messages = messages.concat(result.messages); + }); - return Promise.all(metadatas.map(normalize)) - .then((cmps: compiler.NormalizedComponentWithViewDirectives[]) => { - let messages: Message[] = []; - let errors: ParseError[] = []; - cmps.forEach(cmp => { - let url = _dirPaths.get(cmp.component); - let result = this._extractor.extract(cmp.component.template.template, url); - errors = errors.concat(result.errors); - messages = messages.concat(result.messages); - }); - - // Extraction Result might contain duplicate messages at this point - return new ExtractionResult(messages, errors); - }); + // Extraction Result might contain duplicate messages at this point + return new ExtractionResult(messages, errors); } private _readComponents(absSourcePath: string): Promise[] { @@ -96,7 +81,7 @@ class Extractor { directive = this._resolver.maybeGetDirectiveMetadata(staticType); if (directive && directive.isComponent) { - let promise = this._compiler.normalizeDirectiveMetadata(directive); + let promise = this._normalizer.normalizeDirective(directive).asyncResult; promise.then(md => _dirPaths.set(md, absSourcePath)); result.push(promise); } @@ -165,12 +150,6 @@ class Extractor { }); const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config); const parser = new Parser(new Lexer()); - const tmplParser = new TemplateParser( - parser, new DomElementSchemaRegistry(), htmlParser, - /*console*/ null, []); - const offlineCompiler = new compiler.OfflineCompiler( - normalizer, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config), - new TypeScriptEmitter(reflectorHost)); const resolver = new CompileMetadataResolver( new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), new compiler.ViewResolver(staticReflector), config, staticReflector); @@ -179,7 +158,7 @@ class Extractor { const extractor = new MessageExtractor(htmlParser, parser, [], {}); return new Extractor( - options, program, compilerHost, staticReflector, resolver, offlineCompiler, reflectorHost, + options, program, compilerHost, staticReflector, resolver, normalizer, reflectorHost, extractor); } } diff --git a/modules/@angular/compiler-cli/src/static_reflector.ts b/modules/@angular/compiler-cli/src/static_reflector.ts index 171f7538b6..6109b64c5b 100644 --- a/modules/@angular/compiler-cli/src/static_reflector.ts +++ b/modules/@angular/compiler-cli/src/static_reflector.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AttributeMetadata, ComponentMetadata, ContentChildMetadata, ContentChildrenMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, HostMetadata, InjectMetadata, InjectableMetadata, InputMetadata, OptionalMetadata, OutputMetadata, PipeMetadata, Provider, QueryMetadata, SelfMetadata, SkipSelfMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; +import {AppModuleMetadata, AttributeMetadata, ComponentMetadata, ContentChildMetadata, ContentChildrenMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, HostMetadata, InjectMetadata, InjectableMetadata, InputMetadata, OptionalMetadata, OutputMetadata, PipeMetadata, Provider, QueryMetadata, SelfMetadata, SkipSelfMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {ReflectorReader} from './core_private'; @@ -216,6 +216,8 @@ export class StaticReflector implements ReflectorReader { this.host.findDeclaration(coreDecorators, 'Directive'), DirectiveMetadata); this.registerDecoratorOrConstructor( this.host.findDeclaration(coreDecorators, 'Component'), ComponentMetadata); + this.registerDecoratorOrConstructor( + this.host.findDeclaration(coreDecorators, 'AppModule'), AppModuleMetadata); // Note: Some metadata classes can be used directly with Provider.deps. this.registerDecoratorOrConstructor( diff --git a/modules/@angular/compiler/compiler.ts b/modules/@angular/compiler/compiler.ts index 2d20021a92..4812b0f876 100644 --- a/modules/@angular/compiler/compiler.ts +++ b/modules/@angular/compiler/compiler.ts @@ -11,7 +11,7 @@ * @description * Starting point to import all compiler APIs. */ -export {COMPILER_PROVIDERS, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileFactoryMetadata, CompileIdentifierMetadata, CompileMetadataWithIdentifier, CompileMetadataWithType, CompilePipeMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata, CompilerConfig, DEFAULT_PACKAGE_URL_PROVIDER, DirectiveResolver, NormalizedComponentWithViewDirectives, OfflineCompiler, PipeResolver, RenderTypes, RuntimeCompiler, SourceModule, TEMPLATE_TRANSFORMS, UrlResolver, ViewResolver, XHR, createOfflineCompileUrlResolver} from './src/compiler'; +export {COMPILER_PROVIDERS, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileFactoryMetadata, CompileIdentifierMetadata, CompileMetadataWithIdentifier, CompileMetadataWithType, CompilePipeMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata, CompilerConfig, DEFAULT_PACKAGE_URL_PROVIDER, DirectiveResolver, OfflineCompiler, PipeResolver, RenderTypes, RuntimeCompiler, SourceModule, TEMPLATE_TRANSFORMS, UrlResolver, ViewResolver, XHR, createOfflineCompileUrlResolver} from './src/compiler'; export {ElementSchemaRegistry} from './src/schema/element_schema_registry'; export * from './src/template_ast'; diff --git a/modules/@angular/compiler/core_private.ts b/modules/@angular/compiler/core_private.ts index 9e4d0e46ac..a47beb008c 100644 --- a/modules/@angular/compiler/core_private.ts +++ b/modules/@angular/compiler/core_private.ts @@ -29,6 +29,7 @@ export var CodegenComponentFactoryResolver: typeof t.CodegenComponentFactoryReso export var AppView: typeof t.AppView = r.AppView; export type DebugAppView = t.DebugAppView; export var DebugAppView: typeof t.DebugAppView = r.DebugAppView; +export var AppModuleInjector: typeof t.AppModuleInjector = r.AppModuleInjector; export type ViewType = t.ViewType; export var ViewType: typeof t.ViewType = r.ViewType; export var MAX_INTERPOLATION_VALUES: typeof t.MAX_INTERPOLATION_VALUES = r.MAX_INTERPOLATION_VALUES; diff --git a/modules/@angular/compiler/private_export.ts b/modules/@angular/compiler/private_export.ts index a53a2fe72d..be6557d3ab 100644 --- a/modules/@angular/compiler/private_export.ts +++ b/modules/@angular/compiler/private_export.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import * as app_module_compiler from './src/app_module_compiler'; import * as directive_normalizer from './src/directive_normalizer'; import * as lexer from './src/expression_parser/lexer'; import * as parser from './src/expression_parser/parser'; @@ -98,6 +99,9 @@ export var StyleCompiler = style_compiler.StyleCompiler; export type ViewCompiler = view_compiler.ViewCompiler; export var ViewCompiler = view_compiler.ViewCompiler; +export type AppModuleCompiler = app_module_compiler.AppModuleCompiler; +export var AppModuleCompiler = app_module_compiler.AppModuleCompiler; + export type TypeScriptEmitter = ts_emitter.TypeScriptEmitter; export var TypeScriptEmitter = ts_emitter.TypeScriptEmitter; } diff --git a/modules/@angular/compiler/src/app_module_compiler.ts b/modules/@angular/compiler/src/app_module_compiler.ts new file mode 100644 index 0000000000..907c7fecdd --- /dev/null +++ b/modules/@angular/compiler/src/app_module_compiler.ts @@ -0,0 +1,215 @@ +/** + * @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 {CompileAppModuleMetadata, CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileTokenMap, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata'; +import {isBlank, isPresent} from './facade/lang'; +import {Identifiers, identifierToken} from './identifiers'; +import * as o from './output/output_ast'; +import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util'; +import {AppModuleProviderParser} from './provider_parser'; +import {ProviderAst, ProviderAstType} from './template_ast'; +import {createDiTokenExpression} from './util'; + +export class ComponentFactoryDependency { + constructor( + public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {} +} + +export class AppModuleCompileResult { + constructor( + public statements: o.Statement[], public appModuleFactoryVar: string, + public dependencies: ComponentFactoryDependency[]) {} +} + +@Injectable() +export class AppModuleCompiler { + compile(appModuleMeta: CompileAppModuleMetadata): AppModuleCompileResult { + var sourceFileName = isPresent(appModuleMeta.type.moduleUrl) ? + `in AppModule ${appModuleMeta.type.name} in ${appModuleMeta.type.moduleUrl}` : + `in AppModule ${appModuleMeta.type.name}`; + var sourceFile = new ParseSourceFile('', sourceFileName); + var sourceSpan = new ParseSourceSpan( + new ParseLocation(sourceFile, null, null, null), + new ParseLocation(sourceFile, null, null, null)); + var deps: ComponentFactoryDependency[] = []; + var precompileComponents = appModuleMeta.precompile.map((precompileComp) => { + var id = new CompileIdentifierMetadata({name: precompileComp.name}); + deps.push(new ComponentFactoryDependency(precompileComp, id)); + return id; + }); + var builder = new _InjectorBuilder(appModuleMeta, precompileComponents, sourceSpan); + + var providerParser = new AppModuleProviderParser(appModuleMeta, sourceSpan); + providerParser.parse().forEach((provider) => builder.addProvider(provider)); + var injectorClass = builder.build(); + var appModuleFactoryVar = `${appModuleMeta.type.name}NgFactory`; + var appModuleFactoryStmt = + o.variable(appModuleFactoryVar) + .set(o.importExpr(Identifiers.AppModuleFactory) + .instantiate( + [o.variable(injectorClass.name), o.importExpr(appModuleMeta.type)], + o.importType( + Identifiers.AppModuleFactory, [o.importType(appModuleMeta.type)], + [o.TypeModifier.Const]))) + .toDeclStmt(null, [o.StmtModifier.Final]); + + return new AppModuleCompileResult( + [injectorClass, appModuleFactoryStmt], appModuleFactoryVar, deps); + } +} + +class _InjectorBuilder { + private _instances = new CompileTokenMap(); + private _fields: o.ClassField[] = []; + private _createStmts: o.Statement[] = []; + private _getters: o.ClassGetter[] = []; + + constructor( + private _appModuleMeta: CompileAppModuleMetadata, + private _precompileComponents: CompileIdentifierMetadata[], + private _sourceSpan: ParseSourceSpan) {} + + addProvider(resolvedProvider: ProviderAst) { + var providerValueExpressions = + resolvedProvider.providers.map((provider) => this._getProviderValue(provider)); + var propName = `_${resolvedProvider.token.name}_${this._instances.size}`; + var instance = this._createProviderProperty( + propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider, + resolvedProvider.eager); + this._instances.add(resolvedProvider.token, instance); + } + + build(): o.ClassStmt { + let getMethodStmts: o.Statement[] = this._instances.keys().map((token) => { + var providerExpr = this._instances.get(token); + return new o.IfStmt( + InjectMethodVars.token.identical(createDiTokenExpression(token)), + [new o.ReturnStatement(providerExpr)]); + }); + var methods = [ + new o.ClassMethod( + 'createInternal', [], this._createStmts.concat( + new o.ReturnStatement(this._instances.get(identifierToken(this._appModuleMeta.type))) + ), o.importType(this._appModuleMeta.type) + ), + new o.ClassMethod( + 'getInternal', + [ + new o.FnParam(InjectMethodVars.token.name, o.DYNAMIC_TYPE), + new o.FnParam(InjectMethodVars.notFoundResult.name, o.DYNAMIC_TYPE) + ], + getMethodStmts.concat([new o.ReturnStatement(InjectMethodVars.notFoundResult)]), + o.DYNAMIC_TYPE) + ]; + + var ctor = new o.ClassMethod( + null, [new o.FnParam(InjectorProps.parent.name, o.importType(Identifiers.Injector))], + [o.SUPER_EXPR + .callFn([ + o.variable(InjectorProps.parent.name), + o.literalArr(this._precompileComponents.map( + (precompiledComponent) => o.importExpr(precompiledComponent))) + ]) + .toStmt()]); + + var injClassName = `${this._appModuleMeta.type.name}Injector`; + return new o.ClassStmt( + injClassName, + o.importExpr(Identifiers.AppModuleInjector, [o.importType(this._appModuleMeta.type)]), + this._fields, this._getters, ctor, methods); + } + + private _getProviderValue(provider: CompileProviderMetadata): o.Expression { + var result: o.Expression; + if (isPresent(provider.useExisting)) { + result = this._getDependency(new CompileDiDependencyMetadata({token: provider.useExisting})); + } else if (isPresent(provider.useFactory)) { + var deps = isPresent(provider.deps) ? provider.deps : provider.useFactory.diDeps; + var depsExpr = deps.map((dep) => this._getDependency(dep)); + result = o.importExpr(provider.useFactory).callFn(depsExpr); + } else if (isPresent(provider.useClass)) { + var deps = isPresent(provider.deps) ? provider.deps : provider.useClass.diDeps; + var depsExpr = deps.map((dep) => this._getDependency(dep)); + result = + o.importExpr(provider.useClass).instantiate(depsExpr, o.importType(provider.useClass)); + } else { + result = o.literal(provider.useValue); + } + return result; + } + + + private _createProviderProperty( + propName: string, provider: ProviderAst, providerValueExpressions: o.Expression[], + isMulti: boolean, isEager: boolean): o.Expression { + var resolvedProviderValueExpr: o.Expression; + var type: o.Type; + if (isMulti) { + resolvedProviderValueExpr = o.literalArr(providerValueExpressions); + type = new o.ArrayType(o.DYNAMIC_TYPE); + } else { + resolvedProviderValueExpr = providerValueExpressions[0]; + type = providerValueExpressions[0].type; + } + if (isBlank(type)) { + type = o.DYNAMIC_TYPE; + } + if (isEager) { + this._fields.push(new o.ClassField(propName, type)); + this._createStmts.push(o.THIS_EXPR.prop(propName).set(resolvedProviderValueExpr).toStmt()); + } else { + var internalField = `_${propName}`; + this._fields.push(new o.ClassField(internalField, type)); + // Note: Equals is important for JS so that it also checks the undefined case! + var getterStmts = [ + new o.IfStmt( + o.THIS_EXPR.prop(internalField).isBlank(), + [o.THIS_EXPR.prop(internalField).set(resolvedProviderValueExpr).toStmt()]), + new o.ReturnStatement(o.THIS_EXPR.prop(internalField)) + ]; + this._getters.push(new o.ClassGetter(propName, getterStmts, type)); + } + return o.THIS_EXPR.prop(propName); + } + + private _getDependency(dep: CompileDiDependencyMetadata): o.Expression { + var result: o.Expression = null; + if (dep.isValue) { + result = o.literal(dep.value); + } + if (!dep.isSkipSelf) { + if (dep.token && + (dep.token.equalsTo(identifierToken(Identifiers.Injector)) || + dep.token.equalsTo(identifierToken(Identifiers.ComponentFactoryResolver)))) { + result = o.THIS_EXPR; + } + if (isBlank(result)) { + result = this._instances.get(dep.token); + } + } + if (isBlank(result)) { + var args = [createDiTokenExpression(dep.token)]; + if (dep.isOptional) { + args.push(o.NULL_EXPR); + } + result = InjectorProps.parent.callMethod('get', args); + } + return result; + } +} + +class InjectorProps { + static parent = o.THIS_EXPR.prop('parent'); +} + +class InjectMethodVars { + static token = o.variable('token'); + static notFoundResult = o.variable('notFoundResult'); +} diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index 9e0023e968..2c12439084 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -11,13 +11,14 @@ import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; import {CHANGE_DETECTION_STRATEGY_VALUES, LIFECYCLE_HOOKS_VALUES, LifecycleHooks, VIEW_ENCAPSULATION_VALUES, reflector} from '../core_private'; import {ListWrapper, StringMapWrapper} from '../src/facade/collection'; import {BaseException, unimplemented} from '../src/facade/exceptions'; -import {NumberWrapper, RegExpWrapper, Type, isArray, isBlank, isBoolean, isNumber, isPresent, isString, normalizeBlank, normalizeBool, serializeEnum} from '../src/facade/lang'; +import {NumberWrapper, RegExpWrapper, Type, isArray, isBlank, isBoolean, isNumber, isPresent, isString, isStringMap, normalizeBlank, normalizeBool, serializeEnum} from '../src/facade/lang'; import {CssSelector} from './selector'; import {getUrlScheme} from './url_resolver'; import {sanitizeIdentifier, splitAtColon} from './util'; + // group 2: "event" from "(event)" var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g; @@ -468,12 +469,14 @@ export class CompileTokenMetadata implements CompileMetadataWithIdentifier { export class CompileTokenMap { private _valueMap = new Map(); private _values: VALUE[] = []; + private _tokens: CompileTokenMetadata[] = []; add(token: CompileTokenMetadata, value: VALUE) { var existing = this.get(token); if (isPresent(existing)) { throw new BaseException(`Can only add to a TokenMap! Token: ${token.name}`); } + this._tokens.push(token); this._values.push(value); var rk = token.runtimeCacheKey; if (isPresent(rk)) { @@ -496,6 +499,7 @@ export class CompileTokenMap { } return result; } + keys(): CompileTokenMetadata[] { return this._tokens; } values(): VALUE[] { return this._values; } get size(): number { return this._values.length; } } @@ -966,7 +970,61 @@ export class CompilePipeMetadata implements CompileMetadataWithType { } } +/** + * Metadata regarding compilation of a directive. + */ +export class CompileAppModuleMetadata implements CompileMetadataWithType { + type: CompileTypeMetadata; + providers: CompileProviderMetadata[]; + directives: CompileTypeMetadata[]; + pipes: CompileTypeMetadata[]; + precompile: CompileTypeMetadata[]; + modules: CompileTypeMetadata[]; + + constructor({type, providers, directives, pipes, precompile, modules}: { + type?: CompileTypeMetadata, + providers?: Array, + directives?: CompileTypeMetadata[], + pipes?: CompileTypeMetadata[], + precompile?: CompileTypeMetadata[], + modules?: CompileTypeMetadata[] + } = {}) { + this.type = type; + this.directives = _normalizeArray(directives); + this.pipes = _normalizeArray(pipes); + this.providers = _normalizeArray(providers); + this.precompile = _normalizeArray(precompile); + this.modules = _normalizeArray(modules); + } + + get identifier(): CompileIdentifierMetadata { return this.type; } + + static fromJson(data: {[key: string]: any}): CompileAppModuleMetadata { + return new CompileAppModuleMetadata({ + type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'], + providers: _arrayFromJson(data['providers'], metadataFromJson), + directives: _arrayFromJson(data['directives'], metadataFromJson), + pipes: _arrayFromJson(data['pipes'], metadataFromJson), + precompile: _arrayFromJson(data['precompile'], CompileTypeMetadata.fromJson), + modules: _arrayFromJson(data['modules'], CompileTypeMetadata.fromJson) + }); + } + + toJson(): {[key: string]: any} { + return { + 'class': 'AppModule', + 'type': isPresent(this.type) ? this.type.toJson() : this.type, + 'providers': _arrayToJson(this.providers), + 'directives': _arrayToJson(this.directives), + 'pipes': _arrayToJson(this.pipes), + 'precompile': _arrayToJson(this.precompile), + 'modules': _arrayToJson(this.modules) + }; + } +} + var _COMPILE_METADATA_FROM_JSON = { + 'AppModule': CompileAppModuleMetadata.fromJson, 'Directive': CompileDirectiveMetadata.fromJson, 'Pipe': CompilePipeMetadata.fromJson, 'Type': CompileTypeMetadata.fromJson, @@ -1006,3 +1064,12 @@ function _objToJson(obj: any): string|{[key: string]: any} { function _normalizeArray(obj: any[]): any[] { return isPresent(obj) ? obj : []; } + +export function isStaticSymbol(value: any): value is StaticSymbol { + return isStringMap(value) && isPresent(value['name']) && isPresent(value['filePath']); +} + +export interface StaticSymbol { + name: string; + filePath: string; +} diff --git a/modules/@angular/compiler/src/compiler.ts b/modules/@angular/compiler/src/compiler.ts index ee0867b33f..c6e2b61dba 100644 --- a/modules/@angular/compiler/src/compiler.ts +++ b/modules/@angular/compiler/src/compiler.ts @@ -27,6 +27,7 @@ import {DirectiveNormalizer} from './directive_normalizer'; import {CompileMetadataResolver} from './metadata_resolver'; import {StyleCompiler} from './style_compiler'; import {ViewCompiler} from './view_compiler/view_compiler'; +import {AppModuleCompiler} from './app_module_compiler'; import {CompilerConfig} from './config'; import {RuntimeCompiler} from './runtime_compiler'; import {ElementSchemaRegistry} from './schema/element_schema_registry'; @@ -44,13 +45,24 @@ import {PipeResolver} from './pipe_resolver'; */ export const COMPILER_PROVIDERS: Array = /*@ts2dart_const*/[ - Lexer, Parser, HtmlParser, TemplateParser, DirectiveNormalizer, CompileMetadataResolver, - DEFAULT_PACKAGE_URL_PROVIDER, StyleCompiler, ViewCompiler, + Lexer, + Parser, + HtmlParser, + TemplateParser, + DirectiveNormalizer, + CompileMetadataResolver, + DEFAULT_PACKAGE_URL_PROVIDER, + StyleCompiler, + ViewCompiler, + AppModuleCompiler, /*@ts2dart_Provider*/ {provide: CompilerConfig, useValue: new CompilerConfig()}, RuntimeCompiler, /*@ts2dart_Provider*/ {provide: ComponentResolver, useExisting: RuntimeCompiler}, /*@ts2dart_Provider*/ {provide: Compiler, useExisting: RuntimeCompiler}, DomElementSchemaRegistry, /*@ts2dart_Provider*/ {provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry}, - UrlResolver, ViewResolver, DirectiveResolver, PipeResolver + UrlResolver, + ViewResolver, + DirectiveResolver, + PipeResolver ]; diff --git a/modules/@angular/compiler/src/directive_normalizer.ts b/modules/@angular/compiler/src/directive_normalizer.ts index e08f09c0e3..1a1f1c7654 100644 --- a/modules/@angular/compiler/src/directive_normalizer.ts +++ b/modules/@angular/compiler/src/directive_normalizer.ts @@ -20,14 +20,9 @@ import {HtmlParser} from './html_parser'; import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver'; import {PreparsedElementType, preparseElement} from './template_preparser'; import {UrlResolver} from './url_resolver'; +import {SyncAsyncResult} from './util'; import {XHR} from './xhr'; -export class NormalizeDirectiveResult { - constructor( - public syncResult: CompileDirectiveMetadata, - public asyncResult: Promise) {} -} - @Injectable() export class DirectiveNormalizer { private _xhrCache = new Map>(); @@ -56,10 +51,11 @@ export class DirectiveNormalizer { return result; } - normalizeDirective(directive: CompileDirectiveMetadata): NormalizeDirectiveResult { + normalizeDirective(directive: CompileDirectiveMetadata): + SyncAsyncResult { if (!directive.isComponent) { // For non components there is nothing to be normalized yet. - return new NormalizeDirectiveResult(directive, Promise.resolve(directive)); + return new SyncAsyncResult(directive, Promise.resolve(directive)); } let normalizedTemplateSync: CompileTemplateMetadata = null; let normalizedTemplateAsync: Promise; @@ -74,11 +70,10 @@ export class DirectiveNormalizer { if (normalizedTemplateSync && normalizedTemplateSync.styleUrls.length === 0) { // sync case let normalizedDirective = _cloneDirectiveWithTemplate(directive, normalizedTemplateSync); - return new NormalizeDirectiveResult( - normalizedDirective, Promise.resolve(normalizedDirective)); + return new SyncAsyncResult(normalizedDirective, Promise.resolve(normalizedDirective)); } else { // async case - return new NormalizeDirectiveResult( + return new SyncAsyncResult( null, normalizedTemplateAsync .then((normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate)) diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index 7138e63d62..f83a893813 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactoryResolver, ElementRef, Injector, QueryList, RenderComponentType, Renderer, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; +import {AppModuleFactory, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, QueryList, RenderComponentType, Renderer, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; -import {AnimationGroupPlayer as AnimationGroupPlayer_, AnimationKeyframe as AnimationKeyframe_, AnimationSequencePlayer as AnimationSequencePlayer_, AnimationStyles as AnimationStyles_, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NoOpAnimationPlayer as NoOpAnimationPlayer_, SecurityContext, StaticNodeDebugInfo, TemplateRef_, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes as impBalanceAnimationKeyframes, castByValue, checkBinding, clearStyles as impClearStyles, collectAndResolveStyles as impCollectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles as impBalanceAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, renderStyles as impRenderStyles, uninitialized} from '../core_private'; +import {AnimationGroupPlayer as AnimationGroupPlayer_, AnimationKeyframe as AnimationKeyframe_, AnimationSequencePlayer as AnimationSequencePlayer_, AnimationStyles as AnimationStyles_, AppElement, AppModuleInjector, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NoOpAnimationPlayer as NoOpAnimationPlayer_, SecurityContext, StaticNodeDebugInfo, TemplateRef_, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes as impBalanceAnimationKeyframes, castByValue, checkBinding, clearStyles as impClearStyles, collectAndResolveStyles as impCollectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles as impBalanceAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, renderStyles as impRenderStyles, uninitialized} from '../core_private'; import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; import {assetUrl} from './util'; @@ -108,6 +108,21 @@ export class Identifiers { moduleUrl: assetUrl('core', 'linker/component_factory_resolver'), runtime: ComponentFactoryResolver }); + static ComponentFactory = new CompileIdentifierMetadata({ + name: 'ComponentFactory', + runtime: ComponentFactory, + moduleUrl: assetUrl('core', 'linker/component_factory') + }); + static AppModuleFactory = new CompileIdentifierMetadata({ + name: 'AppModuleFactory', + runtime: AppModuleFactory, + moduleUrl: assetUrl('core', 'linker/app_module_factory') + }); + static AppModuleInjector = new CompileIdentifierMetadata({ + name: 'AppModuleInjector', + runtime: AppModuleInjector, + moduleUrl: assetUrl('core', 'linker/app_module_factory') + }); static ValueUnwrapper = new CompileIdentifierMetadata( {name: 'ValueUnwrapper', moduleUrl: CD_MODULE_URL, runtime: impValueUnwrapper}); static Injector = new CompileIdentifierMetadata( diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index 08c6d4e480..4d13f9abed 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, AttributeMetadata, ComponentMetadata, HostMetadata, Inject, InjectMetadata, Injectable, Optional, OptionalMetadata, Provider, QueryMetadata, SelfMetadata, SkipSelfMetadata, ViewMetadata, ViewQueryMetadata, resolveForwardRef} from '@angular/core'; +import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, AppModuleMetadata, AttributeMetadata, ComponentMetadata, HostMetadata, Inject, InjectMetadata, Injectable, Optional, OptionalMetadata, Provider, QueryMetadata, SelfMetadata, SkipSelfMetadata, ViewMetadata, ViewQueryMetadata, resolveForwardRef} from '@angular/core'; import {LIFECYCLE_HOOKS_VALUES, ReflectorReader, createProvider, isProviderLiteral, reflector} from '../core_private'; import {StringMapWrapper} from '../src/facade/collection'; @@ -27,6 +27,7 @@ import {ViewResolver} from './view_resolver'; export class CompileMetadataResolver { private _directiveCache = new Map(); private _pipeCache = new Map(); + private _appModuleCache = new Map(); private _anonymousTypes = new Map(); private _anonymousTypeIndex = 0; @@ -49,14 +50,16 @@ export class CompileMetadataResolver { return sanitizeIdentifier(identifier); } - clearCacheFor(compType: Type) { - this._directiveCache.delete(compType); - this._pipeCache.delete(compType); + clearCacheFor(type: Type) { + this._directiveCache.delete(type); + this._pipeCache.delete(type); + this._appModuleCache.delete(type); } clearCache() { this._directiveCache.clear(); this._pipeCache.clear(); + this._appModuleCache.clear(); } getAnimationEntryMetadata(entry: AnimationEntryMetadata): cpl.CompileAnimationEntryMetadata { @@ -102,6 +105,7 @@ export class CompileMetadataResolver { } getDirectiveMetadata(directiveType: Type): cpl.CompileDirectiveMetadata { + directiveType = resolveForwardRef(directiveType); var meta = this._directiveCache.get(directiveType); if (isBlank(meta)) { var dirMeta = this._directiveResolver.resolve(directiveType); @@ -176,6 +180,72 @@ export class CompileMetadataResolver { return meta; } + getAppModuleMetadata(moduleType: any, meta: AppModuleMetadata = null): + cpl.CompileAppModuleMetadata { + // Only cache if we read the metadata via the reflector, + // as we use the moduleType as cache key. + let useCache = !meta; + moduleType = resolveForwardRef(moduleType); + var compileMeta = this._appModuleCache.get(moduleType); + if (isBlank(compileMeta) || !useCache) { + if (!meta) { + meta = this._reflector.annotations(moduleType) + .find((meta) => meta instanceof AppModuleMetadata); + } + if (!meta) { + throw new BaseException( + `Could not compile '${stringify(moduleType)}' because it is not an AppModule.`); + } + let providers: any[] = []; + if (meta.providers) { + providers.push(...this.getProvidersMetadata(meta.providers)); + } + + let directives: cpl.CompileTypeMetadata[] = []; + if (meta.directives) { + directives.push(...flattenArray(meta.directives) + .map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type)))); + } + + let pipes: cpl.CompileTypeMetadata[] = []; + if (meta.pipes) { + pipes.push(...flattenArray(meta.pipes) + .map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type)))); + } + + let precompile: cpl.CompileTypeMetadata[] = []; + if (meta.precompile) { + precompile.push(...flattenArray(meta.precompile) + .map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type)))); + } + let modules: cpl.CompileTypeMetadata[] = []; + if (meta.modules) { + flattenArray(meta.modules).forEach((moduleType) => { + var meta = this.getAppModuleMetadata(moduleType); + providers.push(...meta.providers); + directives.push(...meta.directives); + pipes.push(...meta.pipes); + precompile.push(...meta.precompile); + modules.push(meta.type); + modules.push(...meta.modules); + }); + } + + compileMeta = new cpl.CompileAppModuleMetadata({ + type: this.getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)), + providers: providers, + directives: directives, + pipes: pipes, + precompile: precompile, + modules: modules + }); + if (useCache) { + this._appModuleCache.set(moduleType, compileMeta); + } + } + return compileMeta; + } + /** * @param someType a symbol which may or may not be a directive type * @returns {cpl.CompileDirectiveMetadata} if possible, otherwise null. @@ -193,6 +263,7 @@ export class CompileMetadataResolver { getTypeMetadata(type: Type, moduleUrl: string, dependencies: any[] = null): cpl.CompileTypeMetadata { + type = resolveForwardRef(type); return new cpl.CompileTypeMetadata({ name: this.sanitizeTokenName(type), moduleUrl: moduleUrl, @@ -203,6 +274,7 @@ export class CompileMetadataResolver { getFactoryMetadata(factory: Function, moduleUrl: string, dependencies: any[] = null): cpl.CompileFactoryMetadata { + factory = resolveForwardRef(factory); return new cpl.CompileFactoryMetadata({ name: this.sanitizeTokenName(factory), moduleUrl: moduleUrl, @@ -212,6 +284,7 @@ export class CompileMetadataResolver { } getPipeMetadata(pipeType: Type): cpl.CompilePipeMetadata { + pipeType = resolveForwardRef(pipeType); var meta = this._pipeCache.get(pipeType); if (isBlank(meta)) { var pipeMeta = this._pipeResolver.resolve(pipeType); @@ -349,8 +422,11 @@ export class CompileMetadataResolver { return this.getProviderMetadata(provider); } else if (isProviderLiteral(provider)) { return this.getProviderMetadata(createProvider(provider)); - } else { + } else if (isValidType(provider)) { return this.getTypeMetadata(provider, staticTypeModuleUrl(provider)); + } else { + throw new BaseException( + `Invalid provider - only instances of Provider and Type are allowed, got: ${stringify(provider)}`); } }); } @@ -468,21 +544,17 @@ function verifyNonBlankProviders( return providersTree; } -function isStaticType(value: any): boolean { - return isStringMap(value) && isPresent(value['name']) && isPresent(value['filePath']); -} - function isValidType(value: any): boolean { - return isStaticType(value) || (value instanceof Type); + return cpl.isStaticSymbol(value) || (value instanceof Type); } function staticTypeModuleUrl(value: any): string { - return isStaticType(value) ? value['filePath'] : null; + return cpl.isStaticSymbol(value) ? value.filePath : null; } function componentModuleUrl( reflector: ReflectorReader, type: any, cmpMetadata: ComponentMetadata): string { - if (isStaticType(type)) { + if (cpl.isStaticSymbol(type)) { return staticTypeModuleUrl(type); } @@ -503,9 +575,8 @@ function convertToCompileValue(value: any): any { class _CompileValueConverter extends ValueTransformer { visitOther(value: any, context: any): any { - if (isStaticType(value)) { - return new cpl.CompileIdentifierMetadata( - {name: value['name'], moduleUrl: staticTypeModuleUrl(value)}); + if (cpl.isStaticSymbol(value)) { + return new cpl.CompileIdentifierMetadata({name: value.name, moduleUrl: value.filePath}); } else { return new cpl.CompileIdentifierMetadata({runtime: value}); } diff --git a/modules/@angular/compiler/src/offline_compiler.ts b/modules/@angular/compiler/src/offline_compiler.ts index 43b82328cf..c09d7a6481 100644 --- a/modules/@angular/compiler/src/offline_compiler.ts +++ b/modules/@angular/compiler/src/offline_compiler.ts @@ -6,12 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ -import {ComponentFactory} from '@angular/core'; +import {AppModuleMetadata, ComponentMetadata} from '@angular/core'; -import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata, createHostComponentMeta} from './compile_metadata'; +import {AppModuleCompiler} from './app_module_compiler'; +import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata'; import {DirectiveNormalizer} from './directive_normalizer'; import {ListWrapper} from './facade/collection'; import {BaseException} from './facade/exceptions'; +import {Identifiers} from './identifiers'; +import {CompileMetadataResolver} from './metadata_resolver'; import {OutputEmitter} from './output/abstract_emitter'; import * as o from './output/output_ast'; import {CompiledStylesheet, StyleCompiler} from './style_compiler'; @@ -20,79 +23,164 @@ import {assetUrl} from './util'; import {ComponentFactoryDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; import {XHR} from './xhr'; -var _COMPONENT_FACTORY_IDENTIFIER = new CompileIdentifierMetadata({ - name: 'ComponentFactory', - runtime: ComponentFactory, - moduleUrl: assetUrl('core', 'linker/component_factory') -}); - export class SourceModule { constructor(public moduleUrl: string, public source: string) {} } -export class StyleSheetSourceWithImports { - constructor(public source: SourceModule, public importedUrls: string[]) {} -} +export class AppModulesSummary { + private _compAppModule = new Map(); + private _hashKey(type: StaticSymbol) { return `${type.filePath}#${type.name}`; } -export class NormalizedComponentWithViewDirectives { - constructor( - public component: CompileDirectiveMetadata, public directives: CompileDirectiveMetadata[], - public pipes: CompilePipeMetadata[]) {} -} - -export class OfflineCompiler { - constructor( - private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, - private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, - private _outputEmitter: OutputEmitter) {} - - normalizeDirectiveMetadata(directive: CompileDirectiveMetadata): - Promise { - return this._directiveNormalizer.normalizeDirective(directive).asyncResult; + hasComponent(component: StaticSymbol): boolean { + return this._compAppModule.has(this._hashKey(component)); } - compileTemplates(components: NormalizedComponentWithViewDirectives[]): SourceModule[] { - if (components.length === 0) { - throw new BaseException('No components given'); - } - var statements: o.DeclareVarStmt[] = []; - var exportedVars: string[] = []; - var moduleUrl = _ngfactoryModuleUrl(components[0].component.type); - var outputSourceModules: SourceModule[] = []; - components.forEach(componentWithDirs => { - var compMeta = componentWithDirs.component; - _assertComponent(compMeta); - var fileSuffix = _splitLastSuffix(compMeta.type.moduleUrl)[1]; - var stylesCompileResults = this._styleCompiler.compileComponent(compMeta); - stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => { - outputSourceModules.push(this._codgenStyles(compiledStyleSheet, fileSuffix)); - }); + addComponent(module: StaticSymbol, component: StaticSymbol) { + this._compAppModule.set(this._hashKey(component), module); + } - var compViewFactoryVar = this._compileComponent( - compMeta, componentWithDirs.directives, componentWithDirs.pipes, - stylesCompileResults.componentStylesheet, fileSuffix, statements); - exportedVars.push(compViewFactoryVar); + getModule(comp: StaticSymbol): StaticSymbol { + return this._compAppModule.get(this._hashKey(comp)); + } +} +export class OfflineCompiler { + constructor( + private _metadataResolver: CompileMetadataResolver, + private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, + private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, + private _appModuleCompiler: AppModuleCompiler, private _outputEmitter: OutputEmitter) {} - var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); - var hostViewFactoryVar = - this._compileComponent(hostMeta, [compMeta], [], null, fileSuffix, statements); - var compFactoryVar = _componentFactoryName(compMeta.type); - statements.push( - o.variable(compFactoryVar) - .set(o.importExpr(_COMPONENT_FACTORY_IDENTIFIER, [o.importType(compMeta.type)]) - .instantiate( - [ - o.literal(compMeta.selector), o.variable(hostViewFactoryVar), - o.importExpr(compMeta.type) - ], - o.importType( - _COMPONENT_FACTORY_IDENTIFIER, [o.importType(compMeta.type)], - [o.TypeModifier.Const]))) - .toDeclStmt(null, [o.StmtModifier.Final])); - exportedVars.push(compFactoryVar); + analyzeModules(appModules: StaticSymbol[]): AppModulesSummary { + let result = new AppModulesSummary(); + appModules.forEach((appModule) => { + let appModuleMeta = this._metadataResolver.getAppModuleMetadata(appModule); + appModuleMeta.precompile.forEach( + (precompileComp) => + this._getTransitiveComponents(appModule, precompileComp.runtime, result)); }); - outputSourceModules.unshift(this._codegenSourceModule(moduleUrl, statements, exportedVars)); - return outputSourceModules; + return result; + } + + private _getTransitiveComponents( + appModule: StaticSymbol, component: StaticSymbol, + target: AppModulesSummary = new AppModulesSummary()): AppModulesSummary { + var compMeta = this._metadataResolver.getDirectiveMetadata(component); + // TODO(tbosch): preserve all modules per component, not just one. + // Then run the template parser with the union and the intersection of the modules (regarding + // directives/pipes) + // and report an error if some directives/pipes are only matched with the union but not with the + // intersection! + // -> this means that a component is used in the wrong way! + if (!compMeta.isComponent || target.hasComponent(component)) { + return target; + } + target.addComponent(appModule, component); + this._metadataResolver.getViewDirectivesMetadata(component).forEach((dirMeta) => { + this._getTransitiveComponents(appModule, dirMeta.type.runtime); + }); + compMeta.precompile.forEach((precompileComp) => { + this._getTransitiveComponents(appModule, precompileComp.type.runtime); + }); + return target; + } + + clearCache() { + this._directiveNormalizer.clearCache(); + this._metadataResolver.clearCache(); + } + + compile( + moduleUrl: string, appModulesSummary: AppModulesSummary, components: StaticSymbol[], + appModules: StaticSymbol[]): Promise { + let fileSuffix = _splitLastSuffix(moduleUrl)[1]; + let statements: o.Statement[] = []; + let exportedVars: string[] = []; + let outputSourceModules: SourceModule[] = []; + + // compile app modules + exportedVars.push( + ...appModules.map((appModule) => this._compileAppModule(appModule, statements))); + + // compile components + return Promise + .all(components.map((compType) => { + let appModule = appModulesSummary.getModule(compType); + let appModuleDirectives: CompileDirectiveMetadata[] = []; + let appModulePipes: CompilePipeMetadata[] = []; + if (appModule) { + let appModuleMeta = this._metadataResolver.getAppModuleMetadata(appModule); + appModuleDirectives.push(...appModuleMeta.directives.map( + type => this._metadataResolver.getDirectiveMetadata(type.runtime))); + appModulePipes.push(...appModuleMeta.pipes.map( + type => this._metadataResolver.getPipeMetadata(type.runtime))); + } + return Promise + .all([ + this._metadataResolver.getDirectiveMetadata(compType), ...appModuleDirectives, + ...this._metadataResolver.getViewDirectivesMetadata(compType) + ].map(dirMeta => this._directiveNormalizer.normalizeDirective(dirMeta).asyncResult)) + .then((normalizedCompWithDirectives) => { + let compMeta = normalizedCompWithDirectives[0]; + let dirMetas = normalizedCompWithDirectives.slice(1); + _assertComponent(compMeta); + + // compile styles + let stylesCompileResults = this._styleCompiler.compileComponent(compMeta); + stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => { + outputSourceModules.push(this._codgenStyles(compiledStyleSheet, fileSuffix)); + }); + + // compile components + exportedVars.push(this._compileComponentFactory(compMeta, fileSuffix, statements)); + let pipeMetas = [ + ...appModulePipes, + ...this._metadataResolver.getViewPipesMetadata(compMeta.type.runtime) + ]; + exportedVars.push(this._compileComponent( + compMeta, dirMetas, pipeMetas, stylesCompileResults.componentStylesheet, + fileSuffix, statements)); + }); + })) + .then(() => { + if (statements.length > 0) { + outputSourceModules.unshift(this._codegenSourceModule( + _ngfactoryModuleUrl(moduleUrl), statements, exportedVars)); + } + return outputSourceModules; + }); + } + + private _compileAppModule(appModuleType: StaticSymbol, targetStatements: o.Statement[]): string { + let appModuleMeta = this._metadataResolver.getAppModuleMetadata(appModuleType); + let appCompileResult = this._appModuleCompiler.compile(appModuleMeta); + appCompileResult.dependencies.forEach((dep) => { + dep.placeholder.name = _componentFactoryName(dep.comp); + dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp.moduleUrl); + }); + targetStatements.push(...appCompileResult.statements); + return appCompileResult.appModuleFactoryVar; + } + + private _compileComponentFactory( + compMeta: CompileDirectiveMetadata, fileSuffix: string, + targetStatements: o.Statement[]): string { + var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); + var hostViewFactoryVar = + this._compileComponent(hostMeta, [compMeta], [], null, fileSuffix, targetStatements); + var compFactoryVar = _componentFactoryName(compMeta.type); + targetStatements.push( + o.variable(compFactoryVar) + .set(o.importExpr(Identifiers.ComponentFactory, [o.importType(compMeta.type)]) + .instantiate( + [ + o.literal(compMeta.selector), o.variable(hostViewFactoryVar), + o.importExpr(compMeta.type) + ], + o.importType( + Identifiers.ComponentFactory, [o.importType(compMeta.type)], + [o.TypeModifier.Const]))) + .toDeclStmt(null, [o.StmtModifier.Final])); + return compFactoryVar; } private _compileComponent( @@ -130,11 +218,11 @@ function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] compileResult.dependencies.forEach((dep) => { if (dep instanceof ViewFactoryDependency) { let vfd = dep; - vfd.placeholder.moduleUrl = _ngfactoryModuleUrl(vfd.comp); + vfd.placeholder.moduleUrl = _ngfactoryModuleUrl(vfd.comp.moduleUrl); } else if (dep instanceof ComponentFactoryDependency) { let cfd = dep; cfd.placeholder.name = _componentFactoryName(cfd.comp); - cfd.placeholder.moduleUrl = _ngfactoryModuleUrl(cfd.comp); + cfd.placeholder.moduleUrl = _ngfactoryModuleUrl(cfd.comp.moduleUrl); } }); return compileResult.statements; @@ -149,8 +237,8 @@ function _resolveStyleStatements( return compileResult.statements; } -function _ngfactoryModuleUrl(comp: CompileIdentifierMetadata): string { - var urlWithSuffix = _splitLastSuffix(comp.moduleUrl); +function _ngfactoryModuleUrl(compUrl: string): string { + var urlWithSuffix = _splitLastSuffix(compUrl); return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`; } diff --git a/modules/@angular/compiler/src/output/interpretive_view.ts b/modules/@angular/compiler/src/output/interpretive_view.ts deleted file mode 100644 index 6f1aebbc96..0000000000 --- a/modules/@angular/compiler/src/output/interpretive_view.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @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 {AppElement, AppView, DebugAppView} from '../../core_private'; -import {BaseException} from '../facade/exceptions'; -import {isPresent} from '../facade/lang'; - -import {DynamicInstance, InstanceFactory} from './output_interpreter'; - -export class InterpretiveAppViewInstanceFactory implements InstanceFactory { - createInstance( - superClass: any, clazz: any, args: any[], props: Map, - getters: Map, methods: Map): any { - if (superClass === AppView) { - // We are always using DebugAppView as parent. - // However, in prod mode we generate a constructor call that does - // not have the argument for the debugNodeInfos. - args = args.concat([null]); - return new _InterpretiveAppView(args, props, getters, methods); - } else if (superClass === DebugAppView) { - return new _InterpretiveAppView(args, props, getters, methods); - } - throw new BaseException(`Can't instantiate class ${superClass} in interpretative mode`); - } -} - -class _InterpretiveAppView extends DebugAppView implements DynamicInstance { - constructor( - args: any[], public props: Map, public getters: Map, - public methods: Map) { - super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); - } - createInternal(rootSelector: string|any): AppElement { - var m = this.methods.get('createInternal'); - if (isPresent(m)) { - return m(rootSelector); - } else { - return super.createInternal(rootSelector); - } - } - injectorGetInternal(token: any, nodeIndex: number, notFoundResult: any): any { - var m = this.methods.get('injectorGetInternal'); - if (isPresent(m)) { - return m(token, nodeIndex, notFoundResult); - } else { - return super.injectorGet(token, nodeIndex, notFoundResult); - } - } - detachInternal(): void { - var m = this.methods.get('detachInternal'); - if (isPresent(m)) { - return m(); - } else { - return super.detachInternal(); - } - } - destroyInternal(): void { - var m = this.methods.get('destroyInternal'); - if (isPresent(m)) { - return m(); - } else { - return super.destroyInternal(); - } - } - dirtyParentQueriesInternal(): void { - var m = this.methods.get('dirtyParentQueriesInternal'); - if (isPresent(m)) { - return m(); - } else { - return super.dirtyParentQueriesInternal(); - } - } - detectChangesInternal(throwOnChange: boolean): void { - var m = this.methods.get('detectChangesInternal'); - if (isPresent(m)) { - return m(throwOnChange); - } else { - return super.detectChangesInternal(throwOnChange); - } - } -} diff --git a/modules/@angular/compiler/src/output/output_ast.ts b/modules/@angular/compiler/src/output/output_ast.ts index 207fed6af9..7d51a8b911 100644 --- a/modules/@angular/compiler/src/output/output_ast.ts +++ b/modules/@angular/compiler/src/output/output_ast.ts @@ -7,7 +7,11 @@ */ import {CompileIdentifierMetadata} from '../compile_metadata'; +import {StringMapWrapper} from '../facade/collection'; +import {BaseException} from '../facade/exceptions'; import {isBlank, isPresent, isString} from '../facade/lang'; +import {ValueTransformer, visitValue} from '../util'; + //// Types @@ -875,10 +879,6 @@ export function importType( return isPresent(id) ? new ExternalType(id, typeParams, typeModifiers) : null; } -export function literal(value: any, type: Type = null): LiteralExpr { - return new LiteralExpr(value, type); -} - export function literalArr(values: Expression[], type: Type = null): LiteralArrayExpr { return new LiteralArrayExpr(values, type); } @@ -895,3 +895,30 @@ export function not(expr: Expression): NotExpr { export function fn(params: FnParam[], body: Statement[], type: Type = null): FunctionExpr { return new FunctionExpr(params, body, type); } + +export function literal(value: any, type: Type = null): Expression { + return visitValue(value, new _ValueOutputAstTransformer(), type); +} + +class _ValueOutputAstTransformer implements ValueTransformer { + visitArray(arr: any[], type: Type): Expression { + return literalArr(arr.map(value => visitValue(value, this, null)), type); + } + visitStringMap(map: {[key: string]: any}, type: MapType): Expression { + var entries: Array[] = []; + StringMapWrapper.forEach(map, (value: any, key: string) => { + entries.push([key, visitValue(value, this, null)]); + }); + return literalMap(entries, type); + } + visitPrimitive(value: any, type: Type): Expression { return new LiteralExpr(value, type); } + visitOther(value: any, type: Type): Expression { + if (value instanceof CompileIdentifierMetadata) { + return importExpr(value); + } else if (value instanceof Expression) { + return value; + } else { + throw new BaseException(`Illegal state: Don't now how to compile value ${value}`); + } + } +} diff --git a/modules/@angular/compiler/src/output/output_interpreter.ts b/modules/@angular/compiler/src/output/output_interpreter.ts index b503f71f4f..bd08b79668 100644 --- a/modules/@angular/compiler/src/output/output_interpreter.ts +++ b/modules/@angular/compiler/src/output/output_interpreter.ts @@ -6,49 +6,23 @@ * found in the LICENSE file at https://angular.io/license */ -import {reflector} from '../../core_private'; import {ObservableWrapper} from '../facade/async'; import {ListWrapper} from '../facade/collection'; import {BaseException, unimplemented} from '../facade/exceptions'; -import {FunctionWrapper, IS_DART, isPresent} from '../facade/lang'; +import {IS_DART, isPresent} from '../facade/lang'; import {debugOutputAstAsDart} from './dart_emitter'; import * as o from './output_ast'; import {debugOutputAstAsTypeScript} from './ts_emitter'; -export function interpretStatements( - statements: o.Statement[], resultVar: string, instanceFactory: InstanceFactory): any { +export function interpretStatements(statements: o.Statement[], resultVar: string): any { var stmtsWithReturn = statements.concat([new o.ReturnStatement(o.variable(resultVar))]); - var ctx = new _ExecutionContext( - null, null, null, null, new Map(), new Map(), - new Map(), new Map(), instanceFactory); + var ctx = new _ExecutionContext(null, null, null, new Map()); var visitor = new StatementInterpreter(); var result = visitor.visitAllStatements(stmtsWithReturn, ctx); return isPresent(result) ? result.value : null; } -export interface InstanceFactory { - createInstance( - superClass: any, clazz: any, constructorArgs: any[], props: Map, - getters: Map, methods: Map): DynamicInstance; -} - -export abstract class DynamicInstance { - get props(): Map { return unimplemented(); } - get getters(): Map { return unimplemented(); } - get methods(): Map { return unimplemented(); } - get clazz(): any { return unimplemented(); } -} - -function isDynamicInstance(instance: any): any { - if (IS_DART) { - return instance instanceof DynamicInstance; - } else { - return isPresent(instance) && isPresent(instance.props) && isPresent(instance.getters) && - isPresent(instance.methods); - } -} - function _executeFunctionStatements( varNames: string[], varValues: any[], statements: o.Statement[], ctx: _ExecutionContext, visitor: StatementInterpreter): any { @@ -62,15 +36,11 @@ function _executeFunctionStatements( class _ExecutionContext { constructor( - public parent: _ExecutionContext, public superClass: any, public superInstance: any, - public className: string, public vars: Map, public props: Map, - public getters: Map, public methods: Map, - public instanceFactory: InstanceFactory) {} + public parent: _ExecutionContext, public instance: any, public className: string, + public vars: Map) {} createChildWihtLocalVars(): _ExecutionContext { - return new _ExecutionContext( - this, this.superClass, this.superInstance, this.className, new Map(), - this.props, this.getters, this.methods, this.instanceFactory); + return new _ExecutionContext(this, this.instance, this.className, new Map()); } } @@ -78,38 +48,44 @@ class ReturnValue { constructor(public value: any) {} } -class _DynamicClass { - constructor( - private _classStmt: o.ClassStmt, private _ctx: _ExecutionContext, - private _visitor: StatementInterpreter) {} +function createDynamicClass( + _classStmt: o.ClassStmt, _ctx: _ExecutionContext, _visitor: StatementInterpreter): Function { + let propertyDescriptors: {[key: string]: any} = {}; - instantiate(args: any[]): DynamicInstance { - var props = new Map(); - var getters = new Map(); - var methods = new Map(); - var superClass = this._classStmt.parent.visitExpression(this._visitor, this._ctx); - var instanceCtx = new _ExecutionContext( - this._ctx, superClass, null, this._classStmt.name, this._ctx.vars, props, getters, methods, - this._ctx.instanceFactory); + _classStmt.getters.forEach((getter: o.ClassGetter) => { + // Note: use `function` instead of arrow function to capture `this` + propertyDescriptors[getter.name] = { + configurable: false, + get: function() { + let instanceCtx = new _ExecutionContext(_ctx, this, _classStmt.name, _ctx.vars); + return _executeFunctionStatements([], [], getter.body, instanceCtx, _visitor); + } + }; + }); + _classStmt.methods.forEach(function(method: o.ClassMethod) { + var paramNames = method.params.map(param => param.name); + // Note: use `function` instead of arrow function to capture `this` + propertyDescriptors[method.name] = { + writable: false, + configurable: false, + value: function(...args: any[]) { + let instanceCtx = new _ExecutionContext(_ctx, this, _classStmt.name, _ctx.vars); + return _executeFunctionStatements(paramNames, args, method.body, instanceCtx, _visitor); + } + }; + }); - this._classStmt.fields.forEach((field: o.ClassField) => { props.set(field.name, null); }); - this._classStmt.getters.forEach((getter: o.ClassGetter) => { - getters.set( - getter.name, - () => _executeFunctionStatements([], [], getter.body, instanceCtx, this._visitor)); - }); - this._classStmt.methods.forEach((method: o.ClassMethod) => { - var paramNames = method.params.map(param => param.name); - methods.set(method.name, _declareFn(paramNames, method.body, instanceCtx, this._visitor)); - }); - - var ctorParamNames = this._classStmt.constructorMethod.params.map(param => param.name); + var ctorParamNames = _classStmt.constructorMethod.params.map(param => param.name); + // Note: use `function` instead of arrow function to capture `this` + var ctor = function(...args: any[]) { + let instanceCtx = new _ExecutionContext(_ctx, this, _classStmt.name, _ctx.vars); + _classStmt.fields.forEach((field) => { this[field.name] = undefined; }); _executeFunctionStatements( - ctorParamNames, args, this._classStmt.constructorMethod.body, instanceCtx, this._visitor); - return instanceCtx.superInstance; - } - - debugAst(): string { return this._visitor.debugAst(this._classStmt); } + ctorParamNames, args, _classStmt.constructorMethod.body, instanceCtx, _visitor); + }; + var superClass = _classStmt.parent.visitExpression(_visitor, _ctx); + ctor.prototype = Object.create(superClass.prototype, propertyDescriptors); + return ctor; } class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { @@ -138,8 +114,9 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { if (isPresent(ast.builtin)) { switch (ast.builtin) { case o.BuiltinVar.Super: + return ctx.instance.__proto__; case o.BuiltinVar.This: - return ctx.superInstance; + return ctx.instance; case o.BuiltinVar.CatchError: varName = CATCH_ERROR_VAR; break; @@ -169,23 +146,14 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { visitWritePropExpr(expr: o.WritePropExpr, ctx: _ExecutionContext): any { var receiver = expr.receiver.visitExpression(this, ctx); var value = expr.value.visitExpression(this, ctx); - if (isDynamicInstance(receiver)) { - var di = receiver; - if (di.props.has(expr.name)) { - di.props.set(expr.name, value); - } else { - reflector.setter(expr.name)(receiver, value); - } - } else { - reflector.setter(expr.name)(receiver, value); - } + receiver[expr.name] = value; return value; } visitInvokeMethodExpr(expr: o.InvokeMethodExpr, ctx: _ExecutionContext): any { var receiver = expr.receiver.visitExpression(this, ctx); var args = this.visitAllExpressions(expr.args, ctx); - var result: any /** TODO #9100 */; + var result: any; if (isPresent(expr.builtin)) { switch (expr.builtin) { case o.BuiltinMethod.ConcatArray: @@ -204,15 +172,8 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { default: throw new BaseException(`Unknown builtin method ${expr.builtin}`); } - } else if (isDynamicInstance(receiver)) { - var di = receiver; - if (di.methods.has(expr.name)) { - result = FunctionWrapper.apply(di.methods.get(expr.name), args); - } else { - result = reflector.method(expr.name)(receiver, args); - } } else { - result = reflector.method(expr.name)(receiver, args); + result = receiver[expr.name].apply(receiver, args); } return result; } @@ -220,20 +181,18 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { var args = this.visitAllExpressions(stmt.args, ctx); var fnExpr = stmt.fn; if (fnExpr instanceof o.ReadVarExpr && fnExpr.builtin === o.BuiltinVar.Super) { - ctx.superInstance = ctx.instanceFactory.createInstance( - ctx.superClass, ctx.className, args, ctx.props, ctx.getters, ctx.methods); - ctx.parent.superInstance = ctx.superInstance; + ctx.instance.constructor.prototype.constructor.apply(ctx.instance, args); return null; } else { var fn = stmt.fn.visitExpression(this, ctx); - return FunctionWrapper.apply(fn, args); + return fn.apply(null, args); } } visitReturnStmt(stmt: o.ReturnStatement, ctx: _ExecutionContext): any { return new ReturnValue(stmt.value.visitExpression(this, ctx)); } visitDeclareClassStmt(stmt: o.ClassStmt, ctx: _ExecutionContext): any { - var clazz = new _DynamicClass(stmt, ctx, this); + var clazz = createDynamicClass(stmt, ctx, this); ctx.vars.set(stmt.name, clazz); return null; } @@ -266,11 +225,7 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { visitInstantiateExpr(ast: o.InstantiateExpr, ctx: _ExecutionContext): any { var args = this.visitAllExpressions(ast.args, ctx); var clazz = ast.classExpr.visitExpression(this, ctx); - if (clazz instanceof _DynamicClass) { - return clazz.instantiate(args); - } else { - return FunctionWrapper.apply(reflector.factory(clazz), args); - } + return new clazz(...args); } visitLiteralExpr(ast: o.LiteralExpr, ctx: _ExecutionContext): any { return ast.value; } visitExternalExpr(ast: o.ExternalExpr, ctx: _ExecutionContext): any { return ast.value.runtime; } @@ -337,22 +292,9 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { } } visitReadPropExpr(ast: o.ReadPropExpr, ctx: _ExecutionContext): any { - var result: any /** TODO #9100 */; + var result: any; var receiver = ast.receiver.visitExpression(this, ctx); - if (isDynamicInstance(receiver)) { - var di = receiver; - if (di.props.has(ast.name)) { - result = di.props.get(ast.name); - } else if (di.getters.has(ast.name)) { - result = di.getters.get(ast.name)(); - } else if (di.methods.has(ast.name)) { - result = di.methods.get(ast.name); - } else { - result = reflector.getter(ast.name)(receiver); - } - } else { - result = reflector.getter(ast.name)(receiver); - } + result = receiver[ast.name]; return result; } visitReadKeyExpr(ast: o.ReadKeyExpr, ctx: _ExecutionContext): any { @@ -366,7 +308,7 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: _ExecutionContext): any { var result = {}; ast.entries.forEach( - (entry) => (result as any /** TODO #9100 */)[entry[0]] = + (entry) => (result as any)[entry[0]] = (entry[1]).visitExpression(this, ctx)); return result; } @@ -390,61 +332,7 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { function _declareFn( varNames: string[], statements: o.Statement[], ctx: _ExecutionContext, visitor: StatementInterpreter): Function { - switch (varNames.length) { - case 0: - return () => _executeFunctionStatements(varNames, [], statements, ctx, visitor); - case 1: - return (d0: any /** TODO #9100 */) => - _executeFunctionStatements(varNames, [d0], statements, ctx, visitor); - case 2: - return (d0: any /** TODO #9100 */, d1: any /** TODO #9100 */) => - _executeFunctionStatements(varNames, [d0, d1], statements, ctx, visitor); - case 3: - return (d0: any /** TODO #9100 */, d1: any /** TODO #9100 */, d2: any /** TODO #9100 */) => - _executeFunctionStatements(varNames, [d0, d1, d2], statements, ctx, visitor); - case 4: - return (d0: any /** TODO #9100 */, d1: any /** TODO #9100 */, d2: any /** TODO #9100 */, - d3: any /** TODO #9100 */) => - _executeFunctionStatements(varNames, [d0, d1, d2, d3], statements, ctx, visitor); - case 5: - return (d0: any /** TODO #9100 */, d1: any /** TODO #9100 */, d2: any /** TODO #9100 */, - d3: any /** TODO #9100 */, d4: any /** TODO #9100 */) => - _executeFunctionStatements( - varNames, [d0, d1, d2, d3, d4], statements, ctx, visitor); - case 6: - return (d0: any /** TODO #9100 */, d1: any /** TODO #9100 */, d2: any /** TODO #9100 */, - d3: any /** TODO #9100 */, d4: any /** TODO #9100 */, d5: any /** TODO #9100 */) => - _executeFunctionStatements( - varNames, [d0, d1, d2, d3, d4, d5], statements, ctx, visitor); - case 7: - return (d0: any /** TODO #9100 */, d1: any /** TODO #9100 */, d2: any /** TODO #9100 */, - d3: any /** TODO #9100 */, d4: any /** TODO #9100 */, d5: any /** TODO #9100 */, - d6: any /** TODO #9100 */) => - _executeFunctionStatements( - varNames, [d0, d1, d2, d3, d4, d5, d6], statements, ctx, visitor); - case 8: - return (d0: any /** TODO #9100 */, d1: any /** TODO #9100 */, d2: any /** TODO #9100 */, - d3: any /** TODO #9100 */, d4: any /** TODO #9100 */, d5: any /** TODO #9100 */, - d6: any /** TODO #9100 */, d7: any /** TODO #9100 */) => - _executeFunctionStatements( - varNames, [d0, d1, d2, d3, d4, d5, d6, d7], statements, ctx, visitor); - case 9: - return (d0: any /** TODO #9100 */, d1: any /** TODO #9100 */, d2: any /** TODO #9100 */, - d3: any /** TODO #9100 */, d4: any /** TODO #9100 */, d5: any /** TODO #9100 */, - d6: any /** TODO #9100 */, d7: any /** TODO #9100 */, d8: any /** TODO #9100 */) => - _executeFunctionStatements( - varNames, [d0, d1, d2, d3, d4, d5, d6, d7, d8], statements, ctx, visitor); - case 10: - return (d0: any /** TODO #9100 */, d1: any /** TODO #9100 */, d2: any /** TODO #9100 */, - d3: any /** TODO #9100 */, d4: any /** TODO #9100 */, d5: any /** TODO #9100 */, - d6: any /** TODO #9100 */, d7: any /** TODO #9100 */, d8: any /** TODO #9100 */, - d9: any /** TODO #9100 */) => - _executeFunctionStatements( - varNames, [d0, d1, d2, d3, d4, d5, d6, d7, d8, d9], statements, ctx, visitor); - default: - throw new BaseException( - 'Declaring functions with more than 10 arguments is not supported right now'); - } + return (...args: any[]) => _executeFunctionStatements(varNames, args, statements, ctx, visitor); } var CATCH_ERROR_VAR = 'error'; diff --git a/modules/@angular/compiler/src/parse_util.ts b/modules/@angular/compiler/src/parse_util.ts index 8bfaeb378a..aea4387a21 100644 --- a/modules/@angular/compiler/src/parse_util.ts +++ b/modules/@angular/compiler/src/parse_util.ts @@ -5,13 +5,16 @@ * 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 {isPresent} from './facade/lang'; export class ParseLocation { constructor( public file: ParseSourceFile, public offset: number, public line: number, public col: number) {} - toString(): string { return `${this.file.url}@${this.line}:${this.col}`; } + toString(): string { + return isPresent(this.offset) ? `${this.file.url}@${this.line}:${this.col}` : this.file.url; + } } export class ParseSourceFile { @@ -39,38 +42,41 @@ export abstract class ParseError { toString(): string { var source = this.span.start.file.content; var ctxStart = this.span.start.offset; - if (ctxStart > source.length - 1) { - ctxStart = source.length - 1; - } - var ctxEnd = ctxStart; - var ctxLen = 0; - var ctxLines = 0; + var contextStr = ''; + if (isPresent(ctxStart)) { + if (ctxStart > source.length - 1) { + ctxStart = source.length - 1; + } + var ctxEnd = ctxStart; + var ctxLen = 0; + var ctxLines = 0; - while (ctxLen < 100 && ctxStart > 0) { - ctxStart--; - ctxLen++; - if (source[ctxStart] == '\n') { - if (++ctxLines == 3) { - break; + while (ctxLen < 100 && ctxStart > 0) { + ctxStart--; + ctxLen++; + if (source[ctxStart] == '\n') { + if (++ctxLines == 3) { + break; + } } } - } - ctxLen = 0; - ctxLines = 0; - while (ctxLen < 100 && ctxEnd < source.length - 1) { - ctxEnd++; - ctxLen++; - if (source[ctxEnd] == '\n') { - if (++ctxLines == 3) { - break; + ctxLen = 0; + ctxLines = 0; + while (ctxLen < 100 && ctxEnd < source.length - 1) { + ctxEnd++; + ctxLen++; + if (source[ctxEnd] == '\n') { + if (++ctxLines == 3) { + break; + } } } + + let context = source.substring(ctxStart, this.span.start.offset) + '[ERROR ->]' + + source.substring(this.span.start.offset, ctxEnd + 1); + contextStr = ` ("${context}")`; } - - let context = source.substring(ctxStart, this.span.start.offset) + '[ERROR ->]' + - source.substring(this.span.start.offset, ctxEnd + 1); - - return `${this.msg} ("${context}"): ${this.span.start}`; + return `${this.msg}${contextStr}: ${this.span.start}`; } } diff --git a/modules/@angular/compiler/src/provider_parser.ts b/modules/@angular/compiler/src/provider_parser.ts index 1ac718f80a..ceaaf1315d 100644 --- a/modules/@angular/compiler/src/provider_parser.ts +++ b/modules/@angular/compiler/src/provider_parser.ts @@ -7,9 +7,10 @@ */ import {ListWrapper} from '../src/facade/collection'; +import {BaseException} from '../src/facade/exceptions'; import {isArray, isBlank, isPresent, normalizeBlank} from '../src/facade/lang'; -import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMap, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata'; +import {CompileAppModuleMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMap, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata'; import {Identifiers, identifierToken} from './identifiers'; import {ParseError, ParseSourceSpan} from './parse_util'; import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst, VariableAst} from './template_ast'; @@ -270,6 +271,115 @@ export class ProviderElementContext { } } + +export class AppModuleProviderParser { + private _transformedProviders = new CompileTokenMap(); + private _seenProviders = new CompileTokenMap(); + private _unparsedProviders: any[] = []; + private _allProviders: CompileTokenMap; + private _errors: ProviderError[] = []; + + constructor(appModule: CompileAppModuleMetadata, sourceSpan: ParseSourceSpan) { + this._allProviders = new CompileTokenMap(); + [appModule.type].concat(appModule.modules).forEach((appModuleType: CompileTypeMetadata) => { + var appModuleProvider = new CompileProviderMetadata( + {token: new CompileTokenMetadata({identifier: appModuleType}), useClass: appModuleType}); + _resolveProviders( + [appModuleProvider], ProviderAstType.PublicService, true, sourceSpan, this._errors, + this._allProviders); + }); + _resolveProviders( + _normalizeProviders(appModule.providers, sourceSpan, this._errors), + ProviderAstType.PublicService, false, sourceSpan, this._errors, this._allProviders); + } + + parse(): ProviderAst[] { + this._allProviders.values().forEach( + (provider) => { this._getOrCreateLocalProvider(provider.token, provider.eager); }); + if (this._errors.length > 0) { + var errorString = this._errors.join('\n'); + throw new BaseException(`Provider parse errors:\n${errorString}`); + } + return this._transformedProviders.values(); + } + + private _getOrCreateLocalProvider(token: CompileTokenMetadata, eager: boolean): ProviderAst { + var resolvedProvider = this._allProviders.get(token); + if (isBlank(resolvedProvider)) { + return null; + } + var transformedProviderAst = this._transformedProviders.get(token); + if (isPresent(transformedProviderAst)) { + return transformedProviderAst; + } + if (isPresent(this._seenProviders.get(token))) { + this._errors.push(new ProviderError( + `Cannot instantiate cyclic dependency! ${token.name}`, resolvedProvider.sourceSpan)); + return null; + } + this._seenProviders.add(token, true); + var transformedProviders = resolvedProvider.providers.map((provider) => { + var transformedUseValue = provider.useValue; + var transformedUseExisting = provider.useExisting; + var transformedDeps: CompileDiDependencyMetadata[]; + if (isPresent(provider.useExisting)) { + var existingDiDep = this._getDependency( + new CompileDiDependencyMetadata({token: provider.useExisting}), eager, + resolvedProvider.sourceSpan); + if (isPresent(existingDiDep.token)) { + transformedUseExisting = existingDiDep.token; + } else { + transformedUseExisting = null; + transformedUseValue = existingDiDep.value; + } + } else if (isPresent(provider.useFactory)) { + var deps = isPresent(provider.deps) ? provider.deps : provider.useFactory.diDeps; + transformedDeps = + deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan)); + } else if (isPresent(provider.useClass)) { + var deps = isPresent(provider.deps) ? provider.deps : provider.useClass.diDeps; + transformedDeps = + deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan)); + } + return _transformProvider(provider, { + useExisting: transformedUseExisting, + useValue: transformedUseValue, + deps: transformedDeps + }); + }); + transformedProviderAst = + _transformProviderAst(resolvedProvider, {eager: eager, providers: transformedProviders}); + this._transformedProviders.add(token, transformedProviderAst); + return transformedProviderAst; + } + + private _getDependency( + dep: CompileDiDependencyMetadata, eager: boolean = null, + requestorSourceSpan: ParseSourceSpan): CompileDiDependencyMetadata { + var foundLocal = false; + if (!dep.isSkipSelf && isPresent(dep.token)) { + // access the injector + if (dep.token.equalsTo(identifierToken(Identifiers.Injector)) || + dep.token.equalsTo(identifierToken(Identifiers.ComponentFactoryResolver))) { + foundLocal = true; + // access providers + } else if (isPresent(this._getOrCreateLocalProvider(dep.token, eager))) { + foundLocal = true; + } + } + var result: CompileDiDependencyMetadata = dep; + if (dep.isSelf && !foundLocal) { + if (dep.isOptional) { + result = new CompileDiDependencyMetadata({isValue: true, value: null}); + } else { + this._errors.push( + new ProviderError(`No provider for ${dep.token.name}`, requestorSourceSpan)); + } + } + return result; + } +} + function _transformProvider( provider: CompileProviderMetadata, {useExisting, useValue, deps}: diff --git a/modules/@angular/compiler/src/runtime_compiler.ts b/modules/@angular/compiler/src/runtime_compiler.ts index 915dac0d3d..bf25ac5fe2 100644 --- a/modules/@angular/compiler/src/runtime_compiler.ts +++ b/modules/@angular/compiler/src/runtime_compiler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Compiler, ComponentFactory, ComponentResolver, Injectable} from '@angular/core'; +import {AppModuleFactory, AppModuleMetadata, Compiler, ComponentFactory, ComponentResolver, Injectable} from '@angular/core'; import {BaseException} from '../src/facade/exceptions'; import {ConcreteType, IS_DART, Type, isBlank, isString, stringify} from '../src/facade/lang'; @@ -17,14 +17,15 @@ import {createHostComponentMeta, CompileDirectiveMetadata, CompilePipeMetadata, import {TemplateAst,} from './template_ast'; import {StyleCompiler, StylesCompileDependency, CompiledStylesheet} from './style_compiler'; import {ViewCompiler, ViewCompileResult, ViewFactoryDependency, ComponentFactoryDependency} from './view_compiler/view_compiler'; +import {AppModuleCompiler} from './app_module_compiler'; import {TemplateParser} from './template_parser'; -import {DirectiveNormalizer, NormalizeDirectiveResult} from './directive_normalizer'; +import {DirectiveNormalizer} from './directive_normalizer'; import {CompileMetadataResolver} from './metadata_resolver'; import {CompilerConfig} from './config'; import * as ir from './output/output_ast'; import {jitStatements} from './output/output_jit'; import {interpretStatements} from './output/output_interpreter'; -import {InterpretiveAppViewInstanceFactory} from './output/interpretive_view'; +import {SyncAsyncResult} from './util'; /** * An internal module of the Angular compiler that begins with component types, @@ -37,14 +38,15 @@ import {InterpretiveAppViewInstanceFactory} from './output/interpretive_view'; */ @Injectable() export class RuntimeCompiler implements ComponentResolver, Compiler { - private _compiledTemplateCache = new Map(); - private _compiledHostTemplateCache = new Map(); + private _compiledTemplateCache = new Map(); + private _compiledHostTemplateCache = new Map(); + private _compiledAppModuleCache = new Map>(); constructor( private _metadataResolver: CompileMetadataResolver, private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, - private _genConfig: CompilerConfig) {} + private _appModuleCompiler: AppModuleCompiler, private _genConfig: CompilerConfig) {} resolveComponent(component: Type|string): Promise> { if (isString(component)) { @@ -54,39 +56,101 @@ export class RuntimeCompiler implements ComponentResolver, Compiler { return this.compileComponentAsync(>component); } - compileComponentAsync(compType: ConcreteType): Promise> { - var templates = this._getTransitiveCompiledTemplates(compType, true); + compileAppModuleSync(moduleType: ConcreteType, metadata: AppModuleMetadata = null): + AppModuleFactory { + return this._compileAppModule(moduleType, true, metadata).syncResult; + } + + compileAppModuleAsync(moduleType: ConcreteType, metadata: AppModuleMetadata = null): + Promise> { + return this._compileAppModule(moduleType, false, metadata).asyncResult; + } + + private _compileAppModule( + moduleType: ConcreteType, isSync: boolean, + metadata: AppModuleMetadata = null): SyncAsyncResult> { + // Only cache if we read the metadata via the reflector, + // as we use the moduleType as cache key. + let useCache = !metadata; + let appModuleFactory = this._compiledAppModuleCache.get(moduleType); + let componentCompilePromises: Promise[] = []; + if (!appModuleFactory || !useCache) { + var compileModuleMeta = this._metadataResolver.getAppModuleMetadata(moduleType, metadata); + var compileResult = this._appModuleCompiler.compile(compileModuleMeta); + compileResult.dependencies.forEach((dep) => { + let compileResult = this._compileComponent( + dep.comp.runtime, isSync, + compileModuleMeta.directives.map(compileType => compileType.runtime), + compileModuleMeta.pipes.map(compileType => compileType.runtime)); + dep.placeholder.runtime = compileResult.syncResult; + componentCompilePromises.push(compileResult.asyncResult); + dep.placeholder.name = `compFactory_${dep.comp.name}`; + }); + if (IS_DART || !this._genConfig.useJit) { + appModuleFactory = + interpretStatements(compileResult.statements, compileResult.appModuleFactoryVar); + } else { + appModuleFactory = jitStatements( + `${compileModuleMeta.type.name}.ngfactory.js`, compileResult.statements, + compileResult.appModuleFactoryVar); + } + if (useCache) { + this._compiledAppModuleCache.set(moduleType, appModuleFactory); + } + } + return new SyncAsyncResult( + appModuleFactory, Promise.all(componentCompilePromises).then(() => appModuleFactory)); + } + + compileComponentAsync(compType: ConcreteType, {moduleDirectives = [], modulePipes = []}: { + moduleDirectives?: ConcreteType[], + modulePipes?: ConcreteType[] + } = {}): Promise> { + return this._compileComponent(compType, false, moduleDirectives, modulePipes).asyncResult; + } + + compileComponentSync(compType: ConcreteType, {moduleDirectives = [], modulePipes = []}: { + moduleDirectives?: ConcreteType[], + modulePipes?: ConcreteType[] + } = {}): ComponentFactory { + return this._compileComponent(compType, true, moduleDirectives, modulePipes).syncResult; + } + + private _compileComponent( + compType: ConcreteType, isSync: boolean, moduleDirectives: ConcreteType[], + modulePipes: ConcreteType[]): SyncAsyncResult> { + var templates = + this._getTransitiveCompiledTemplates(compType, true, moduleDirectives, modulePipes); var loadingPromises: Promise[] = []; templates.forEach((template) => { if (template.loading) { - loadingPromises.push(template.loading); + if (isSync) { + throw new BaseException( + `Can't compile synchronously as ${template.compType.name} is still being loaded!`); + } else { + loadingPromises.push(template.loading); + } } }); - return Promise.all(loadingPromises).then(() => { - templates.forEach((template) => { this._compileTemplate(template); }); - return this._getCompiledHostTemplate(compType).proxyComponentFactory; - }); + let compile = () => { templates.forEach((template) => { this._compileTemplate(template); }); }; + if (isSync) { + compile(); + } + let result = this._compiledHostTemplateCache.get(compType).proxyComponentFactory; + return new SyncAsyncResult(result, Promise.all(loadingPromises).then(() => { + compile(); + return result; + })); } - compileComponentSync(compType: ConcreteType): ComponentFactory { - var templates = this._getTransitiveCompiledTemplates(compType, true); - templates.forEach((template) => { - if (template.loading) { - throw new BaseException( - `Can't compile synchronously as ${template.compType.name} is still being loaded!`); - } - }); - templates.forEach((template) => { this._compileTemplate(template); }); - return this._getCompiledHostTemplate(compType).proxyComponentFactory; - } - - clearCacheFor(compType: Type) { - this._metadataResolver.clearCacheFor(compType); - this._compiledHostTemplateCache.delete(compType); - var compiledTemplate = this._compiledTemplateCache.get(compType); + clearCacheFor(type: Type) { + this._compiledAppModuleCache.delete(type); + this._metadataResolver.clearCacheFor(type); + this._compiledHostTemplateCache.delete(type); + var compiledTemplate = this._compiledTemplateCache.get(type); if (compiledTemplate) { this._templateNormalizer.clearCacheFor(compiledTemplate.normalizedCompMeta); - this._compiledTemplateCache.delete(compType); + this._compiledTemplateCache.delete(type); } } @@ -95,9 +159,10 @@ export class RuntimeCompiler implements ComponentResolver, Compiler { this._compiledTemplateCache.clear(); this._compiledHostTemplateCache.clear(); this._templateNormalizer.clearCache(); + this._compiledAppModuleCache.clear(); } - private _getCompiledHostTemplate(type: Type): CompiledTemplate { + private _createCompiledHostTemplate(type: Type): CompiledTemplate { var compiledTemplate = this._compiledHostTemplateCache.get(type); if (isBlank(compiledTemplate)) { var compMeta = this._metadataResolver.getDirectiveMetadata(type); @@ -111,12 +176,16 @@ export class RuntimeCompiler implements ComponentResolver, Compiler { return compiledTemplate; } - private _getCompiledTemplate(type: Type): CompiledTemplate { + private _createCompiledTemplate( + type: Type, moduleDirectives: ConcreteType[], + modulePipes: ConcreteType[]): CompiledTemplate { var compiledTemplate = this._compiledTemplateCache.get(type); if (isBlank(compiledTemplate)) { var compMeta = this._metadataResolver.getDirectiveMetadata(type); assertComponent(compMeta); var viewDirectives: CompileDirectiveMetadata[] = []; + moduleDirectives.forEach( + (type) => viewDirectives.push(this._metadataResolver.getDirectiveMetadata(type))); var viewComponentTypes: Type[] = []; this._metadataResolver.getViewDirectivesMetadata(type).forEach(dirOrComp => { if (dirOrComp.isComponent) { @@ -126,7 +195,10 @@ export class RuntimeCompiler implements ComponentResolver, Compiler { } }); var precompileComponentTypes = compMeta.precompile.map((typeMeta) => typeMeta.runtime); - var pipes = this._metadataResolver.getViewPipesMetadata(type); + var pipes = [ + ...modulePipes.map((type) => this._metadataResolver.getPipeMetadata(type)), + ...this._metadataResolver.getViewPipesMetadata(type) + ]; compiledTemplate = new CompiledTemplate( false, compMeta.selector, compMeta.type, viewDirectives, viewComponentTypes, precompileComponentTypes, pipes, this._templateNormalizer.normalizeDirective(compMeta)); @@ -136,16 +208,20 @@ export class RuntimeCompiler implements ComponentResolver, Compiler { } private _getTransitiveCompiledTemplates( - compType: Type, isHost: boolean, + compType: Type, isHost: boolean, moduleDirectives: ConcreteType[], + modulePipes: ConcreteType[], target: Set = new Set()): Set { - var template = - isHost ? this._getCompiledHostTemplate(compType) : this._getCompiledTemplate(compType); + var template = isHost ? this._createCompiledHostTemplate(compType) : + this._createCompiledTemplate(compType, moduleDirectives, modulePipes); if (!target.has(template)) { target.add(template); - template.viewComponentTypes.forEach( - (compType) => { this._getTransitiveCompiledTemplates(compType, false, target); }); - template.precompileHostComponentTypes.forEach( - (compType) => { this._getTransitiveCompiledTemplates(compType, true, target); }); + template.viewComponentTypes.forEach((compType) => { + this._getTransitiveCompiledTemplates( + compType, false, moduleDirectives, modulePipes, target); + }); + template.precompileHostComponentTypes.forEach((compType) => { + this._getTransitiveCompiledTemplates(compType, true, moduleDirectives, modulePipes, target); + }); } return target; } @@ -162,7 +238,7 @@ export class RuntimeCompiler implements ComponentResolver, Compiler { this._resolveStylesCompileResult( stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl); var viewCompMetas = template.viewComponentTypes.map( - (compType) => this._getCompiledTemplate(compType).normalizedCompMeta); + (compType) => this._compiledTemplateCache.get(compType).normalizedCompMeta); var parsedTemplate = this._templateParser.parse( compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas), template.viewPipes, compMeta.type.name); @@ -173,12 +249,12 @@ export class RuntimeCompiler implements ComponentResolver, Compiler { let depTemplate: CompiledTemplate; if (dep instanceof ViewFactoryDependency) { let vfd = dep; - depTemplate = this._getCompiledTemplate(vfd.comp.runtime); + depTemplate = this._compiledTemplateCache.get(vfd.comp.runtime); vfd.placeholder.runtime = depTemplate.proxyViewFactory; vfd.placeholder.name = `viewFactory_${vfd.comp.name}`; } else if (dep instanceof ComponentFactoryDependency) { let cfd = dep; - depTemplate = this._getCompiledHostTemplate(cfd.comp.runtime); + depTemplate = this._compiledHostTemplateCache.get(cfd.comp.runtime); cfd.placeholder.runtime = depTemplate.proxyComponentFactory; cfd.placeholder.name = `compFactory_${cfd.comp.name}`; } @@ -188,11 +264,10 @@ export class RuntimeCompiler implements ComponentResolver, Compiler { stylesCompileResult.componentStylesheet.statements.concat(compileResult.statements); var factory: any; if (IS_DART || !this._genConfig.useJit) { - factory = interpretStatements( - statements, compileResult.viewFactoryVar, new InterpretiveAppViewInstanceFactory()); + factory = interpretStatements(statements, compileResult.viewFactoryVar); } else { factory = jitStatements( - `${template.compType.name}.template.js`, statements, compileResult.viewFactoryVar); + `${template.compType.name}.ngfactory.js`, statements, compileResult.viewFactoryVar); } template.compiled(factory); } @@ -213,8 +288,7 @@ export class RuntimeCompiler implements ComponentResolver, Compiler { externalStylesheetsByModuleUrl: Map): string[] { this._resolveStylesCompileResult(result, externalStylesheetsByModuleUrl); if (IS_DART || !this._genConfig.useJit) { - return interpretStatements( - result.statements, result.stylesVar, new InterpretiveAppViewInstanceFactory()); + return interpretStatements(result.statements, result.stylesVar); } else { return jitStatements(`${result.meta.moduleUrl}.css.js`, result.statements, result.stylesVar); } @@ -234,7 +308,7 @@ class CompiledTemplate { public isHost: boolean, selector: string, public compType: CompileIdentifierMetadata, public viewDirectives: CompileDirectiveMetadata[], public viewComponentTypes: Type[], public precompileHostComponentTypes: Type[], public viewPipes: CompilePipeMetadata[], - private _normalizeResult: NormalizeDirectiveResult) { + _normalizeResult: SyncAsyncResult) { this.proxyViewFactory = (...args: any[]) => this._viewFactory.apply(null, args); this.proxyComponentFactory = isHost ? new ComponentFactory(selector, this.proxyViewFactory, compType.runtime) : diff --git a/modules/@angular/compiler/src/util.ts b/modules/@angular/compiler/src/util.ts index ba3f671850..9ff114b61e 100644 --- a/modules/@angular/compiler/src/util.ts +++ b/modules/@angular/compiler/src/util.ts @@ -6,8 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ +import {CompileTokenMetadata} from './compile_metadata'; import {StringMapWrapper} from './facade/collection'; -import {IS_DART, StringWrapper, isArray, isBlank, isPrimitive, isStrictStringMap} from './facade/lang'; +import {IS_DART, StringWrapper, isArray, isBlank, isPresent, isPrimitive, isStrictStringMap} from './facade/lang'; +import * as o from './output/output_ast'; export var MODULE_SUFFIX = IS_DART ? '.dart' : ''; @@ -80,3 +82,22 @@ export function assetUrl(pkg: string, path: string = null, type: string = 'src') } } } + +export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression { + if (isPresent(token.value)) { + return o.literal(token.value); + } else if (token.identifierIsInstance) { + return o.importExpr(token.identifier) + .instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const])); + } else { + return o.importExpr(token.identifier); + } +} + +export class SyncAsyncResult { + constructor(public syncResult: T, public asyncResult: Promise = null) { + if (!asyncResult) { + asyncResult = Promise.resolve(syncResult); + } + } +} diff --git a/modules/@angular/compiler/src/view_compiler/compile_element.ts b/modules/@angular/compiler/src/view_compiler/compile_element.ts index 5427f046a8..920969790b 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_element.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_element.ts @@ -18,10 +18,10 @@ import {CompileView} from './compile_view'; import {InjectMethodVars} from './constants'; import {CompileTokenMap, CompileDirectiveMetadata, CompileTokenMetadata, CompileQueryMetadata, CompileProviderMetadata, CompileDiDependencyMetadata, CompileIdentifierMetadata,} from '../compile_metadata'; -import {getPropertyInView, createDiTokenExpression, injectFromViewParentInjector} from './util'; +import {getPropertyInView, injectFromViewParentInjector} from './util'; import {CompileQuery, createQueryList, addQueryToTokenMap} from './compile_query'; import {CompileMethod} from './compile_method'; -import {ValueTransformer, visitValue} from '../util'; +import {createDiTokenExpression} from '../util'; export class CompileNode { constructor( @@ -165,7 +165,7 @@ export class CompileElement extends CompileNode { return o.importExpr(provider.useClass) .instantiate(depsExpr, o.importType(provider.useClass)); } else { - return _convertValueToOutputAst(provider.useValue); + return o.literal(provider.useValue); } }); var propName = `_${resolvedProvider.token.name}_${this.nodeIndex}_${this._instances.size}`; @@ -432,30 +432,3 @@ class _QueryWithRead { this.read = isPresent(query.meta.read) ? query.meta.read : match; } } - -function _convertValueToOutputAst(value: any): o.Expression { - return visitValue(value, new _ValueOutputAstTransformer(), null); -} - -class _ValueOutputAstTransformer extends ValueTransformer { - visitArray(arr: any[], context: any): o.Expression { - return o.literalArr(arr.map(value => visitValue(value, this, context))); - } - visitStringMap(map: {[key: string]: any}, context: any): o.Expression { - var entries: Array[] = []; - StringMapWrapper.forEach(map, (value: any, key: string) => { - entries.push([key, visitValue(value, this, context)]); - }); - return o.literalMap(entries); - } - visitPrimitive(value: any, context: any): o.Expression { return o.literal(value); } - visitOther(value: any, context: any): o.Expression { - if (value instanceof CompileIdentifierMetadata) { - return o.importExpr(value); - } else if (value instanceof o.Expression) { - return value; - } else { - throw new BaseException(`Illegal state: Don't now how to compile value ${value}`); - } - } -} diff --git a/modules/@angular/compiler/src/view_compiler/compile_view.ts b/modules/@angular/compiler/src/view_compiler/compile_view.ts index 1b06ee2759..d5b68ec857 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_view.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_view.ts @@ -14,6 +14,7 @@ import {ListWrapper} from '../facade/collection'; import {isBlank, isPresent} from '../facade/lang'; import {Identifiers} from '../identifiers'; import * as o from '../output/output_ast'; +import {createDiTokenExpression} from '../util'; import {CompileBinding} from './compile_binding'; import {CompileElement, CompileNode} from './compile_element'; @@ -22,7 +23,7 @@ import {CompilePipe} from './compile_pipe'; import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query'; import {EventHandlerVars} from './constants'; import {NameResolver} from './expression_converter'; -import {createDiTokenExpression, createPureProxy, getPropertyInView, getViewFactoryName, injectFromViewParentInjector} from './util'; +import {createPureProxy, getPropertyInView, getViewFactoryName, injectFromViewParentInjector} from './util'; export class CompileView implements NameResolver { public viewType: ViewType; diff --git a/modules/@angular/compiler/src/view_compiler/util.ts b/modules/@angular/compiler/src/view_compiler/util.ts index f89a8a51d2..2421c35784 100644 --- a/modules/@angular/compiler/src/view_compiler/util.ts +++ b/modules/@angular/compiler/src/view_compiler/util.ts @@ -13,6 +13,7 @@ import * as o from '../output/output_ast'; import {CompileTokenMetadata, CompileDirectiveMetadata,} from '../compile_metadata'; import {CompileView} from './compile_view'; import {Identifiers} from '../identifiers'; +import {createDiTokenExpression} from '../util'; export function getPropertyInView( property: o.Expression, callingView: CompileView, definedView: CompileView): o.Expression { @@ -55,18 +56,6 @@ export function getViewFactoryName( return `viewFactory_${component.type.name}${embeddedTemplateIndex}`; } - -export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression { - if (isPresent(token.value)) { - return o.literal(token.value); - } else if (token.identifierIsInstance) { - return o.importExpr(token.identifier) - .instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const])); - } else { - return o.importExpr(token.identifier); - } -} - export function createFlatArray(expressions: o.Expression[]): o.Expression { var lastNonArrayExpressions: any[] /** TODO #9100 */ = []; var result: o.Expression = o.literalArr([]); diff --git a/modules/@angular/compiler/src/view_compiler/view_builder.ts b/modules/@angular/compiler/src/view_compiler/view_builder.ts index 3f573ccf5c..b07a20bd42 100644 --- a/modules/@angular/compiler/src/view_compiler/view_builder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_builder.ts @@ -16,11 +16,12 @@ import {StringWrapper, isPresent} from '../facade/lang'; import {Identifiers, identifierToken} from '../identifiers'; import * as o from '../output/output_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ProviderAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_ast'; +import {createDiTokenExpression} from '../util'; import {CompileElement, CompileNode} from './compile_element'; import {CompileView} from './compile_view'; import {ChangeDetectorStatusEnum, DetectChangesVars, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants'; -import {createDiTokenExpression, createFlatArray, getViewFactoryName} from './util'; +import {createFlatArray, getViewFactoryName} from './util'; const IMPLICIT_TEMPLATE_VAR = '\$implicit'; const CLASS_ATTR = 'class'; diff --git a/modules/@angular/compiler/test/offline_compiler_codegen_typed.ts b/modules/@angular/compiler/test/offline_compiler_codegen_typed.ts deleted file mode 100644 index d9041f8403..0000000000 --- a/modules/@angular/compiler/test/offline_compiler_codegen_typed.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @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 - */ - -// ATTENTION: This file will be overwritten with generated code by main() -import {DartEmitter} from '@angular/compiler/src/output/dart_emitter'; -import {DartImportGenerator} from '@angular/compiler/src/output/dart_imports'; -import * as o from '@angular/compiler/src/output/output_ast'; -import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; -import {ComponentFactory} from '@angular/core/src/linker/component_factory'; - -import {IS_DART, print} from '../src/facade/lang'; - -import {compAMetadata, compileComp} from './offline_compiler_util'; -import {CompA, SimpleJsImportGenerator} from './offline_compiler_util'; - -export const CompANgFactory: ComponentFactory = null; - -export function emit(): Promise { - var emitter = IS_DART ? new DartEmitter(new DartImportGenerator()) : - new TypeScriptEmitter(new SimpleJsImportGenerator()); - return compileComp(emitter, compAMetadata); -} - -// Generator -export function main(args: string[]) { - emit().then((source) => { - // debug: console.error(source); - print(source); - }); -} diff --git a/modules/@angular/compiler/test/offline_compiler_codegen_untyped.ts b/modules/@angular/compiler/test/offline_compiler_codegen_untyped.ts deleted file mode 100644 index 40aed4c8b7..0000000000 --- a/modules/@angular/compiler/test/offline_compiler_codegen_untyped.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @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 - */ - -// ATTENTION: This file will be overwritten with generated code by main() -import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; -import {ComponentFactory} from '@angular/core/src/linker/component_factory'; - -import {print} from '../src/facade/lang'; - -import {compAMetadata, compileComp} from './offline_compiler_util'; -import {CompA, SimpleJsImportGenerator} from './offline_compiler_util'; - -export const CompANgFactory: ComponentFactory = null; - -export function emit() { - var emitter = new JavaScriptEmitter(new SimpleJsImportGenerator()); - return compileComp(emitter, compAMetadata); -} - -// Generator -export function main(args: string[]) { - emit().then((source) => { - // debug: console.error(source); - print(source); - }); -} diff --git a/modules/@angular/compiler/test/offline_compiler_compa.css.shim.ts b/modules/@angular/compiler/test/offline_compiler_compa.css.shim.ts deleted file mode 100644 index ab2f0fae33..0000000000 --- a/modules/@angular/compiler/test/offline_compiler_compa.css.shim.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @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 const styles = /*@ts2dart_const*/[`.greenStyle[_ngcontent-a-1] { color: green; }`]; diff --git a/modules/@angular/compiler/test/offline_compiler_spec.ts b/modules/@angular/compiler/test/offline_compiler_spec.ts deleted file mode 100644 index 2d95f8fbaf..0000000000 --- a/modules/@angular/compiler/test/offline_compiler_spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @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 {ddescribe, describe, xdescribe, it, iit, xit, expect, beforeEach, afterEach, inject, beforeEachProviders,} from '@angular/core/testing/testing_internal'; - -import {IS_DART} from '../src/facade/lang'; -import {Injector} from '@angular/core'; - -import {ComponentFactory} from '@angular/core/src/linker/component_factory'; -import * as typed from './offline_compiler_codegen_typed'; -import * as untyped from './offline_compiler_codegen_untyped'; - -import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; -import {SharedStylesHost} from '@angular/platform-browser/src/dom/shared_styles_host'; - -import {CompA} from './offline_compiler_util'; - -export function main() { - var typedComponentFactory = typed.CompANgFactory; - var untypedComponentFactory = untyped.CompANgFactory; - var fixtures: TestFixture[] = []; - - if (IS_DART || !getDOM().supportsDOMEvents()) { - // Our generator only works on node.js and Dart... - fixtures.push(new TestFixture(typedComponentFactory, 'typed')); - } - if (!IS_DART) { - // Our generator only works on node.js and Dart... - if (!getDOM().supportsDOMEvents()) { - fixtures.push(new TestFixture(untypedComponentFactory, 'untyped')); - } - } - describe('OfflineCompiler', () => { - var injector: Injector; - var sharedStylesHost: SharedStylesHost; - - beforeEach(inject( - [Injector, SharedStylesHost], - (_injector: Injector, _sharedStylesHost: SharedStylesHost) => { - injector = _injector; - sharedStylesHost = _sharedStylesHost; - })); - - fixtures.forEach((fixture) => { - describe(`${fixture.name}`, () => { - it('should compile components', () => { - var hostEl = fixture.compFactory.create(injector); - expect(hostEl.instance).toBeAnInstanceOf(CompA); - var styles = sharedStylesHost.getAllStyles(); - expect(styles[0]).toContain('.redStyle[_ngcontent'); - expect(styles[1]).toContain('.greenStyle[_ngcontent'); - }); - }); - }); - }); -} - -class TestFixture { - constructor(public compFactory: ComponentFactory, public name: string) {} -} diff --git a/modules/@angular/compiler/test/offline_compiler_util.ts b/modules/@angular/compiler/test/offline_compiler_util.ts deleted file mode 100644 index 81f6f08c12..0000000000 --- a/modules/@angular/compiler/test/offline_compiler_util.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * @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 {CompileDirectiveMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata'; -import {CompilerConfig} from '@angular/compiler/src/config'; -import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer'; -import {Lexer} from '@angular/compiler/src/expression_parser/lexer'; -import {Parser} from '@angular/compiler/src/expression_parser/parser'; -import {HtmlParser} from '@angular/compiler/src/html_parser'; -import {NormalizedComponentWithViewDirectives, OfflineCompiler, SourceModule} from '@angular/compiler/src/offline_compiler'; -import {OutputEmitter} from '@angular/compiler/src/output/abstract_emitter'; -import {ImportGenerator} from '@angular/compiler/src/output/path_util'; -import {StyleCompiler} from '@angular/compiler/src/style_compiler'; -import {TemplateParser} from '@angular/compiler/src/template_parser'; -import {createOfflineCompileUrlResolver} from '@angular/compiler/src/url_resolver'; -import {MODULE_SUFFIX} from '@angular/compiler/src/util'; -import {ViewCompiler} from '@angular/compiler/src/view_compiler/view_compiler'; -import {XHR} from '@angular/compiler/src/xhr'; - -import {Console} from '../core_private'; -import {IS_DART, isPresent, print} from '../src/facade/lang'; -import {MockSchemaRegistry} from '../testing/schema_registry_mock'; - - -export class CompA { user: string; } - -var THIS_MODULE_PATH = `asset:@angular/lib/compiler/test`; -var THIS_MODULE_URL = `${THIS_MODULE_PATH}/offline_compiler_util${MODULE_SUFFIX}`; - -export var compAMetadata = CompileDirectiveMetadata.create({ - isComponent: true, - selector: 'comp-a', - type: new CompileTypeMetadata( - {name: 'CompA', moduleUrl: THIS_MODULE_URL, runtime: CompA, diDeps: []}), - template: new CompileTemplateMetadata({ - templateUrl: './offline_compiler_compa.html', - styles: ['.redStyle { color: red; }'], - styleUrls: ['./offline_compiler_compa.css'] - }) -}); - -function _createOfflineCompiler(xhr: XHR, emitter: OutputEmitter): OfflineCompiler { - var urlResolver = createOfflineCompileUrlResolver(); - var htmlParser = new HtmlParser(); - var config = new CompilerConfig({genDebugInfo: true, useJit: true}); - var normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config); - return new OfflineCompiler( - normalizer, - new TemplateParser( - new Parser(new Lexer()), new MockSchemaRegistry({}, {}), htmlParser, new Console(), []), - new StyleCompiler(urlResolver), new ViewCompiler(config), emitter); -} - -export function compileComp( - emitter: OutputEmitter, comp: CompileDirectiveMetadata): Promise { - var xhr = new MockXhr(); - xhr.setResult(`${THIS_MODULE_PATH}/offline_compiler_compa.html`, ''); - xhr.setResult(`${THIS_MODULE_PATH}/offline_compiler_compa.css`, ''); - var compiler = _createOfflineCompiler(xhr, emitter); - var result = compiler.normalizeDirectiveMetadata(comp).then((normComp) => { - return compiler - .compileTemplates([new NormalizedComponentWithViewDirectives(normComp, [], [])])[0] - .source; - }); - return result; -} - -export class SimpleJsImportGenerator implements ImportGenerator { - getImportPath(moduleUrlStr: string, importedUrlStr: string): string { - var importedAssetUrl = ImportGenerator.parseAssetUrl(importedUrlStr); - if (isPresent(importedAssetUrl)) { - return `${importedAssetUrl.packageName}/${importedAssetUrl.modulePath}`; - } else { - return importedUrlStr; - } - } -} - -class MockXhr implements XHR { - results: {[key: string]: string} = {}; - setResult(url: string, content: string) { this.results[url] = content; } - - get(url: string): Promise { - if (url in this.results) { - return Promise.resolve(this.results[url]); - } - return Promise.reject(`Unknown url ${url}`); - } -} \ No newline at end of file diff --git a/modules/@angular/compiler/test/output/js_emitter_spec.ts b/modules/@angular/compiler/test/output/js_emitter_spec.ts index 957165cba0..c023152bbc 100644 --- a/modules/@angular/compiler/test/output/js_emitter_spec.ts +++ b/modules/@angular/compiler/test/output/js_emitter_spec.ts @@ -12,7 +12,7 @@ import {isBlank} from '../../src/facade/lang'; import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata'; import * as o from '@angular/compiler/src/output/output_ast'; -import {SimpleJsImportGenerator} from '../offline_compiler_util'; +import {SimpleJsImportGenerator} from './output_emitter_util'; var someModuleUrl = 'asset:somePackage/lib/somePath'; var anotherModuleUrl = 'asset:somePackage/lib/someOtherPath'; diff --git a/modules/@angular/compiler/test/output/output_emitter_codegen_typed.ts b/modules/@angular/compiler/test/output/output_emitter_codegen_typed.ts index e334de455d..f585050da4 100644 --- a/modules/@angular/compiler/test/output/output_emitter_codegen_typed.ts +++ b/modules/@angular/compiler/test/output/output_emitter_codegen_typed.ts @@ -15,9 +15,8 @@ import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; import {unimplemented} from '../../src/facade/exceptions'; import {IS_DART, print} from '../../src/facade/lang'; import {assetUrl} from '../../src/util'; -import {SimpleJsImportGenerator} from '../offline_compiler_util'; -import {codegenExportsVars, codegenStmts} from './output_emitter_util'; +import {SimpleJsImportGenerator, codegenExportsVars, codegenStmts} from './output_emitter_util'; export function getExpressions(): any { return unimplemented(); diff --git a/modules/@angular/compiler/test/output/output_emitter_codegen_untyped.ts b/modules/@angular/compiler/test/output/output_emitter_codegen_untyped.ts index b26e751c87..12869d3b42 100644 --- a/modules/@angular/compiler/test/output/output_emitter_codegen_untyped.ts +++ b/modules/@angular/compiler/test/output/output_emitter_codegen_untyped.ts @@ -12,9 +12,8 @@ import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; import {unimplemented} from '../../src/facade/exceptions'; import {print} from '../../src/facade/lang'; import {assetUrl} from '../../src/util'; -import {SimpleJsImportGenerator} from '../offline_compiler_util'; -import {codegenExportsVars, codegenStmts} from './output_emitter_util'; +import {SimpleJsImportGenerator, codegenExportsVars, codegenStmts} from './output_emitter_util'; export function getExpressions(): any { return unimplemented(); diff --git a/modules/@angular/compiler/test/output/output_emitter_spec.ts b/modules/@angular/compiler/test/output/output_emitter_spec.ts index 3f4211a52b..fc50d1ba81 100644 --- a/modules/@angular/compiler/test/output/output_emitter_spec.ts +++ b/modules/@angular/compiler/test/output/output_emitter_spec.ts @@ -14,7 +14,7 @@ import * as typed from './output_emitter_codegen_typed'; import * as untyped from './output_emitter_codegen_untyped'; import {jitStatements} from '@angular/compiler/src/output/output_jit'; import {interpretStatements} from '@angular/compiler/src/output/output_interpreter'; -import {codegenStmts, ExternalClass, DynamicClassInstanceFactory} from './output_emitter_util'; +import {codegenStmts, ExternalClass} from './output_emitter_util'; import {EventEmitter} from '@angular/core'; import {ViewType} from '@angular/core/src/linker/view_type'; import {BaseException} from '@angular/core'; @@ -23,8 +23,7 @@ import {browserDetection} from '@angular/platform-browser/testing/browser_util' export function main() { var outputDefs: any[] /** TODO #9100 */ = []; outputDefs.push({ - 'getExpressions': () => interpretStatements( - codegenStmts, 'getExpressions', new DynamicClassInstanceFactory()), + 'getExpressions': () => interpretStatements(codegenStmts, 'getExpressions'), 'name': 'interpreted' }); if (IS_DART || !getDOM().supportsDOMEvents()) { diff --git a/modules/@angular/compiler/test/output/output_emitter_util.ts b/modules/@angular/compiler/test/output/output_emitter_util.ts index 0e70f1061a..e98f4c2c8d 100644 --- a/modules/@angular/compiler/test/output/output_emitter_util.ts +++ b/modules/@angular/compiler/test/output/output_emitter_util.ts @@ -8,7 +8,7 @@ import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata'; import * as o from '@angular/compiler/src/output/output_ast'; -import {DynamicInstance, InstanceFactory} from '@angular/compiler/src/output/output_interpreter'; +import {ImportGenerator} from '@angular/compiler/src/output/path_util'; import {assetUrl} from '@angular/compiler/src/util'; import {EventEmitter} from '@angular/core'; import {ViewType} from '@angular/core/src/linker/view_type'; @@ -253,22 +253,13 @@ function createOperatorFn(op: o.BinaryOperator) { o.DYNAMIC_TYPE); } -export class DynamicClassInstanceFactory implements InstanceFactory { - createInstance( - superClass: any, clazz: any, args: any[], props: Map, - getters: Map, methods: Map): any { - if (superClass === ExternalClass) { - return new _InterpretiveDynamicClass(args, clazz, props, getters, methods); +export class SimpleJsImportGenerator implements ImportGenerator { + getImportPath(moduleUrlStr: string, importedUrlStr: string): string { + var importedAssetUrl = ImportGenerator.parseAssetUrl(importedUrlStr); + if (importedAssetUrl) { + return `${importedAssetUrl.packageName}/${importedAssetUrl.modulePath}`; + } else { + return importedUrlStr; } - throw new BaseException(`Can't instantiate class ${superClass} in interpretative mode`); } } - -class _InterpretiveDynamicClass extends ExternalClass implements DynamicInstance { - constructor( - args: any[], public clazz: any, public props: Map, - public getters: Map, public methods: Map) { - super(args[0]); - } - childMethod(a: any /** TODO #9100 */) { return this.methods.get('childMethod')(a); } -} diff --git a/modules/@angular/compiler/test/output/ts_emitter_spec.ts b/modules/@angular/compiler/test/output/ts_emitter_spec.ts index 3589dfb87c..7d9203763e 100644 --- a/modules/@angular/compiler/test/output/ts_emitter_spec.ts +++ b/modules/@angular/compiler/test/output/ts_emitter_spec.ts @@ -12,7 +12,7 @@ import {isBlank} from '../../src/facade/lang'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata'; import * as o from '@angular/compiler/src/output/output_ast'; -import {SimpleJsImportGenerator} from '../offline_compiler_util'; +import {SimpleJsImportGenerator} from './output_emitter_util'; var someModuleUrl = 'asset:somePackage/lib/somePath'; var anotherModuleUrl = 'asset:somePackage/lib/someOtherPath'; diff --git a/modules/@angular/compiler/test/runtime_compiler_spec.ts b/modules/@angular/compiler/test/runtime_compiler_spec.ts index 2ad77e4485..af58f6b3de 100644 --- a/modules/@angular/compiler/test/runtime_compiler_spec.ts +++ b/modules/@angular/compiler/test/runtime_compiler_spec.ts @@ -1,5 +1,5 @@ import {beforeEach, ddescribe, xdescribe, describe, expect, iit, inject, beforeEachProviders, it, xit,} from '@angular/core/testing/testing_internal'; -import {Injectable, Component, Input, ViewMetadata, Compiler, ComponentFactory, Injector} from '@angular/core'; +import {Injectable, Component, Input, ViewMetadata, Compiler, ComponentFactory, Injector, AppModule, AppModuleMetadata, AppModuleFactory} from '@angular/core'; import {ConcreteType, stringify} from '../src/facade/lang'; import {fakeAsync, tick, TestComponentBuilder, ComponentFixture} from '@angular/core/testing'; import {XHR, ViewResolver} from '@angular/compiler'; @@ -19,6 +19,10 @@ class SomeComp { class SomeCompWithUrlTemplate { } +@AppModule({}) +class SomeModule { +} + export function main() { describe('RuntimeCompiler', () => { let compiler: Compiler; @@ -104,5 +108,51 @@ export function main() { expect(compFixture.nativeElement).toHaveText('hello'); })); }); + + describe('compileAppModuleAsync', () => { + it('should allow to use templateUrl components', fakeAsync(() => { + xhr.spy('get').andCallFake(() => Promise.resolve('hello')); + let appModuleFactory: AppModuleFactory; + compiler + .compileAppModuleAsync( + SomeModule, new AppModuleMetadata({precompile: [SomeCompWithUrlTemplate]})) + .then((f) => appModuleFactory = f); + tick(); + expect(appModuleFactory.moduleType).toBe(SomeModule); + })); + }); + + describe('compileAppModuleSync', () => { + it('should throw when using a templateUrl that has not been compiled before', () => { + xhr.spy('get').andCallFake(() => Promise.resolve('')); + expect( + () => compiler.compileAppModuleSync( + SomeModule, new AppModuleMetadata({precompile: [SomeCompWithUrlTemplate]}))) + .toThrowError( + `Can't compile synchronously as ${stringify(SomeCompWithUrlTemplate)} is still being loaded!`); + }); + + it('should throw when using a templateUrl in a nested component that has not been compiled before', + () => { + xhr.spy('get').andCallFake(() => Promise.resolve('')); + viewResolver.setView( + SomeComp, new ViewMetadata({template: '', directives: [ChildComp]})); + viewResolver.setView(ChildComp, new ViewMetadata({templateUrl: '/someTpl.html'})); + expect( + () => compiler.compileAppModuleSync( + SomeModule, new AppModuleMetadata({precompile: [SomeComp]}))) + .toThrowError( + `Can't compile synchronously as ${stringify(ChildComp)} is still being loaded!`); + }); + + it('should allow to use templateUrl components that have been loaded before', + fakeAsync(() => { + xhr.spy('get').andCallFake(() => Promise.resolve('hello')); + tcb.createFakeAsync(SomeCompWithUrlTemplate); + let appModuleFactory = compiler.compileAppModuleSync( + SomeModule, new AppModuleMetadata({precompile: [SomeCompWithUrlTemplate]})); + expect(appModuleFactory).toBeTruthy(); + })); + }); }); } \ No newline at end of file diff --git a/modules/@angular/core/private_export.ts b/modules/@angular/core/private_export.ts index 92dd842bb1..417e4e5180 100644 --- a/modules/@angular/core/private_export.ts +++ b/modules/@angular/core/private_export.ts @@ -21,6 +21,7 @@ import * as console from './src/console'; import * as debug from './src/debug/debug_renderer'; import * as provider_util from './src/di/provider_util'; import * as reflective_provider from './src/di/reflective_provider'; +import * as app_module_factory from './src/linker/app_module_factory'; import * as component_factory_resolver from './src/linker/component_factory_resolver'; import * as component_resolver from './src/linker/component_resolver'; import * as debug_context from './src/linker/debug_context'; @@ -58,6 +59,7 @@ export declare namespace __core_private_types__ { export type AppElement = element.AppElement; export var AppElement: typeof element.AppElement; export var AppView: typeof view.AppView; + export var AppModuleInjector: typeof app_module_factory.AppModuleInjector; export type DebugAppView = view.DebugAppView; export var DebugAppView: typeof view.DebugAppView; export type ViewType = view_type.ViewType; @@ -148,6 +150,7 @@ export var __core_private__ = { AppElement: element.AppElement, AppView: view.AppView, DebugAppView: view.DebugAppView, + AppModuleInjector: app_module_factory.AppModuleInjector, ViewType: view_type.ViewType, MAX_INTERPOLATION_VALUES: view_utils.MAX_INTERPOLATION_VALUES, checkBinding: view_utils.checkBinding, diff --git a/modules/@angular/core/src/di/injector.ts b/modules/@angular/core/src/di/injector.ts index 0e2fd84242..df56e4c7b8 100644 --- a/modules/@angular/core/src/di/injector.ts +++ b/modules/@angular/core/src/di/injector.ts @@ -6,16 +6,27 @@ * found in the LICENSE file at https://angular.io/license */ -import {unimplemented} from '../facade/exceptions'; +import {BaseException, unimplemented} from '../facade/exceptions'; +import {stringify} from '../facade/lang'; const _THROW_IF_NOT_FOUND = /*@ts2dart_const*/ new Object(); export const THROW_IF_NOT_FOUND = /*@ts2dart_const*/ _THROW_IF_NOT_FOUND; +class _NullInjector implements Injector { + get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any { + if (notFoundValue === _THROW_IF_NOT_FOUND) { + throw new BaseException(`No provider for ${stringify(token)}!`); + } + return notFoundValue; + } +} + /** * @stable */ export abstract class Injector { static THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND; + static NULL: Injector = new _NullInjector(); /** * Retrieves an instance from the injector based on the provided token. diff --git a/modules/@angular/core/src/linker.ts b/modules/@angular/core/src/linker.ts index 263176f1d7..a300cb4bf5 100644 --- a/modules/@angular/core/src/linker.ts +++ b/modules/@angular/core/src/linker.ts @@ -7,6 +7,7 @@ */ // Public API for compiler +export {AppModuleFactory, AppModuleRef} from './linker/app_module_factory'; export {Compiler} from './linker/compiler'; export {ComponentFactory, ComponentRef} from './linker/component_factory'; export {ComponentFactoryResolver, NoComponentFactoryError} from './linker/component_factory_resolver'; diff --git a/modules/@angular/core/src/linker/app_module_factory.ts b/modules/@angular/core/src/linker/app_module_factory.ts new file mode 100644 index 0000000000..bc66577875 --- /dev/null +++ b/modules/@angular/core/src/linker/app_module_factory.ts @@ -0,0 +1,90 @@ +/** + * @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 {Injector, THROW_IF_NOT_FOUND} from '../di/injector'; +import {unimplemented} from '../facade/exceptions'; +import {ConcreteType} from '../facade/lang'; + +import {ComponentFactory} from './component_factory'; +import {CodegenComponentFactoryResolver, ComponentFactoryResolver} from './component_factory_resolver'; + + +/** + * Represents an instance of an AppModule created via a {@link AppModuleFactory}. + * + * `AppModuleRef` provides access to the AppModule Instance as well other objects related to this + * AppModule Instance. + * @stable + */ +export abstract class AppModuleRef { + /** + * The injector that contains all of the providers of the AppModule. + */ + get injector(): Injector { return unimplemented(); } + + /** + * The ComponentFactoryResolver to get hold of the ComponentFactories + * delcared in the `precompile` property of the module. + */ + get componentFactoryResolver(): ComponentFactoryResolver { return unimplemented(); } + + /** + * The AppModule instance. + */ + get instance(): T { return unimplemented(); } +} + +/** + * @stable + */ +export class AppModuleFactory { + constructor( + private _injectorClass: {new (parentInjector: Injector): AppModuleInjector}, + private _moduleype: ConcreteType) {} + + get moduleType(): ConcreteType { return this._moduleype; } + + create(parentInjector: Injector = null): AppModuleRef { + if (!parentInjector) { + parentInjector = Injector.NULL; + } + var instance = new this._injectorClass(parentInjector); + instance.create(); + return instance; + } +} + +const _UNDEFINED = new Object(); + +export abstract class AppModuleInjector extends CodegenComponentFactoryResolver implements + Injector, + AppModuleRef { + public instance: T; + + constructor(public parent: Injector, factories: ComponentFactory[]) { + super(factories, parent.get(ComponentFactoryResolver, ComponentFactoryResolver.NULL)); + } + + create() { this.instance = this.createInternal(); } + + abstract createInternal(): T; + + get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any { + if (token === Injector || token === ComponentFactoryResolver) { + return this; + } + var result = this.getInternal(token, _UNDEFINED); + return result === _UNDEFINED ? this.parent.get(token, notFoundValue) : result; + } + + abstract getInternal(token: any, notFoundValue: any): any; + + get injector(): Injector { return this; } + + get componentFactoryResolver(): ComponentFactoryResolver { return this; } +} diff --git a/modules/@angular/core/src/linker/compiler.ts b/modules/@angular/core/src/linker/compiler.ts index 1aa89828e6..ed9c71c36e 100644 --- a/modules/@angular/core/src/linker/compiler.ts +++ b/modules/@angular/core/src/linker/compiler.ts @@ -8,7 +8,9 @@ import {BaseException} from '../facade/exceptions'; import {ConcreteType, Type, stringify} from '../facade/lang'; +import {AppModuleMetadata} from '../metadata/app_module'; +import {AppModuleFactory} from './app_module_factory'; import {ComponentFactory} from './component_factory'; @@ -22,7 +24,10 @@ export class Compiler { /** * Loads the template and styles of a component and returns the associated `ComponentFactory`. */ - compileComponentAsync(component: ConcreteType): Promise> { + compileComponentAsync(component: ConcreteType, {moduleDirectives = [], modulePipes = []}: { + moduleDirectives?: ConcreteType[], + modulePipes?: ConcreteType[] + } = {}): Promise> { throw new BaseException( `Runtime compiler is not loaded. Tried to compile ${stringify(component)}`); } @@ -30,16 +35,37 @@ export class Compiler { * Compiles the given component. All templates have to be either inline or compiled via * `compileComponentAsync` before. */ - compileComponentSync(component: ConcreteType): ComponentFactory { + compileComponentSync(component: ConcreteType, {moduleDirectives = [], modulePipes = []}: { + moduleDirectives?: ConcreteType[], + modulePipes?: ConcreteType[] + } = {}): ComponentFactory { throw new BaseException( `Runtime compiler is not loaded. Tried to compile ${stringify(component)}`); } + /** + * Compiles the given App Module. All templates of the components listed in `precompile` + * have to be either inline or compiled before via `compileComponentAsync` / + * `compileAppModuleAsync`. + */ + compileAppModuleSync(moduleType: ConcreteType, metadata: AppModuleMetadata = null): + AppModuleFactory { + throw new BaseException( + `Runtime compiler is not loaded. Tried to compile ${stringify(moduleType)}`); + } + + compileAppModuleAsync(moduleType: ConcreteType, metadata: AppModuleMetadata = null): + Promise> { + throw new BaseException( + `Runtime compiler is not loaded. Tried to compile ${stringify(moduleType)}`); + } + /** * Clears all caches */ clearCache(): void {} + /** - * Clears the cache for the given component. + * Clears the cache for the given component/appModule. */ - clearCacheFor(compType: Type) {} + clearCacheFor(type: Type) {} } diff --git a/modules/@angular/core/src/metadata.ts b/modules/@angular/core/src/metadata.ts index ba57d2c49f..e1c0bc3834 100644 --- a/modules/@angular/core/src/metadata.ts +++ b/modules/@angular/core/src/metadata.ts @@ -14,10 +14,12 @@ import {ChangeDetectionStrategy} from '../src/change_detection/change_detection'; import {AnimationEntryMetadata} from './animation/metadata'; +import {AppModuleMetadata} from './metadata/app_module'; import {AttributeMetadata, ContentChildMetadata, ContentChildrenMetadata, QueryMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata} from './metadata/di'; import {ComponentMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, InputMetadata, OutputMetadata, PipeMetadata} from './metadata/directives'; import {ViewEncapsulation, ViewMetadata} from './metadata/view'; +export {AppModuleMetadata} from './metadata/app_module'; export {AttributeMetadata, ContentChildMetadata, ContentChildrenMetadata, QueryMetadata, ViewChildMetadata, ViewChildrenMetadata, ViewQueryMetadata} from './metadata/di'; export {ComponentMetadata, DirectiveMetadata, HostBindingMetadata, HostListenerMetadata, InputMetadata, OutputMetadata, PipeMetadata} from './metadata/directives'; export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './metadata/lifecycle_hooks'; @@ -83,6 +85,16 @@ export interface ViewDecorator extends TypeDecorator { }): ViewDecorator; } +/** + * Interface for the {@link AppModuleMetadata} decorator function. + * + * See {@link AppModuleMetadataFactory}. + * + * @stable + */ +export interface AppModuleDecorator extends TypeDecorator {} + + /** * {@link DirectiveMetadata} factory for creating annotations, decorators or DSL. * @@ -477,6 +489,28 @@ export interface HostListenerMetadataFactory { new (eventName: string, args?: string[]): any; } +/** + * {@link AppModuleMetadata} factory for creating annotations, decorators or DSL. + * + * @stable + */ +export interface AppModuleMetadataFactory { + (obj: { + providers?: any[], + directives?: Array, + pipes?: Array, + precompile?: Array, + modules?: Array, + }): AppModuleDecorator; + new (obj: { + providers?: any[], + directives?: Array, + pipes?: Array, + precompile?: Array, + modules?: Array, + }): AppModuleMetadata; +} + // TODO(alexeagle): remove the duplication of this doc. It is copied from ComponentMetadata. /** * Declare reusable UI building blocks for an application. @@ -1499,3 +1533,11 @@ export var HostBinding: HostBindingMetadataFactory = makePropDecorator(HostBindi * @Annotation */ export var HostListener: HostListenerMetadataFactory = makePropDecorator(HostListenerMetadata); + +/** + * Declares an app module. + * @stable + * @Annotation + */ +export var AppModule: AppModuleMetadataFactory = + makeDecorator(AppModuleMetadata); diff --git a/modules/@angular/core/src/metadata/app_module.ts b/modules/@angular/core/src/metadata/app_module.ts new file mode 100644 index 0000000000..c707f67558 --- /dev/null +++ b/modules/@angular/core/src/metadata/app_module.ts @@ -0,0 +1,113 @@ +/** + * @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 {InjectableMetadata} from '../di/metadata'; +import {Type} from '../facade/lang'; + +/** + * Declares an Application Module. + * @stable + */ +export class AppModuleMetadata extends InjectableMetadata { + /** + * Defines the set of injectable objects that are available in the injector + * of this module. + * + * ## Simple Example + * + * Here is an example of a class that can be injected: + * + * ``` + * class Greeter { + * greet(name:string) { + * return 'Hello ' + name + '!'; + * } + * } + * + * @AppModule({ + * providers: [ + * Greeter + * ] + * }) + * class HelloWorld { + * greeter:Greeter; + * + * constructor(greeter:Greeter) { + * this.greeter = greeter; + * } + * } + * ``` + */ + get providers(): any[] { return this._providers; } + private _providers: any[]; + + + /** + * Specifies a list of directives that can be used within the template + * of any component that is part of this application module. + * + * ### Example + * + * ```javascript + * @AppModule({ + * directives: [NgFor] + * }) + * class MyAppModule { + * } + * ``` + */ + directives: Array; + + /** + * Specifies a list of pipes that can be used within the template + * of any component that is part of this application module. + * + * ### Example + * + * ```javascript + * @AppModule({ + * pipes: [SomePipe] + * }) + * class MyAppModule { + * } + * ``` + */ + pipes: Array; + + /** + * Defines the components that should be precompiled as well when + * this component is defined. For each components listed here, + * Angular will create a {@link ComponentFactory ComponentFactory} and store it in the + * {@link ComponentFactoryResolver ComponentFactoryResolver}. + */ + precompile: Array; + + /** + * Defines modules that should be included into this module. + * The providers / directives / pipes / precompile entries will be added + * to this module. + * Just like the main module, the modules listed here are also eagerly + * created and accessible via DI. + */ + modules: Array; + + constructor({providers, directives, pipes, precompile, modules}: { + providers?: any[], + directives?: Array, + pipes?: Array, + precompile?: Array, + modules?: Array + } = {}) { + super(); + this._providers = providers; + this.directives = directives; + this.pipes = pipes; + this.precompile = precompile; + this.modules = modules; + } +} diff --git a/modules/@angular/core/test/di/injector_spec.ts b/modules/@angular/core/test/di/injector_spec.ts new file mode 100644 index 0000000000..04e242f401 --- /dev/null +++ b/modules/@angular/core/test/di/injector_spec.ts @@ -0,0 +1,27 @@ +/** + * @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 {Injector} from '@angular/core'; +import {beforeEach, ddescribe, describe, expect, iit, it} from '@angular/core/testing/testing_internal'; + +import {IS_DART} from '../../src/facade/lang'; + +export function main() { + describe('Injector.NULL', () => { + it('should throw if no arg is given', () => {expect(() => Injector.NULL.get('someToken')) + .toThrowError('No provider for someToken!')}); + + it('should throw if THROW_IF_NOT_FOUND is given', + () => {expect(() => Injector.NULL.get('someToken', Injector.THROW_IF_NOT_FOUND)) + .toThrowError('No provider for someToken!')}); + + it('should return the default value', + () => { expect(Injector.NULL.get('someToken', 'notFound')).toEqual('notFound'); }); + }); +} diff --git a/modules/@angular/core/test/linker/app_module_integration_spec.ts b/modules/@angular/core/test/linker/app_module_integration_spec.ts new file mode 100644 index 0000000000..d594e039a4 --- /dev/null +++ b/modules/@angular/core/test/linker/app_module_integration_spec.ts @@ -0,0 +1,478 @@ +import {LowerCasePipe, NgIf} from '@angular/common'; +import {CompilerConfig} from '@angular/compiler'; +import {AppModule, AppModuleMetadata, Compiler, Component, ComponentFactoryResolver, ComponentRef, DebugElement, Host, Inject, Injectable, Injector, OpaqueToken, Optional, Provider, SelfMetadata, SkipSelf, SkipSelfMetadata, forwardRef, getDebugNode, provide} from '@angular/core'; +import {ComponentFixture} from '@angular/core/testing'; +import {beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal'; + +import {BaseException} from '../../src/facade/exceptions'; +import {ConcreteType, IS_DART, Type, stringify} from '../../src/facade/lang'; + +class Engine {} + +class BrokenEngine { + constructor() { throw new BaseException('Broken Engine'); } +} + +class DashboardSoftware {} + +@Injectable() +class Dashboard { + constructor(software: DashboardSoftware) {} +} + +class TurboEngine extends Engine {} + +@Injectable() +class Car { + engine: Engine; + constructor(engine: Engine) { this.engine = engine; } +} + +@Injectable() +class CarWithOptionalEngine { + engine: Engine; + constructor(@Optional() engine: Engine) { this.engine = engine; } +} + +@Injectable() +class CarWithDashboard { + engine: Engine; + dashboard: Dashboard; + constructor(engine: Engine, dashboard: Dashboard) { + this.engine = engine; + this.dashboard = dashboard; + } +} + +@Injectable() +class SportsCar extends Car { + engine: Engine; + constructor(engine: Engine) { super(engine); } +} + +@Injectable() +class CarWithInject { + engine: Engine; + constructor(@Inject(TurboEngine) engine: Engine) { this.engine = engine; } +} + +@Injectable() +class CyclicEngine { + constructor(car: Car) {} +} + +class NoAnnotations { + constructor(secretDependency: any) {} +} + +function factoryFn(a: any) {} + +@Injectable() +class SomeService { +} + +@AppModule({}) +class SomeModule { +} + +@AppModule({providers: [{provide: 'someToken', useValue: 'someValue'}]}) +class ModuleWithProvider { +} + +@Component({selector: 'comp', template: ''}) +class SomeComp { +} + +@AppModule({precompile: [SomeComp]}) +class ModuleWithPrecompile { +} + +@Component({ + selector: 'comp', + template: `
` +}) +class CompUsingModuleDirectiveAndPipe { +} + +@Component( + {selector: 'parent', template: ``, directives: [CompUsingModuleDirectiveAndPipe]}) +class ParentCompUsingModuleDirectiveAndPipe { +} + +@AppModule( + {directives: [NgIf], pipes: [LowerCasePipe], precompile: [CompUsingModuleDirectiveAndPipe]}) +class ModuleWithDirectivesAndPipes { +} + +export function main() { + if (IS_DART) { + declareTests({useJit: false}); + } else { + describe('jit', () => { declareTests({useJit: true}); }); + + describe('no jit', () => { declareTests({useJit: false}); }); + } +} + +function declareTests({useJit}: {useJit: boolean}) { + describe('AppModule', () => { + var compiler: Compiler; + var injector: Injector; + + beforeEach(inject([Compiler, Injector], (_compiler: Compiler, _injector: Injector) => { + compiler = _compiler; + injector = _injector; + })); + + beforeEachProviders(() => [{ + provide: CompilerConfig, + useValue: new CompilerConfig({genDebugInfo: true, useJit: useJit}) + }]); + + describe('precompile', function() { + it('should resolve ComponentFactories', () => { + let appModule = compiler.compileAppModuleSync(ModuleWithPrecompile).create(); + expect(appModule.componentFactoryResolver.resolveComponentFactory(SomeComp).componentType) + .toBe(SomeComp); + expect(appModule.injector.get(ComponentFactoryResolver) + .resolveComponentFactory(SomeComp) + .componentType) + .toBe(SomeComp); + }); + + it('should resolve ComponentFactories for nested modules', () => { + let appModule = + compiler + .compileAppModuleSync( + SomeModule, new AppModuleMetadata({modules: [ModuleWithPrecompile]})) + .create(); + expect(appModule.componentFactoryResolver.resolveComponentFactory(SomeComp).componentType) + .toBe(SomeComp); + expect(appModule.injector.get(ComponentFactoryResolver) + .resolveComponentFactory(SomeComp) + .componentType) + .toBe(SomeComp); + }); + }); + + describe('directives and pipes', () => { + function createComp( + compType: ConcreteType, moduleType: ConcreteType, + moduleMeta: AppModuleMetadata = null): ComponentFixture { + let appModule = compiler.compileAppModuleSync(moduleType, moduleMeta).create(); + var cf = appModule.componentFactoryResolver.resolveComponentFactory(compType); + return new ComponentFixture(cf.create(injector), null, false); + } + + function checkNgIfAndLowerCasePipe(compFixture: ComponentFixture, el: DebugElement) { + // Test that ngIf works + expect(el.children.length).toBe(1); + compFixture.detectChanges(); + expect(el.children.length).toBe(2); + + // Test that lowercase pipe works + expect(el.children[0].properties['title']).toBe('hello'); + } + + it('should support module directives and pipes', () => { + let compFixture = createComp(CompUsingModuleDirectiveAndPipe, ModuleWithDirectivesAndPipes); + checkNgIfAndLowerCasePipe(compFixture, compFixture.debugElement); + }); + + it('should support module directives and pipes for nested modules', () => { + let compFixture = createComp( + CompUsingModuleDirectiveAndPipe, SomeModule, + new AppModuleMetadata({modules: [ModuleWithDirectivesAndPipes]})); + checkNgIfAndLowerCasePipe(compFixture, compFixture.debugElement); + }); + + it('should support module directives and pipes in nested components', () => { + let compFixture = + createComp(ParentCompUsingModuleDirectiveAndPipe, SomeModule, new AppModuleMetadata({ + directives: [NgIf], + pipes: [LowerCasePipe], + precompile: [ParentCompUsingModuleDirectiveAndPipe] + })); + checkNgIfAndLowerCasePipe(compFixture, compFixture.debugElement.children[0]); + }); + }); + + describe('providers', function() { + function createInjector(providers: any[], parent: Injector = null): Injector { + return compiler + .compileAppModuleSync(SomeModule, new AppModuleMetadata({providers: providers})) + .create(parent) + .injector; + } + + it('should provide the module', + () => { expect(createInjector([]).get(SomeModule)).toBeAnInstanceOf(SomeModule); }); + + it('should instantiate a class without dependencies', () => { + var injector = createInjector([Engine]); + var engine = injector.get(Engine); + + expect(engine).toBeAnInstanceOf(Engine); + }); + + it('should resolve dependencies based on type information', () => { + var injector = createInjector([Engine, Car]); + var car = injector.get(Car); + + expect(car).toBeAnInstanceOf(Car); + expect(car.engine).toBeAnInstanceOf(Engine); + }); + + it('should resolve dependencies based on @Inject annotation', () => { + var injector = createInjector([TurboEngine, Engine, CarWithInject]); + var car = injector.get(CarWithInject); + + expect(car).toBeAnInstanceOf(CarWithInject); + expect(car.engine).toBeAnInstanceOf(TurboEngine); + }); + + it('should throw when no type and not @Inject (class case)', () => { + expect(() => createInjector([NoAnnotations])) + .toThrowError('Can\'t resolve all parameters for NoAnnotations: (?).'); + }); + + it('should throw when no type and not @Inject (factory case)', () => { + expect(() => createInjector([provide('someToken', {useFactory: factoryFn})])) + .toThrowError('Can\'t resolve all parameters for factoryFn: (?).'); + }); + + it('should cache instances', () => { + var injector = createInjector([Engine]); + + var e1 = injector.get(Engine); + var e2 = injector.get(Engine); + + expect(e1).toBe(e2); + }); + + it('should provide to a value', () => { + var injector = createInjector([provide(Engine, {useValue: 'fake engine'})]); + + var engine = injector.get(Engine); + expect(engine).toEqual('fake engine'); + }); + + it('should provide to a factory', () => { + function sportsCarFactory(e: Engine) { return new SportsCar(e); } + + var injector = + createInjector([Engine, provide(Car, {useFactory: sportsCarFactory, deps: [Engine]})]); + + var car = injector.get(Car); + expect(car).toBeAnInstanceOf(SportsCar); + expect(car.engine).toBeAnInstanceOf(Engine); + }); + + it('should supporting provider to null', () => { + var injector = createInjector([provide(Engine, {useValue: null})]); + var engine = injector.get(Engine); + expect(engine).toBeNull(); + }); + + it('should provide to an alias', () => { + var injector = createInjector([ + Engine, provide(SportsCar, {useClass: SportsCar}), + provide(Car, {useExisting: SportsCar}) + ]); + + var car = injector.get(Car); + var sportsCar = injector.get(SportsCar); + expect(car).toBeAnInstanceOf(SportsCar); + expect(car).toBe(sportsCar); + }); + + it('should support multiProviders', () => { + var injector = createInjector([ + Engine, provide(Car, {useClass: SportsCar, multi: true}), + provide(Car, {useClass: CarWithOptionalEngine, multi: true}) + ]); + + var cars = injector.get(Car); + expect(cars.length).toEqual(2); + expect(cars[0]).toBeAnInstanceOf(SportsCar); + expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine); + }); + + it('should support multiProviders that are created using useExisting', () => { + var injector = createInjector( + [Engine, SportsCar, provide(Car, {useExisting: SportsCar, multi: true})]); + + var cars = injector.get(Car); + expect(cars.length).toEqual(1); + expect(cars[0]).toBe(injector.get(SportsCar)); + }); + + it('should throw when the aliased provider does not exist', () => { + var injector = createInjector([provide('car', {useExisting: SportsCar})]); + var e = `No provider for ${stringify(SportsCar)}!`; + expect(() => injector.get('car')).toThrowError(e); + }); + + it('should handle forwardRef in useExisting', () => { + var injector = createInjector([ + provide('originalEngine', {useClass: forwardRef(() => Engine)}), + provide('aliasedEngine', {useExisting: forwardRef(() => 'originalEngine')}) + ]); + expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine); + }); + + it('should support overriding factory dependencies', () => { + var injector = createInjector( + [Engine, provide(Car, {useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]})]); + + var car = injector.get(Car); + expect(car).toBeAnInstanceOf(SportsCar); + expect(car.engine).toBeAnInstanceOf(Engine); + }); + + it('should support optional dependencies', () => { + var injector = createInjector([CarWithOptionalEngine]); + + var car = injector.get(CarWithOptionalEngine); + expect(car.engine).toEqual(null); + }); + + it('should flatten passed-in providers', () => { + var injector = createInjector([[[Engine, Car]]]); + + var car = injector.get(Car); + expect(car).toBeAnInstanceOf(Car); + }); + + it('should use the last provider when there are multiple providers for same token', () => { + var injector = createInjector( + [provide(Engine, {useClass: Engine}), provide(Engine, {useClass: TurboEngine})]); + + expect(injector.get(Engine)).toBeAnInstanceOf(TurboEngine); + }); + + it('should use non-type tokens', () => { + var injector = createInjector([provide('token', {useValue: 'value'})]); + + expect(injector.get('token')).toEqual('value'); + }); + + it('should throw when given invalid providers', () => { + expect(() => createInjector(['blah'])) + .toThrowError( + 'Invalid provider - only instances of Provider and Type are allowed, got: blah'); + }); + + it('should provide itself', () => { + var parent = createInjector([]); + var child = createInjector([], parent); + + expect(child.get(Injector)).toBe(child); + }); + + it('should throw when no provider defined', () => { + var injector = createInjector([]); + expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!'); + }); + + it('should throw when trying to instantiate a cyclic dependency', () => { + expect(() => createInjector([Car, provide(Engine, {useClass: CyclicEngine})])) + .toThrowError(/Cannot instantiate cyclic dependency! Car/g); + }); + + it('should support null values', () => { + var injector = createInjector([provide('null', {useValue: null})]); + expect(injector.get('null')).toBe(null); + }); + + + describe('child', () => { + it('should load instances from parent injector', () => { + var parent = createInjector([Engine]); + var child = createInjector([], parent); + + var engineFromParent = parent.get(Engine); + var engineFromChild = child.get(Engine); + + expect(engineFromChild).toBe(engineFromParent); + }); + + it('should not use the child providers when resolving the dependencies of a parent provider', + () => { + var parent = createInjector([Car, Engine]); + var child = createInjector([provide(Engine, {useClass: TurboEngine})], parent); + + var carFromChild = child.get(Car); + expect(carFromChild.engine).toBeAnInstanceOf(Engine); + }); + + it('should create new instance in a child injector', () => { + var parent = createInjector([Engine]); + var child = createInjector([provide(Engine, {useClass: TurboEngine})], parent); + + var engineFromParent = parent.get(Engine); + var engineFromChild = child.get(Engine); + + expect(engineFromParent).not.toBe(engineFromChild); + expect(engineFromChild).toBeAnInstanceOf(TurboEngine); + }); + + }); + + describe('depedency resolution', () => { + describe('@Self()', () => { + it('should return a dependency from self', () => { + var inj = createInjector([ + Engine, + provide( + Car, + {useFactory: (e: Engine) => new Car(e), deps: [[Engine, new SelfMetadata()]]}) + ]); + + expect(inj.get(Car)).toBeAnInstanceOf(Car); + }); + + it('should throw when not requested provider on self', () => { + expect(() => createInjector([provide(Car, { + useFactory: (e: Engine) => new Car(e), + deps: [[Engine, new SelfMetadata()]] + })])) + .toThrowError(/No provider for Engine/g); + }); + }); + + describe('default', () => { + it('should not skip self', () => { + var parent = createInjector([Engine]); + var child = createInjector( + [ + provide(Engine, {useClass: TurboEngine}), + provide(Car, {useFactory: (e: Engine) => new Car(e), deps: [Engine]}) + ], + parent); + + expect(child.get(Car).engine).toBeAnInstanceOf(TurboEngine); + }); + }); + }); + + describe('nested modules', () => { + it('should merge the providers of nested modules', () => { + var injector = + compiler + .compileAppModuleSync(SomeModule, new AppModuleMetadata({ + providers: [{provide: 'a', useValue: 'aValue'}], + modules: [ModuleWithProvider] + })) + .create() + .injector; + expect(injector.get(SomeModule)).toBeAnInstanceOf(SomeModule); + expect(injector.get(ModuleWithProvider)).toBeAnInstanceOf(ModuleWithProvider); + expect(injector.get('a')).toBe('aValue'); + expect(injector.get('someToken')).toBe('someValue'); + }); + }); + + }); + }); +} diff --git a/tools/cjs-jasmine/tsconfig-output_emitter_codegen.json b/tools/cjs-jasmine/tsconfig-output_emitter_codegen.json index 6ed263a98f..8f942e1157 100644 --- a/tools/cjs-jasmine/tsconfig-output_emitter_codegen.json +++ b/tools/cjs-jasmine/tsconfig-output_emitter_codegen.json @@ -21,8 +21,6 @@ "files": [ "../../node_modules/@types/node/index.d.ts", "../../dist/all/@angular/compiler/test/output/output_emitter_codegen_typed.ts", - "../../dist/all/@angular/compiler/test/output/output_emitter_codegen_untyped.ts", - "../../dist/all/@angular/compiler/test/offline_compiler_codegen_untyped.ts", - "../../dist/all/@angular/compiler/test/offline_compiler_codegen_typed.ts" + "../../dist/all/@angular/compiler/test/output/output_emitter_codegen_untyped.ts" ] } diff --git a/tools/public_api_guard/core/index.d.ts b/tools/public_api_guard/core/index.d.ts index 895f05ce45..f383c209a0 100644 --- a/tools/public_api_guard/core/index.d.ts +++ b/tools/public_api_guard/core/index.d.ts @@ -138,6 +138,63 @@ export declare abstract class ApplicationRef { abstract waitForAsyncInitializers(): Promise; } +/** @stable */ +export declare var AppModule: AppModuleMetadataFactory; + +/** @stable */ +export interface AppModuleDecorator extends TypeDecorator { +} + +/** @stable */ +export declare class AppModuleFactory { + moduleType: ConcreteType; + constructor(_injectorClass: { + new (parentInjector: Injector): AppModuleInjector; + }, _moduleype: ConcreteType); + create(parentInjector?: Injector): AppModuleRef; +} + +/** @stable */ +export declare class AppModuleMetadata extends InjectableMetadata { + directives: Array; + modules: Array; + pipes: Array; + precompile: Array; + providers: any[]; + constructor({providers, directives, pipes, precompile, modules}?: { + providers?: any[]; + directives?: Array; + pipes?: Array; + precompile?: Array; + modules?: Array; + }); +} + +/** @stable */ +export interface AppModuleMetadataFactory { + (obj: { + providers?: any[]; + directives?: Array; + pipes?: Array; + precompile?: Array; + modules?: Array; + }): AppModuleDecorator; + new (obj: { + providers?: any[]; + directives?: Array; + pipes?: Array; + precompile?: Array; + modules?: Array; + }): AppModuleMetadata; +} + +/** @stable */ +export declare abstract class AppModuleRef { + componentFactoryResolver: ComponentFactoryResolver; + injector: Injector; + instance: T; +} + /** @experimental */ export declare function asNativeElements(debugEls: DebugElement[]): any; @@ -229,9 +286,17 @@ export declare class CollectionChangeRecord { /** @stable */ export declare class Compiler { clearCache(): void; - clearCacheFor(compType: Type): void; - compileComponentAsync(component: ConcreteType): Promise>; - compileComponentSync(component: ConcreteType): ComponentFactory; + clearCacheFor(type: Type): void; + compileAppModuleAsync(moduleType: ConcreteType, metadata?: AppModuleMetadata): Promise>; + compileAppModuleSync(moduleType: ConcreteType, metadata?: AppModuleMetadata): AppModuleFactory; + compileComponentAsync(component: ConcreteType, {moduleDirectives, modulePipes}?: { + moduleDirectives?: ConcreteType[]; + modulePipes?: ConcreteType[]; + }): Promise>; + compileComponentSync(component: ConcreteType, {moduleDirectives, modulePipes}?: { + moduleDirectives?: ConcreteType[]; + modulePipes?: ConcreteType[]; + }): ComponentFactory; } /** @stable */ @@ -733,6 +798,7 @@ export interface InjectMetadataFactory { /** @stable */ export declare abstract class Injector { get(token: any, notFoundValue?: any): any; + static NULL: Injector; static THROW_IF_NOT_FOUND: Object; } diff --git a/tools/tsc-watch/index.ts b/tools/tsc-watch/index.ts index 3a651b3145..8011b498f0 100644 --- a/tools/tsc-watch/index.ts +++ b/tools/tsc-watch/index.ts @@ -6,10 +6,8 @@ import {TSC, TscWatch, reportError} from './tsc_watch'; export * from './tsc_watch'; import 'reflect-metadata'; -const OFFLINE_COMPILE = [ - 'output/output_emitter_codegen_untyped', 'output/output_emitter_codegen_typed', - 'offline_compiler_codegen_untyped', 'offline_compiler_codegen_typed' -]; +const OFFLINE_COMPILE = + ['output/output_emitter_codegen_untyped', 'output/output_emitter_codegen_typed']; function processOutputEmitterCodeGen(): Promise { return new Promise((resolve, reject) => {