feat(compiler): use typescript for resolving resource paths

This can also be customized via the new method `resourceNameToFileName` in the
`CompilerHost`.
This commit is contained in:
Tobias Bosch
2017-08-15 17:06:09 -07:00
committed by Hans
parent 2572bf508f
commit 43226cb93d
11 changed files with 142 additions and 37 deletions

View File

@ -7,6 +7,7 @@
*/
import {MissingTranslationStrategy, ViewEncapsulation, ɵConsole as Console} from '@angular/core';
import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveResolver} from '../directive_resolver';
@ -22,7 +23,8 @@ import {PipeResolver} from '../pipe_resolver';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser';
import {createOfflineCompileUrlResolver} from '../url_resolver';
import {UrlResolver} from '../url_resolver';
import {syntaxError} from '../util';
import {ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompiler} from './compiler';
@ -33,6 +35,19 @@ import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
import {AotSummaryResolver} from './summary_resolver';
export function createAotUrlResolver(host: {
resourceNameToFileName(resourceName: string, containingFileName: string): string | null;
}): UrlResolver {
return {
resolve: (basePath: string, url: string) => {
const filePath = host.resourceNameToFileName(url, basePath);
if (!filePath) {
throw syntaxError(`Couldn't resolve resource ${url} from ${basePath}`);
}
return filePath;
}
};
}
/**
* Creates a new AotCompiler based on options and a host.
@ -41,7 +56,7 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
{compiler: AotCompiler, reflector: StaticReflector} {
let translations: string = options.translations || '';
const urlResolver = createOfflineCompileUrlResolver();
const urlResolver = createAotUrlResolver(compilerHost);
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);

View File

@ -14,6 +14,11 @@ import {AotSummaryResolverHost} from './summary_resolver';
* services and from underlying file systems.
*/
export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryResolverHost {
/**
* Converts a path that refers to a resource into an absolute filePath
* that can be later on used for loading the resource via `loadResource.
*/
resourceNameToFileName(resourceName: string, containingFileName: string): string|null;
/**
* Loads a resource (e.g. html / css)
*/

View File

@ -13,6 +13,7 @@
import {ViewEncapsulation, ɵConsole as Console} from '@angular/core';
import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler';
import {createAotUrlResolver} from '../aot/compiler_factory';
import {StaticReflector} from '../aot/static_reflector';
import {StaticSymbolCache} from '../aot/static_symbol';
import {StaticSymbolResolver, StaticSymbolResolverHost} from '../aot/static_symbol_resolver';
@ -28,14 +29,21 @@ import {NgModuleResolver} from '../ng_module_resolver';
import {ParseError} from '../parse_util';
import {PipeResolver} from '../pipe_resolver';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {createOfflineCompileUrlResolver} from '../url_resolver';
import {syntaxError} from '../util';
import {MessageBundle} from './message_bundle';
/**
* The host of the Extractor disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface ExtractorHost extends StaticSymbolResolverHost, AotSummaryResolverHost {
/**
* Converts a path that refers to a resource into an absolute filePath
* that can be lateron used for loading the resource via `loadResource.
*/
resourceNameToFileName(path: string, containingFile: string): string|null;
/**
* Loads a resource (e.g. html / css)
*/
@ -87,7 +95,7 @@ export class Extractor {
{extractor: Extractor, staticReflector: StaticReflector} {
const htmlParser = new HtmlParser();
const urlResolver = createOfflineCompileUrlResolver();
const urlResolver = createAotUrlResolver(host);
const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(host, symbolCache);
const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver);

View File

@ -11,14 +11,6 @@ import {Inject, InjectionToken, PACKAGE_ROOT_URL} from '@angular/core';
import {CompilerInjectable} from './injectable';
/**
* Create a {@link UrlResolver} with no package prefix.
*/
export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
return new UrlResolver();
}
export function createOfflineCompileUrlResolver(): UrlResolver {
return new UrlResolver('.');
}
@ -47,9 +39,12 @@ export const DEFAULT_PACKAGE_URL_PROVIDER = {
* Attacker-controlled data introduced by a template could expose your
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
*/
@CompilerInjectable()
export class UrlResolver {
constructor(@Inject(PACKAGE_ROOT_URL) private _packagePrefix: string|null = null) {}
export interface UrlResolver { resolve(baseUrl: string, url: string): string; }
export interface UrlResolverCtor { new (packagePrefix?: string|null): UrlResolver; }
export const UrlResolver: UrlResolverCtor = class UrlResolverImpl {
constructor(private _packagePrefix: string|null = null) {}
/**
* Resolves the `url` given the `baseUrl`:
@ -75,7 +70,7 @@ export class UrlResolver {
}
return resolvedUrl;
}
}
};
/**
* Extract the scheme of a URL.

View File

@ -330,8 +330,15 @@ export class MockAotCompilerHost implements AotCompilerHost {
private metadataCollector = new MetadataCollector();
private metadataVisible: boolean = true;
private dtsAreSource: boolean = true;
private resolveModuleNameHost: ts.ModuleResolutionHost;
constructor(private tsHost: MockCompilerHost) {}
constructor(private tsHost: MockCompilerHost) {
this.resolveModuleNameHost = Object.create(tsHost);
this.resolveModuleNameHost.fileExists = (fileName) => {
fileName = stripNgResourceSuffix(fileName);
return tsHost.fileExists(fileName);
};
}
hideMetadata() { this.metadataVisible = false; }
@ -369,11 +376,22 @@ export class MockAotCompilerHost implements AotCompilerHost {
moduleName = moduleName.replace(EXT, '');
const resolved = ts.resolveModuleName(
moduleName, containingFile.replace(/\\/g, '/'),
{baseDir: '/', genDir: '/'}, this.tsHost)
{baseDir: '/', genDir: '/'}, this.resolveModuleNameHost)
.resolvedModule;
return resolved ? resolved.resolvedFileName : null;
}
resourceNameToFileName(resourceName: string, containingFile: string) {
// Note: we convert package paths into relative paths to be compatible with the the
// previous implementation of UrlResolver.
if (resourceName && resourceName.charAt(0) !== '.' && !path.isAbsolute(resourceName)) {
resourceName = `./${resourceName}`;
}
const filePathWithNgResource =
this.moduleNameToFileName(addNgResourceSuffix(resourceName), containingFile);
return filePathWithNgResource ? stripNgResourceSuffix(filePathWithNgResource) : null;
}
// AotSummaryResolverHost
loadSummary(filePath: string): string|null { return this.tsHost.readFile(filePath); }
@ -647,3 +665,11 @@ export function compile(
}
return {genFiles, outDir};
}
function stripNgResourceSuffix(fileName: string): string {
return fileName.replace(/\.\$ngresource\$.*/, '');
}
function addNgResourceSuffix(fileName: string): string {
return `${fileName}.$ngresource$`;
}