feat(ivy): Support resource resolution in JIT mode. (#24637)
Used to resolve resource URLs on `@Component` when used with JIT compilation. ``` @Component({ selector: 'my-comp', templateUrl: 'my-comp.html', // This requires asynchronous resolution }) class MyComponnent{ } // Calling `renderComponent` will fail because `MyComponent`'s `@Compenent.templateUrl` // needs to be resolved because `renderComponent` is synchronous process. // renderComponent(MyComponent); // Calling `resolveComponentResources` will resolve `@Compenent.templateUrl` into // `@Compenent.template`, which would allow `renderComponent` to proceed in synchronous manner. // Use browser's `fetch` function as the default resource resolution strategy. resolveComponentResources(fetch).then(() => { // After resolution all URLs have been converted into strings. renderComponent(MyComponent); }); ``` PR Close #24637
This commit is contained in:
136
packages/core/test/metadata/resource_loading_spec.ts
Normal file
136
packages/core/test/metadata/resource_loading_spec.ts
Normal file
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @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 {jasmineAwait} from '@angular/core/testing';
|
||||
|
||||
import {Component} from '../../src/core';
|
||||
import {clearResolutionOfComponentResourcesQueue, resolveComponentResources} from '../../src/metadata/resource_loading';
|
||||
import {ComponentType} from '../../src/render3/interfaces/definition';
|
||||
import {compileComponent} from '../../src/render3/jit/directive';
|
||||
|
||||
describe('resource_loading', () => {
|
||||
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'});
|
||||
expect(() => MyComponent.ngComponentDef).toThrowError(`
|
||||
Component 'MyComponent' is not resolved:
|
||||
- templateUrl: someUrl
|
||||
Did you run and wait for 'resolveComponentResources()'?`.trim());
|
||||
});
|
||||
|
||||
it('should throw an error when compiling component that has unresolved styleUrls', () => {
|
||||
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
|
||||
compileComponent(MyComponent, {styleUrls: ['someUrl1', 'someUrl2']});
|
||||
expect(() => MyComponent.ngComponentDef).toThrowError(`
|
||||
Component 'MyComponent' is not resolved:
|
||||
- styleUrls: ["someUrl1","someUrl2"]
|
||||
Did you run and wait for 'resolveComponentResources()'?`.trim());
|
||||
});
|
||||
|
||||
it('should throw an error when compiling component that has unresolved templateUrl and styleUrls',
|
||||
() => {
|
||||
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
|
||||
compileComponent(
|
||||
MyComponent, {templateUrl: 'someUrl', styleUrls: ['someUrl1', 'someUrl2']});
|
||||
expect(() => MyComponent.ngComponentDef).toThrowError(`
|
||||
Component 'MyComponent' is not resolved:
|
||||
- templateUrl: someUrl
|
||||
- styleUrls: ["someUrl1","someUrl2"]
|
||||
Did you run and wait for 'resolveComponentResources()'?`.trim());
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolution', () => {
|
||||
const URLS: {[url: string]: Promise<string>} = {
|
||||
'test://content': Promise.resolve('content'),
|
||||
'test://style1': Promise.resolve('style1'),
|
||||
'test://style2': Promise.resolve('style2'),
|
||||
};
|
||||
let resourceFetchCount: number;
|
||||
function testResolver(url: string): Promise<string> {
|
||||
resourceFetchCount++;
|
||||
return URLS[url] || Promise.reject('NOT_FOUND: ' + url);
|
||||
}
|
||||
beforeEach(() => resourceFetchCount = 0);
|
||||
|
||||
it('should resolve template', jasmineAwait(async() => {
|
||||
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
|
||||
const metadata: Component = {templateUrl: 'test://content'};
|
||||
compileComponent(MyComponent, metadata);
|
||||
await resolveComponentResources(testResolver);
|
||||
expect(MyComponent.ngComponentDef).toBeDefined();
|
||||
expect(metadata.templateUrl).toBe(undefined);
|
||||
expect(metadata.template).toBe('content');
|
||||
expect(resourceFetchCount).toBe(1);
|
||||
}));
|
||||
|
||||
it('should resolve styleUrls', jasmineAwait(async() => {
|
||||
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
|
||||
const metadata: Component = {template: '', styleUrls: ['test://style1', 'test://style2']};
|
||||
compileComponent(MyComponent, metadata);
|
||||
await resolveComponentResources(testResolver);
|
||||
expect(MyComponent.ngComponentDef).toBeDefined();
|
||||
expect(metadata.styleUrls).toBe(undefined);
|
||||
expect(metadata.styles).toEqual(['style1', 'style2']);
|
||||
expect(resourceFetchCount).toBe(2);
|
||||
}));
|
||||
|
||||
it('should cache multiple resolution to same URL', jasmineAwait(async() => {
|
||||
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
|
||||
const metadata: Component = {template: '', styleUrls: ['test://style1', 'test://style1']};
|
||||
compileComponent(MyComponent, metadata);
|
||||
await resolveComponentResources(testResolver);
|
||||
expect(MyComponent.ngComponentDef).toBeDefined();
|
||||
expect(metadata.styleUrls).toBe(undefined);
|
||||
expect(metadata.styles).toEqual(['style1', 'style1']);
|
||||
expect(resourceFetchCount).toBe(1);
|
||||
}));
|
||||
|
||||
it('should keep order even if the resolution is out of order', jasmineAwait(async() => {
|
||||
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
|
||||
const metadata: Component = {
|
||||
template: '',
|
||||
styles: ['existing'],
|
||||
styleUrls: ['test://style1', 'test://style2']
|
||||
};
|
||||
compileComponent(MyComponent, metadata);
|
||||
const resolvers: any[] = [];
|
||||
const resolved = resolveComponentResources(
|
||||
(url) => new Promise((resolve, response) => resolvers.push(url, resolve)));
|
||||
// Out of order resolution
|
||||
expect(resolvers[0]).toEqual('test://style1');
|
||||
expect(resolvers[2]).toEqual('test://style2');
|
||||
resolvers[3]('second');
|
||||
resolvers[1]('first');
|
||||
await resolved;
|
||||
expect(metadata.styleUrls).toBe(undefined);
|
||||
expect(metadata.styles).toEqual(['existing', 'first', 'second']);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('fetch', () => {
|
||||
function fetch(url: string): Promise<Response> {
|
||||
return Promise.resolve({
|
||||
text() { return 'response for ' + url; }
|
||||
} as any as Response);
|
||||
}
|
||||
|
||||
it('should work with fetch', jasmineAwait(async() => {
|
||||
const MyComponent: ComponentType<any> = (class MyComponent{}) as any;
|
||||
const metadata: Component = {templateUrl: 'test://content'};
|
||||
compileComponent(MyComponent, metadata);
|
||||
await resolveComponentResources(fetch);
|
||||
expect(MyComponent.ngComponentDef).toBeDefined();
|
||||
expect(metadata.templateUrl).toBe(undefined);
|
||||
expect(metadata.template).toBe('response for test://content');
|
||||
}));
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user