diff --git a/modules/@angular/compiler-cli/src/ng_host.ts b/modules/@angular/compiler-cli/src/ng_host.ts index e1d975033e..50c437bb04 100644 --- a/modules/@angular/compiler-cli/src/ng_host.ts +++ b/modules/@angular/compiler-cli/src/ng_host.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AotCompilerHost, StaticSymbol} from '@angular/compiler'; +import {AotCompilerHost, AssetUrl, StaticSymbol} from '@angular/compiler'; import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped'; import * as fs from 'fs'; import * as path from 'path'; @@ -60,6 +60,20 @@ export class NgHost implements AotCompilerHost { return resolved ? resolved.resolvedFileName : null; }; + protected normalizeAssetUrl(url: string): string { + const assetUrl = AssetUrl.parse(url); + const path = assetUrl ? `${assetUrl.packageName}/${assetUrl.modulePath}` : null; + return this.getCanonicalFileName(path); + } + + protected resolveAssetUrl(url: string, containingFile: string): string { + const assetUrl = this.normalizeAssetUrl(url); + if (assetUrl) { + return this.getCanonicalFileName(this.resolveImportToFile(assetUrl, containingFile)); + } + return url; + } + /** * We want a moduleId that will appear in import statements in the generated code. * These need to be in a form that system.js can load, so absolute file paths don't work. @@ -76,6 +90,9 @@ export class NgHost implements AotCompilerHost { * NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`. */ getImportPath(containingFile: string, importedFile: string): string { + importedFile = this.resolveAssetUrl(importedFile, containingFile); + containingFile = this.resolveAssetUrl(containingFile, ''); + // If a file does not yet exist (because we compile it later), we still need to // assume it exists it so that the `resolve` method works! if (!this.compilerHost.fileExists(importedFile)) { diff --git a/modules/@angular/compiler-cli/src/path_mapped_ng_host.ts b/modules/@angular/compiler-cli/src/path_mapped_ng_host.ts index d37ec855ea..863952cf7d 100644 --- a/modules/@angular/compiler-cli/src/path_mapped_ng_host.ts +++ b/modules/@angular/compiler-cli/src/path_mapped_ng_host.ts @@ -70,6 +70,9 @@ export class PathMappedNgHost extends NgHost { * they are resolvable by the moduleResolution strategy from the CompilerHost. */ getImportPath(containingFile: string, importedFile: string): string { + importedFile = this.resolveAssetUrl(importedFile, containingFile); + containingFile = this.resolveAssetUrl(containingFile, ''); + if (this.options.traceResolution) { console.log( 'getImportPath from containingFile', containingFile, 'to importedFile', importedFile); diff --git a/modules/@angular/compiler/src/aot/static_reflector.ts b/modules/@angular/compiler/src/aot/static_reflector.ts index a026b8a710..2efca94244 100644 --- a/modules/@angular/compiler/src/aot/static_reflector.ts +++ b/modules/@angular/compiler/src/aot/static_reflector.ts @@ -7,6 +7,7 @@ */ import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; +import {AssetUrl} from '../output/path_util'; import {ReflectorReader} from '../private_import_core'; import {AotCompilerHost} from './compiler_host'; import {StaticSymbol} from './static_symbol'; @@ -212,6 +213,11 @@ export class StaticReflector implements ReflectorReader { return result; } + private normalizeAssetUrl(url: string): string { + const assetUrl = AssetUrl.parse(url); + return assetUrl ? `${assetUrl.packageName}@${assetUrl.modulePath}` : null; + } + private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol { const resolveModule = (moduleName: string): string => { const resolvedModulePath = this.host.resolveImportToFile(moduleName, filePath); @@ -269,6 +275,10 @@ export class StaticReflector implements ReflectorReader { return symbol; } try { + const assetUrl = this.normalizeAssetUrl(module); + if (assetUrl) { + module = assetUrl; + } const filePath = this.host.resolveImportToFile(module, containingFile); if (!filePath) { diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index 0f6c664758..ab9fd5889a 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -8,7 +8,6 @@ import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; -import {StaticSymbol, isStaticSymbol} from './aot/static_symbol'; import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core'; @@ -340,21 +339,19 @@ export class Identifiers { export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string { if (path == null) { - return `@angular/${pkg}/index`; + return `asset:@angular/lib/${pkg}/index`; } else { - return `@angular/${pkg}/${type}/${path}`; + return `asset:@angular/lib/${pkg}/src/${path}`; } } export function resolveIdentifier(identifier: IdentifierSpec) { - let moduleUrl = identifier.moduleUrl; - const reference = - reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime); - if (isStaticSymbol(reference)) { - moduleUrl = reference.filePath; - } - return new CompileIdentifierMetadata( - {name: identifier.name, moduleUrl: moduleUrl, reference: reference}); + return new CompileIdentifierMetadata({ + name: identifier.name, + moduleUrl: identifier.moduleUrl, + reference: + reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime) + }); } export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata { diff --git a/modules/@angular/compiler/src/output/path_util.ts b/modules/@angular/compiler/src/output/path_util.ts index e8dfb24391..9f1c30efd3 100644 --- a/modules/@angular/compiler/src/output/path_util.ts +++ b/modules/@angular/compiler/src/output/path_util.ts @@ -6,9 +6,30 @@ * found in the LICENSE file at https://angular.io/license */ +// asset:// +const _ASSET_URL_RE = /asset:([^\/]+)\/([^\/]+)\/(.+)/; + /** * Interface that defines how import statements should be generated. */ export abstract class ImportGenerator { + static parseAssetUrl(url: string): AssetUrl { return AssetUrl.parse(url); } + abstract getImportPath(moduleUrlStr: string, importedUrlStr: string): string; } + +export class AssetUrl { + static parse(url: string, allowNonMatching: boolean = true): AssetUrl { + const match = url.match(_ASSET_URL_RE); + if (match !== null) { + return new AssetUrl(match[1], match[2], match[3]); + } + if (allowNonMatching) { + return null; + } + throw new Error(`Url ${url} is not a valid asset: url`); + } + + constructor(public packageName: string, public firstLevelDir: string, public modulePath: string) { + } +} diff --git a/modules/@angular/compiler/src/output/ts_emitter.ts b/modules/@angular/compiler/src/output/ts_emitter.ts index e0628cd3f3..30ab03e794 100644 --- a/modules/@angular/compiler/src/output/ts_emitter.ts +++ b/modules/@angular/compiler/src/output/ts_emitter.ts @@ -14,7 +14,7 @@ import {AbstractEmitterVisitor, CATCH_ERROR_VAR, CATCH_STACK_VAR, EmitterVisitor import * as o from './output_ast'; import {ImportGenerator} from './path_util'; -const _debugModuleUrl = '/debug/lib'; +const _debugModuleUrl = 'asset://debug/lib'; export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.Type | any[]): string { diff --git a/modules/@angular/compiler/src/url_resolver.ts b/modules/@angular/compiler/src/url_resolver.ts index 8d9c031c08..84058063be 100644 --- a/modules/@angular/compiler/src/url_resolver.ts +++ b/modules/@angular/compiler/src/url_resolver.ts @@ -10,6 +10,9 @@ import {Inject, Injectable, PACKAGE_ROOT_URL} from '@angular/core'; import {isBlank, isPresent} from './facade/lang'; + +const _ASSET_SCHEME = 'asset:'; + /** * Create a {@link UrlResolver} with no package prefix. */ @@ -18,7 +21,7 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver { } export function createOfflineCompileUrlResolver(): UrlResolver { - return new UrlResolver('.'); + return new UrlResolver(_ASSET_SCHEME); } /** @@ -67,9 +70,14 @@ export class UrlResolver { if (isPresent(prefix) && isPresent(resolvedParts) && resolvedParts[_ComponentIndex.Scheme] == 'package') { let path = resolvedParts[_ComponentIndex.Path]; - prefix = prefix.replace(/\/+$/, ''); - path = path.replace(/^\/+/, ''); - return `${prefix}/${path}`; + if (this._packagePrefix === _ASSET_SCHEME) { + const pathSegements = path.split(/\//); + resolvedUrl = `asset:${pathSegements[0]}/lib/${pathSegements.slice(1).join('/')}`; + } else { + prefix = prefix.replace(/\/+$/, ''); + path = path.replace(/^\/+/, ''); + return `${prefix}/${path}`; + } } return resolvedUrl; } diff --git a/modules/@angular/compiler/test/output/js_emitter_spec.ts b/modules/@angular/compiler/test/output/js_emitter_spec.ts index f85c21d0d0..8ec1565000 100644 --- a/modules/@angular/compiler/test/output/js_emitter_spec.ts +++ b/modules/@angular/compiler/test/output/js_emitter_spec.ts @@ -13,8 +13,8 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in import {SimpleJsImportGenerator} from './output_emitter_util'; -const someModuleUrl = 'somePackage/somePath'; -const anotherModuleUrl = 'somePackage/someOtherPath'; +const someModuleUrl = 'asset:somePackage/lib/somePath'; +const anotherModuleUrl = 'asset:somePackage/lib/someOtherPath'; const sameModuleIdentifier = new CompileIdentifierMetadata({name: 'someLocalId', moduleUrl: someModuleUrl}); diff --git a/modules/@angular/compiler/test/output/output_emitter_util.ts b/modules/@angular/compiler/test/output/output_emitter_util.ts index c510b794cf..83b445a94a 100644 --- a/modules/@angular/compiler/test/output/output_emitter_util.ts +++ b/modules/@angular/compiler/test/output/output_emitter_util.ts @@ -22,7 +22,7 @@ export class ExternalClass { const testDataIdentifier = new CompileIdentifierMetadata({ name: 'ExternalClass', - moduleUrl: `@angular/compiler/test/output/output_emitter_util`, + moduleUrl: `asset:@angular/lib/compiler/test/output/output_emitter_util`, reference: ExternalClass }); @@ -253,5 +253,12 @@ function createOperatorFn(op: o.BinaryOperator) { } export class SimpleJsImportGenerator implements ImportGenerator { - getImportPath(moduleUrlStr: string, importedUrlStr: string): string { return importedUrlStr; } + getImportPath(moduleUrlStr: string, importedUrlStr: string): string { + const importedAssetUrl = ImportGenerator.parseAssetUrl(importedUrlStr); + if (importedAssetUrl) { + return `${importedAssetUrl.packageName}/${importedAssetUrl.modulePath}`; + } else { + return importedUrlStr; + } + } } diff --git a/modules/@angular/compiler/test/output/ts_emitter_spec.ts b/modules/@angular/compiler/test/output/ts_emitter_spec.ts index 5f221eb927..86d65d53e4 100644 --- a/modules/@angular/compiler/test/output/ts_emitter_spec.ts +++ b/modules/@angular/compiler/test/output/ts_emitter_spec.ts @@ -13,8 +13,8 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in import {SimpleJsImportGenerator} from './output_emitter_util'; -const someModuleUrl = 'somePackage/somePath'; -const anotherModuleUrl = 'somePackage/someOtherPath'; +const someModuleUrl = 'asset:somePackage/lib/somePath'; +const anotherModuleUrl = 'asset:somePackage/lib/someOtherPath'; const sameModuleIdentifier = new CompileIdentifierMetadata({name: 'someLocalId', moduleUrl: someModuleUrl}); diff --git a/modules/@angular/compiler/test/style_url_resolver_spec.ts b/modules/@angular/compiler/test/style_url_resolver_spec.ts index 700a8631ae..8f954bb946 100644 --- a/modules/@angular/compiler/test/style_url_resolver_spec.ts +++ b/modules/@angular/compiler/test/style_url_resolver_spec.ts @@ -91,6 +91,9 @@ export function main() { it('should resolve package: urls', () => { expect(isStyleUrlResolvable('package:someUrl.css')).toBe(true); }); + it('should resolve asset: urls', + () => { expect(isStyleUrlResolvable('asset:someUrl.css')).toBe(true); }); + it('should not resolve empty urls', () => { expect(isStyleUrlResolvable(null)).toBe(false); expect(isStyleUrlResolvable('')).toBe(false); diff --git a/modules/@angular/compiler/test/url_resolver_spec.ts b/modules/@angular/compiler/test/url_resolver_spec.ts index b6a538d04b..67f90bcd3a 100644 --- a/modules/@angular/compiler/test/url_resolver_spec.ts +++ b/modules/@angular/compiler/test/url_resolver_spec.ts @@ -106,6 +106,16 @@ export function main() { }); }); + describe('asset urls', () => { + let resolver: UrlResolver; + beforeEach(() => { resolver = createOfflineCompileUrlResolver(); }); + + it('should resolve package: urls into asset: urls', () => { + expect(resolver.resolve(null, 'package:somePkg/somePath')) + .toEqual('asset:somePkg/lib/somePath'); + }); + }); + describe('corner and error cases', () => { it('should encode URLs before resolving', () => {