refactor(ivy): expose resolving URLs in the ResourceLoader
(#28199)
Resources can be loaded in the context of another file, which means that the path to the resource file must be resolved before it can be loaded. Previously the API of this interface did not allow the client code to get access to the resolved URL which is used to load the resource. Now this API has been refactored so that you must do the resource URL resolving first and the loading expects a resolved URL. PR Close #28199
This commit is contained in:

committed by
Alex Rickabaugh

parent
7bdf3fe41c
commit
73dcd72afb
@ -6,7 +6,50 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves and loads resource files that are referenced in Angular metadata.
|
||||
*
|
||||
* Note that `preload()` and `load()` take a `resolvedUrl`, which can be found
|
||||
* by calling `resolve()`. These two steps are separated because sometimes the
|
||||
* resolved URL to the resource is needed as well as its contents.
|
||||
*/
|
||||
export interface ResourceLoader {
|
||||
preload?(url: string, containingFile: string): Promise<void>|undefined;
|
||||
load(url: string, containingFile: string): string;
|
||||
/**
|
||||
* True if this resource loader can preload resources.
|
||||
*
|
||||
* Sometimes a `ResourceLoader` is not able to do asynchronous pre-loading of resources.
|
||||
*/
|
||||
canPreload: boolean;
|
||||
|
||||
/**
|
||||
* Resolve the url of a resource relative to the file that contains the reference to it.
|
||||
* The return value of this method can be used in the `load()` and `preload()` methods.
|
||||
*
|
||||
* @param url The, possibly relative, url of the resource.
|
||||
* @param fromFile The path to the file that contains the URL of the resource.
|
||||
* @returns A resolved url of resource.
|
||||
* @throws An error if the resource cannot be resolved.
|
||||
*/
|
||||
resolve(file: string, basePath: string): string;
|
||||
|
||||
/**
|
||||
* Preload the specified resource, asynchronously. Once the resource is loaded, its value
|
||||
* should be cached so it can be accessed synchronously via the `load()` method.
|
||||
*
|
||||
* @param resolvedUrl The url (resolved by a call to `resolve()`) of the resource to preload.
|
||||
* @returns A Promise that is resolved once the resource has been loaded or `undefined`
|
||||
* if the file has already been loaded.
|
||||
* @throws An Error if pre-loading is not available.
|
||||
*/
|
||||
preload(resolvedUrl: string): Promise<void>|undefined;
|
||||
|
||||
/**
|
||||
* Load the resource at the given url, synchronously.
|
||||
*
|
||||
* The contents of the resource may have been cached by a previous call to `preload()`.
|
||||
*
|
||||
* @param resolvedUrl The url (resolved by a call to `resolve()`) of the resource to load.
|
||||
* @returns The contents of the resource.
|
||||
*/
|
||||
load(resolvedUrl: string): string;
|
||||
}
|
||||
|
@ -56,33 +56,40 @@ export class ComponentDecoratorHandler implements
|
||||
}
|
||||
|
||||
preanalyze(node: ts.ClassDeclaration, decorator: Decorator): Promise<void>|undefined {
|
||||
if (!this.resourceLoader.canPreload) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const meta = this._resolveLiteral(decorator);
|
||||
const component = reflectObjectLiteral(meta);
|
||||
const promises: Promise<void>[] = [];
|
||||
const containingFile = node.getSourceFile().fileName;
|
||||
|
||||
if (this.resourceLoader.preload !== undefined && component.has('templateUrl')) {
|
||||
if (component.has('templateUrl')) {
|
||||
const templateUrlExpr = component.get('templateUrl') !;
|
||||
const templateUrl = this.evaluator.evaluate(templateUrlExpr);
|
||||
if (typeof templateUrl !== 'string') {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.VALUE_HAS_WRONG_TYPE, templateUrlExpr, 'templateUrl must be a string');
|
||||
}
|
||||
const promise = this.resourceLoader.preload(templateUrl, containingFile);
|
||||
const resourceUrl = this.resourceLoader.resolve(templateUrl, containingFile);
|
||||
const promise = this.resourceLoader.preload(resourceUrl);
|
||||
if (promise !== undefined) {
|
||||
promises.push(promise);
|
||||
}
|
||||
}
|
||||
|
||||
const styleUrls = this._extractStyleUrls(component);
|
||||
if (this.resourceLoader.preload !== undefined && styleUrls !== null) {
|
||||
if (styleUrls !== null) {
|
||||
for (const styleUrl of styleUrls) {
|
||||
const promise = this.resourceLoader.preload(styleUrl, containingFile);
|
||||
const resourceUrl = this.resourceLoader.resolve(styleUrl, containingFile);
|
||||
const promise = this.resourceLoader.preload(resourceUrl);
|
||||
if (promise !== undefined) {
|
||||
promises.push(promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (promises.length !== 0) {
|
||||
return Promise.all(promises).then(() => undefined);
|
||||
} else {
|
||||
@ -118,7 +125,8 @@ export class ComponentDecoratorHandler implements
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.VALUE_HAS_WRONG_TYPE, templateUrlExpr, 'templateUrl must be a string');
|
||||
}
|
||||
templateStr = this.resourceLoader.load(templateUrl, containingFile);
|
||||
const resolvedTemplateUrl = this.resourceLoader.resolve(templateUrl, containingFile);
|
||||
templateStr = this.resourceLoader.load(resolvedTemplateUrl);
|
||||
} else if (component.has('template')) {
|
||||
const templateExpr = component.get('template') !;
|
||||
const resolvedTemplate = this.evaluator.evaluate(templateExpr);
|
||||
@ -223,7 +231,10 @@ export class ComponentDecoratorHandler implements
|
||||
if (styles === null) {
|
||||
styles = [];
|
||||
}
|
||||
styles.push(...styleUrls.map(styleUrl => this.resourceLoader.load(styleUrl, containingFile)));
|
||||
styleUrls.forEach(styleUrl => {
|
||||
const resourceUrl = this.resourceLoader.resolve(styleUrl, containingFile);
|
||||
styles !.push(this.resourceLoader.load(resourceUrl));
|
||||
});
|
||||
}
|
||||
|
||||
const encapsulation: number =
|
||||
|
@ -18,7 +18,10 @@ import {ComponentDecoratorHandler} from '../src/component';
|
||||
import {SelectorScopeRegistry} from '../src/selector_scope';
|
||||
|
||||
export class NoopResourceLoader implements ResourceLoader {
|
||||
load(url: string): string { throw new Error('Not implemented'); }
|
||||
resolve(): string { throw new Error('Not implemented.'); }
|
||||
canPreload = false;
|
||||
load(): string { throw new Error('Not implemented'); }
|
||||
preload(): Promise<void>|undefined { throw new Error('Not implemented'); }
|
||||
}
|
||||
|
||||
describe('ComponentDecoratorHandler', () => {
|
||||
|
Reference in New Issue
Block a user