Prior to this change, we always recompile all Components/Directives/Pipes even if they were AOT-compiled and had no overrides. This is causing problems in case we try to recompile a Component with "templateUrl" or "styleUrls" (which were already resolved in case of AOT) and generally this unnecessary work that TestBed was doing is not required. This commit adds extra logic to check whether a Component/Directive/Pipe already have compiled NG def (like ngComponentDef) and whether there are no overrides present - in this case recompilation is skipped. Recompilation is also skipped in case a Component/Directive has only Provider overrides - in this situation providers resolver function is patched to reflect overrides. Provider overrides are very common in g3, thus this code path ensures no full recompilation. PR Close #29294
131 lines
4.7 KiB
TypeScript
131 lines
4.7 KiB
TypeScript
/**
|
|
* @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 {Type} from '../interface/type';
|
|
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 MyComponent{
|
|
* }
|
|
*
|
|
* // Calling `renderComponent` will fail because `renderComponent` is a synchronous process
|
|
* // and `MyComponent`'s `@Component.templateUrl` needs to be resolved asynchronously.
|
|
*
|
|
* // Calling `resolveComponentResources()` will resolve `@Component.templateUrl` into
|
|
* // `@Component.template`, which allows `renderComponent` to proceed in a synchronous manner.
|
|
*
|
|
* // Use browser's `fetch()` function as the default resource resolution strategy.
|
|
* resolveComponentResources(fetch).then(() => {
|
|
* // After resolution all URLs have been converted into `template` 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 for returning a `Promise` to the
|
|
* contents 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<void> {
|
|
// Store all promises which are fetching the resources.
|
|
const componentResolved: Promise<void>[] = [];
|
|
|
|
// 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));
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
componentResourceResolutionQueue.forEach((component: Component, type: Type<any>) => {
|
|
const promises: Promise<void>[] = [];
|
|
if (component.templateUrl) {
|
|
promises.push(cachedResourceResolve(component.templateUrl).then((template) => {
|
|
component.template = template;
|
|
}));
|
|
}
|
|
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.
|
|
promises.push(cachedResourceResolve(styleUrl).then((style) => {
|
|
styles[styleOffset + index] = style;
|
|
styleUrls.splice(styleUrls.indexOf(styleUrl), 1);
|
|
if (styleUrls.length == 0) {
|
|
component.styleUrls = undefined;
|
|
}
|
|
}));
|
|
});
|
|
const fullyResolved = Promise.all(promises).then(() => componentDefResolved(type));
|
|
componentResolved.push(fullyResolved);
|
|
});
|
|
clearResolutionOfComponentResourcesQueue();
|
|
return Promise.all(componentResolved).then(() => undefined);
|
|
}
|
|
|
|
let componentResourceResolutionQueue = new Map<Type<any>, Component>();
|
|
|
|
// Track when existing ngComponentDef for a Type is waiting on resources.
|
|
const componentDefPendingResolution = new Set<Type<any>>();
|
|
|
|
export function maybeQueueResolutionOfComponentResources(type: Type<any>, metadata: Component) {
|
|
if (componentNeedsResolution(metadata)) {
|
|
componentResourceResolutionQueue.set(type, metadata);
|
|
componentDefPendingResolution.add(type);
|
|
}
|
|
}
|
|
|
|
export function isComponentDefPendingResolution(type: Type<any>): boolean {
|
|
return componentDefPendingResolution.has(type);
|
|
}
|
|
|
|
export function componentNeedsResolution(component: Component): boolean {
|
|
return !!(
|
|
(component.templateUrl && !component.template) ||
|
|
component.styleUrls && component.styleUrls.length);
|
|
}
|
|
export function clearResolutionOfComponentResourcesQueue(): Map<Type<any>, Component> {
|
|
const old = componentResourceResolutionQueue;
|
|
componentResourceResolutionQueue = new Map();
|
|
return old;
|
|
}
|
|
|
|
export function restoreComponentResolutionQueue(queue: Map<Type<any>, Component>): void {
|
|
componentDefPendingResolution.clear();
|
|
queue.forEach((_, type) => componentDefPendingResolution.add(type));
|
|
componentResourceResolutionQueue = queue;
|
|
}
|
|
|
|
export function isComponentResourceResolutionQueueEmpty() {
|
|
return componentResourceResolutionQueue.size === 0;
|
|
}
|
|
|
|
function unwrapResponse(response: string | {text(): Promise<string>}): string|Promise<string> {
|
|
return typeof response == 'string' ? response : response.text();
|
|
}
|
|
|
|
function componentDefResolved(type: Type<any>): void {
|
|
componentDefPendingResolution.delete(type);
|
|
} |