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:
103
packages/core/src/metadata/resource_loading.ts
Normal file
103
packages/core/src/metadata/resource_loading.ts
Normal file
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @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 {Component} from './directives';
|
||||
|
||||
|
||||
/**
|
||||
* Used to resolve resource URLs on `@Component` when used with JIT compilation.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* @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);
|
||||
* });
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* NOTE: In AOT the resolution happens during compilation, and so there should be no need
|
||||
* to call this method outside JIT mode.
|
||||
*
|
||||
* @param resourceResolver a function which is responsible to returning a `Promise` of the resolved
|
||||
* URL. Browser's `fetch` method is a good default implementation.
|
||||
*/
|
||||
export function resolveComponentResources(
|
||||
resourceResolver: (url: string) => (Promise<string|{text(): Promise<string>}>)): Promise<null> {
|
||||
// Store all promises which are fetching the resources.
|
||||
const urlFetches: Promise<string>[] = [];
|
||||
|
||||
// Cache so that we don't fetch the same resource more than once.
|
||||
const urlMap = new Map<string, Promise<string>>();
|
||||
function cachedResourceResolve(url: string): Promise<string> {
|
||||
let promise = urlMap.get(url);
|
||||
if (!promise) {
|
||||
const resp = resourceResolver(url);
|
||||
urlMap.set(url, promise = resp.then(unwrapResponse));
|
||||
urlFetches.push(promise);
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
componentResourceResolutionQueue.forEach((component: Component) => {
|
||||
if (component.templateUrl) {
|
||||
cachedResourceResolve(component.templateUrl).then((template) => {
|
||||
component.template = template;
|
||||
component.templateUrl = undefined;
|
||||
});
|
||||
}
|
||||
const styleUrls = component.styleUrls;
|
||||
const styles = component.styles || (component.styles = []);
|
||||
const styleOffset = component.styles.length;
|
||||
styleUrls && styleUrls.forEach((styleUrl, index) => {
|
||||
styles.push(''); // pre-allocate array.
|
||||
cachedResourceResolve(styleUrl).then((style) => {
|
||||
styles[styleOffset + index] = style;
|
||||
styleUrls.splice(styleUrls.indexOf(styleUrl), 1);
|
||||
if (styleUrls.length == 0) {
|
||||
component.styleUrls = undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
componentResourceResolutionQueue.clear();
|
||||
return Promise.all(urlFetches).then(() => null);
|
||||
}
|
||||
|
||||
const componentResourceResolutionQueue: Set<Component> = new Set();
|
||||
|
||||
export function maybeQueueResolutionOfComponentResources(metadata: Component) {
|
||||
if (componentNeedsResolution(metadata)) {
|
||||
componentResourceResolutionQueue.add(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
export function componentNeedsResolution(component: Component) {
|
||||
return component.templateUrl || component.styleUrls && component.styleUrls.length;
|
||||
}
|
||||
export function clearResolutionOfComponentResourcesQueue() {
|
||||
componentResourceResolutionQueue.clear();
|
||||
}
|
||||
|
||||
function unwrapResponse(response: string | {text(): Promise<string>}): string|Promise<string> {
|
||||
return typeof response == 'string' ? response : response.text();
|
||||
}
|
Reference in New Issue
Block a user