Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions aio/tools/examples/run-example-e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ const IGNORED_EXAMPLES = [
const fixmeIvyExamples = [
// fixmeIvy('unknown') app fails at runtime due to missing external service (goog is undefined)
'i18n',
// fixmeIvy('unknown') JIT app fails with external resources not loaded.
'upgrade-phonecat-2-hybrid',
// fixmeIvy('unknown') JIT app fails with external resources not loaded.
'upgrade-phonecat-3-final',
];

if (argv.ivy) {
Expand Down
5 changes: 5 additions & 0 deletions packages/compiler/src/compiler_facade_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>| string;
};

export type StringMap = {
[key: string]: string;
};
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler/src/jit_compiler_facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {}
Expand Down
3 changes: 3 additions & 0 deletions packages/compiler/test/compiler_facade_interface_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
35 changes: 33 additions & 2 deletions packages/core/src/application_ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -49,7 +51,30 @@ export function compileNgModuleFactory__POST_R3__<M>(
injector: Injector, options: CompilerOptions,
moduleType: Type<M>): Promise<NgModuleFactory<M>> {
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);
Comment thread
devversion marked this conversation as resolved.
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: <C>(cf: ComponentFactory<C>) => boolean = isBoundToModule__PRE_R3__;
Expand Down Expand Up @@ -671,3 +696,9 @@ function remove<T>(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;
}
5 changes: 5 additions & 0 deletions packages/core/src/compiler/compiler_facade_interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>| string;
};

export type StringMap = {
[key: string]: string;
};
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/metadata/resource_loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export function clearResolutionOfComponentResourcesQueue() {
componentResourceResolutionQueue.clear();
}

export function isComponentResourceResolutionQueueEmpty() {
return componentResourceResolutionQueue.size === 0;
}

function unwrapResponse(response: string | {text(): Promise<string>}): string|Promise<string> {
return typeof response == 'string' ? response : response.text();
}
29 changes: 26 additions & 3 deletions packages/core/test/application_ref_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<any>};

function createModule(providers?: any[]): Type<any>;
function createModule(options: CreateModuleOptions): Type<any>;
Expand All @@ -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 {
Expand Down Expand Up @@ -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', () => {
Expand Down
15 changes: 13 additions & 2 deletions packages/core/test/metadata/resource_loading_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any> = (class MyComponent{}) as any;
compileComponent(MyComponent, {templateUrl: 'someUrl'});
Expand Down Expand Up @@ -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<any> = (class MyComponent{}) as any;
const MyComponent2: ComponentType<any> = (class MyComponent{}) as any;

compileComponent(MyComponent, {template: ''});
expect(isComponentResourceResolutionQueueEmpty()).toBe(true);

compileComponent(MyComponent2, {templateUrl: 'test://template'});
expect(isComponentResourceResolutionQueueEmpty()).toBe(false);
});
});

describe('fetch', () => {
Expand Down