diff --git a/packages/compiler/src/compiler_facade_interface.ts b/packages/compiler/src/compiler_facade_interface.ts index 10315fc72c..f069c587cb 100644 --- a/packages/compiler/src/compiler_facade_interface.ts +++ b/packages/compiler/src/compiler_facade_interface.ts @@ -41,10 +41,15 @@ export interface CompilerFacade { createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan; R3ResolvedDependencyType: typeof R3ResolvedDependencyType; + ResourceLoader: {new (): ResourceLoader}; } export interface CoreEnvironment { [name: string]: Function; } +export type ResourceLoader = { + get(url: string): Promise| string; +}; + export type StringMap = { [key: string]: string; }; diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index 15483a5f65..c805b5d2c4 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -23,10 +23,12 @@ import {R3Reference} from './render3/util'; import {R3DirectiveMetadata, R3QueryMetadata} from './render3/view/api'; import {ParsedHostBindings, compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings, verifyHostBindings} from './render3/view/compiler'; import {makeBindingParser, parseTemplate} from './render3/view/template'; +import {ResourceLoader} from './resource_loader'; import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry'; export class CompilerFacadeImpl implements CompilerFacade { R3ResolvedDependencyType = R3ResolvedDependencyType as any; + ResourceLoader = ResourceLoader; private elementSchemaRegistry = new DomElementSchemaRegistry(); constructor(private jitEvaluator = new JitEvaluator()) {} diff --git a/packages/compiler/test/compiler_facade_interface_spec.ts b/packages/compiler/test/compiler_facade_interface_spec.ts index cb40b6711c..4f8b319e8e 100644 --- a/packages/compiler/test/compiler_facade_interface_spec.ts +++ b/packages/compiler/test/compiler_facade_interface_spec.ts @@ -36,6 +36,9 @@ const compilerCompilerFacade: compiler.CompilerFacade = null !as core.CompilerFa const coreCoreEnvironment: core.CoreEnvironment = null !as compiler.CoreEnvironment; const compilerCoreEnvironment: compiler.CoreEnvironment = null !as core.CoreEnvironment; +const coreResourceLoader: core.ResourceLoader = null !as compiler.ResourceLoader; +const compilerResourceLoader: compiler.ResourceLoader = null !as core.ResourceLoader; + const coreStringMap: core.StringMap = null !as compiler.StringMap; const compilerStringMap: compiler.StringMap = null !as core.StringMap; diff --git a/packages/core/src/application_ref.ts b/packages/core/src/application_ref.ts index 8669e889fc..0efd77d2af 100644 --- a/packages/core/src/application_ref.ts +++ b/packages/core/src/application_ref.ts @@ -11,15 +11,17 @@ import {share} from 'rxjs/operators'; import {ApplicationInitStatus} from './application_init'; import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens'; +import {getCompilerFacade} from './compiler/compiler_facade'; import {Console} from './console'; import {Injectable, InjectionToken, Injector, StaticProvider} from './di'; import {ErrorHandler} from './error_handler'; import {Type} from './interface/type'; -import {CompilerFactory, CompilerOptions} from './linker/compiler'; +import {COMPILER_OPTIONS, CompilerFactory, CompilerOptions} from './linker/compiler'; import {ComponentFactory, ComponentRef} from './linker/component_factory'; import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from './linker/component_factory_resolver'; import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from './linker/ng_module_factory'; import {InternalViewRef, ViewRef} from './linker/view_ref'; +import {isComponentResourceResolutionQueueEmpty, resolveComponentResources} from './metadata/resource_loading'; import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile'; import {assertNgModuleType} from './render3/assert'; import {ComponentFactory as R3ComponentFactory} from './render3/component_ref'; @@ -49,7 +51,30 @@ export function compileNgModuleFactory__POST_R3__( injector: Injector, options: CompilerOptions, moduleType: Type): Promise> { ngDevMode && assertNgModuleType(moduleType); - return Promise.resolve(new R3NgModuleFactory(moduleType)); + const moduleFactory = new R3NgModuleFactory(moduleType); + + if (isComponentResourceResolutionQueueEmpty()) { + return Promise.resolve(moduleFactory); + } + + const compilerOptions = injector.get(COMPILER_OPTIONS, []).concat(options); + const compilerProviders = _mergeArrays(compilerOptions.map(o => o.providers !)); + + // In case there are no compiler providers, we just return the module factory as + // there won't be any resource loader. This can happen with Ivy, because AOT compiled + // modules can be still passed through "bootstrapModule". In that case we shouldn't + // unnecessarily require the JIT compiler. + if (compilerProviders.length === 0) { + return Promise.resolve(moduleFactory); + } + + const compiler = getCompilerFacade(); + const compilerInjector = Injector.create({providers: compilerProviders}); + const resourceLoader = compilerInjector.get(compiler.ResourceLoader); + // The resource loader can also return a string while the "resolveComponentResources" + // always expects a promise. Therefore we need to wrap the returned value in a promise. + return resolveComponentResources(url => Promise.resolve(resourceLoader.get(url))) + .then(() => moduleFactory); } let isBoundToModule: (cf: ComponentFactory) => boolean = isBoundToModule__PRE_R3__; @@ -671,3 +696,9 @@ function remove(list: T[], el: T): void { list.splice(index, 1); } } + +function _mergeArrays(parts: any[][]): any[] { + const result: any[] = []; + parts.forEach((part) => part && result.push(...part)); + return result; +} diff --git a/packages/core/src/compiler/compiler_facade_interface.ts b/packages/core/src/compiler/compiler_facade_interface.ts index a5a209845b..954cc0ad89 100644 --- a/packages/core/src/compiler/compiler_facade_interface.ts +++ b/packages/core/src/compiler/compiler_facade_interface.ts @@ -41,10 +41,15 @@ export interface CompilerFacade { createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan; R3ResolvedDependencyType: typeof R3ResolvedDependencyType; + ResourceLoader: {new (): ResourceLoader}; } export interface CoreEnvironment { [name: string]: Function; } +export type ResourceLoader = { + get(url: string): Promise| string; +}; + export type StringMap = { [key: string]: string; }; diff --git a/packages/core/src/metadata/resource_loading.ts b/packages/core/src/metadata/resource_loading.ts index 633e0ac871..fa7657752c 100644 --- a/packages/core/src/metadata/resource_loading.ts +++ b/packages/core/src/metadata/resource_loading.ts @@ -99,6 +99,10 @@ export function clearResolutionOfComponentResourcesQueue() { componentResourceResolutionQueue.clear(); } +export function isComponentResourceResolutionQueueEmpty() { + return componentResourceResolutionQueue.size === 0; +} + function unwrapResponse(response: string | {text(): Promise}): string|Promise { return typeof response == 'string' ? response : response.text(); } diff --git a/packages/core/test/application_ref_spec.ts b/packages/core/test/application_ref_spec.ts index 48f5544863..39034d1789 100644 --- a/packages/core/test/application_ref_spec.ts +++ b/packages/core/test/application_ref_spec.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {ResourceLoader} from '@angular/compiler'; import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, Compiler, CompilerFactory, Component, InjectionToken, NgModule, NgZone, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core'; import {ApplicationRef} from '@angular/core/src/application_ref'; import {ErrorHandler} from '@angular/core/src/error_handler'; @@ -41,7 +42,8 @@ class SomeComponent { getDOM().appendChild(doc.body, rootEl); } - type CreateModuleOptions = {providers?: any[], ngDoBootstrap?: any, bootstrap?: any[]}; + type CreateModuleOptions = + {providers?: any[], ngDoBootstrap?: any, bootstrap?: any[], component?: Type}; function createModule(providers?: any[]): Type; function createModule(options: CreateModuleOptions): Type; @@ -62,8 +64,8 @@ class SomeComponent { @NgModule({ providers: [{provide: ErrorHandler, useValue: errorHandler}, options.providers || []], imports: [platformModule], - declarations: [SomeComponent], - entryComponents: [SomeComponent], + declarations: [options.component || SomeComponent], + entryComponents: [options.component || SomeComponent], bootstrap: options.bootstrap || [] }) class MyModule { @@ -303,6 +305,27 @@ class SomeComponent { expect(ngZone instanceof NoopNgZone).toBe(true); }); })); + + it('should resolve component resources when creating module factory', async() => { + @Component({ + selector: 'with-templates-app', + templateUrl: '/test-template.html', + }) + class WithTemplateUrlComponent { + } + + const loadResourceSpy = jasmine.createSpy('load resource').and.returnValue('fakeContent'); + const testModule = createModule({component: WithTemplateUrlComponent}); + + await defaultPlatform.bootstrapModule(testModule, { + providers: [ + {provide: ResourceLoader, useValue: {get: loadResourceSpy}}, + ] + }); + + expect(loadResourceSpy).toHaveBeenCalledTimes(1); + expect(loadResourceSpy).toHaveBeenCalledWith('/test-template.html'); + }); }); describe('bootstrapModuleFactory', () => { diff --git a/packages/core/test/metadata/resource_loading_spec.ts b/packages/core/test/metadata/resource_loading_spec.ts index 49d6ff5b6d..f6202e6e1b 100644 --- a/packages/core/test/metadata/resource_loading_spec.ts +++ b/packages/core/test/metadata/resource_loading_spec.ts @@ -7,13 +7,14 @@ */ import {Component} from '../../src/core'; -import {clearResolutionOfComponentResourcesQueue, resolveComponentResources} from '../../src/metadata/resource_loading'; +import {clearResolutionOfComponentResourcesQueue, isComponentResourceResolutionQueueEmpty, resolveComponentResources} from '../../src/metadata/resource_loading'; import {ComponentType} from '../../src/render3/interfaces/definition'; import {compileComponent} from '../../src/render3/jit/directive'; describe('resource_loading', () => { + afterEach(clearResolutionOfComponentResourcesQueue); + describe('error handling', () => { - afterEach(clearResolutionOfComponentResourcesQueue); it('should throw an error when compiling component that has unresolved templateUrl', () => { const MyComponent: ComponentType = (class MyComponent{}) as any; compileComponent(MyComponent, {templateUrl: 'someUrl'}); @@ -111,6 +112,16 @@ Did you run and wait for 'resolveComponentResources()'?`.trim()); expect(metadata.styles).toEqual(['existing', 'first', 'second']); }); + it('should not add components without external resources to resolution queue', () => { + const MyComponent: ComponentType = (class MyComponent{}) as any; + const MyComponent2: ComponentType = (class MyComponent{}) as any; + + compileComponent(MyComponent, {template: ''}); + expect(isComponentResourceResolutionQueueEmpty()).toBe(true); + + compileComponent(MyComponent2, {templateUrl: 'test://template'}); + expect(isComponentResourceResolutionQueueEmpty()).toBe(false); + }); }); describe('fetch', () => {