X Tutup
Skip to content

Commit 28860d3

Browse files
committed
feat(core): provide support for relative assets for components
Assets defined for `templateUrl` and `styleUrls` can now be loaded in relative to where the component file is placed so long as the `moduleId` is set within the component annotation. Closes angular#5634
1 parent 5f0ce30 commit 28860d3

File tree

23 files changed

+328
-23
lines changed

23 files changed

+328
-23
lines changed

modules/angular2/angular2.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ export * from './platform/browser';
55
export * from './src/platform/dom/dom_adapter';
66
export * from './src/platform/dom/events/event_manager';
77
export * from './upgrade';
8-
export {UrlResolver, AppRootUrl} from './compiler';
8+
export {UrlResolver, AppRootUrl, getUrlScheme, DEFAULT_PACKAGE_URL_PROVIDER} from './compiler';

modules/angular2/core.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export {
1313
APP_ID,
1414
APP_COMPONENT,
1515
APP_INITIALIZER,
16+
PACKAGE_ROOT_URL,
1617
PLATFORM_INITIALIZER
1718
} from './src/core/application_tokens';
1819
export * from './src/core/zone';
@@ -29,4 +30,4 @@ export * from './src/core/change_detection';
2930
export * from './src/core/platform_directives_and_pipes';
3031
export * from './src/core/platform_common_providers';
3132
export * from './src/core/application_common_providers';
32-
export * from './src/core/reflection/reflection';
33+
export * from './src/core/reflection/reflection';

modules/angular2/src/compiler/compiler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {Compiler} from 'angular2/src/core/linker/compiler';
2424
import {RuntimeCompiler} from 'angular2/src/compiler/runtime_compiler';
2525
import {ElementSchemaRegistry} from 'angular2/src/compiler/schema/element_schema_registry';
2626
import {DomElementSchemaRegistry} from 'angular2/src/compiler/schema/dom_element_schema_registry';
27-
import {UrlResolver} from 'angular2/src/compiler/url_resolver';
27+
import {UrlResolver, DEFAULT_PACKAGE_URL_PROVIDER} from 'angular2/src/compiler/url_resolver';
2828
import {AppRootUrl} from 'angular2/src/compiler/app_root_url';
2929
import {AnchorBasedAppRootUrl} from 'angular2/src/compiler/anchor_based_app_root_url';
3030
import {Parser, Lexer} from 'angular2/src/core/change_detection/change_detection';
@@ -40,6 +40,7 @@ export const COMPILER_PROVIDERS: Array<Type | Provider | any[]> = CONST_EXPR([
4040
TemplateParser,
4141
TemplateNormalizer,
4242
RuntimeMetadataResolver,
43+
DEFAULT_PACKAGE_URL_PROVIDER,
4344
StyleCompiler,
4445
CommandCompiler,
4546
ChangeDetectionCompiler,

modules/angular2/src/compiler/runtime_metadata.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {reflector} from 'angular2/src/core/reflection/reflection';
1919
import {Injectable, Inject, Optional} from 'angular2/src/core/di';
2020
import {PLATFORM_DIRECTIVES} from 'angular2/src/core/platform_directives_and_pipes';
2121
import {MODULE_SUFFIX} from './util';
22+
import {getUrlScheme} from 'angular2/src/compiler/url_resolver';
2223

2324
@Injectable()
2425
export class RuntimeMetadataResolver {
@@ -107,8 +108,11 @@ function isValidDirective(value: Type): boolean {
107108
}
108109

109110
function calcModuleUrl(type: Type, dirMeta: md.DirectiveMetadata): string {
110-
if (isPresent(dirMeta.moduleId)) {
111-
return `package:${dirMeta.moduleId}${MODULE_SUFFIX}`;
111+
var moduleId = dirMeta.moduleId;
112+
if (isPresent(moduleId)) {
113+
var scheme = getUrlScheme(moduleId);
114+
return isPresent(scheme) && scheme.length > 0 ? moduleId :
115+
`package:${moduleId}${MODULE_SUFFIX}`;
112116
} else {
113117
return reflector.importUri(type);
114118
}

modules/angular2/src/compiler/url_resolver.dart

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
library angular2.src.services.url_resolver;
22

3-
import 'package:angular2/src/core/di.dart' show Injectable, Provider;
3+
import 'package:angular2/src/core/di.dart' show Injectable, Inject, Provider;
4+
import 'package:angular2/src/facade/lang.dart' show isPresent, StringWrapper;
5+
import 'package:angular2/src/core/application_tokens.dart' show PACKAGE_ROOT_URL;
46

57
UrlResolver createWithoutPackagePrefix() {
68
return new UrlResolver.withUrlPrefix(null);
79
}
810

11+
const DEFAULT_PACKAGE_URL_PROVIDER = const Provider(PACKAGE_ROOT_URL, useValue: "/packages");
12+
913
@Injectable()
1014
class UrlResolver {
1115
/// This will be the location where 'package:' Urls will resolve. Default is
1216
/// '/packages'
1317
final String _packagePrefix;
1418

15-
const UrlResolver() : _packagePrefix = '/packages';
19+
UrlResolver([@Inject(PACKAGE_ROOT_URL) this._packagePrefix]);
1620

1721
/// Creates a UrlResolver that will resolve 'package:' Urls to a different
1822
/// prefixed location.
@@ -32,15 +36,23 @@ class UrlResolver {
3236
*/
3337
String resolve(String baseUrl, String url) {
3438
Uri uri = Uri.parse(url);
35-
if (!uri.isAbsolute) {
39+
40+
if (isPresent(baseUrl) && baseUrl.length > 0) {
3641
Uri baseUri = Uri.parse(baseUrl);
3742
uri = baseUri.resolveUri(uri);
3843
}
3944

40-
if (_packagePrefix != null && uri.scheme == 'package') {
41-
return '$_packagePrefix/${uri.path}';
45+
var prefix = this._packagePrefix;
46+
if (prefix != null && uri.scheme == 'package') {
47+
prefix = StringWrapper.stripRight(prefix, '/');
48+
var path = StringWrapper.stripLeft(uri.path, '/');
49+
return '$prefix/$path';
4250
} else {
4351
return uri.toString();
4452
}
4553
}
4654
}
55+
56+
String getUrlScheme(String url) {
57+
return Uri.parse(url).scheme;
58+
}

modules/angular2/src/compiler/url_resolver.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
import {Injectable} from 'angular2/src/core/di';
2-
import {isPresent, isBlank, RegExpWrapper, normalizeBlank} from 'angular2/src/facade/lang';
1+
import {Injectable, Inject} from 'angular2/src/core/di';
2+
import {
3+
StringWrapper,
4+
isPresent,
5+
isBlank,
6+
RegExpWrapper,
7+
normalizeBlank
8+
} from 'angular2/src/facade/lang';
39
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
410
import {ListWrapper} from 'angular2/src/facade/collection';
11+
import {PACKAGE_ROOT_URL} from 'angular2/src/core/application_tokens';
12+
import {Provider} from 'angular2/src/core/di';
513

614
export function createWithoutPackagePrefix(): UrlResolver {
715
return new UrlResolver();
816
}
917

18+
export var DEFAULT_PACKAGE_URL_PROVIDER = new Provider(PACKAGE_ROOT_URL, {useValue: "/"});
1019

1120
/**
1221
* Used by the {@link Compiler} when resolving HTML and CSS template URLs.
@@ -17,6 +26,14 @@ export function createWithoutPackagePrefix(): UrlResolver {
1726
*/
1827
@Injectable()
1928
export class UrlResolver {
29+
private _packagePrefix: string;
30+
31+
constructor(@Inject(PACKAGE_ROOT_URL) packagePrefix: string = null) {
32+
if (isPresent(packagePrefix)) {
33+
this._packagePrefix = StringWrapper.stripRight(packagePrefix, "/") + "/";
34+
}
35+
}
36+
2037
/**
2138
* Resolves the `url` given the `baseUrl`:
2239
* - when the `url` is null, the `baseUrl` is returned,
@@ -29,7 +46,21 @@ export class UrlResolver {
2946
* @param {string} url
3047
* @returns {string} the resolved URL
3148
*/
32-
resolve(baseUrl: string, url: string): string { return _resolveUrl(baseUrl, url); }
49+
resolve(baseUrl: string, url: string): string {
50+
var resolvedUrl = url;
51+
if (isPresent(baseUrl) && baseUrl.length > 0) {
52+
resolvedUrl = _resolveUrl(baseUrl, resolvedUrl);
53+
}
54+
if (isPresent(this._packagePrefix) && getUrlScheme(resolvedUrl) == "package") {
55+
resolvedUrl = resolvedUrl.replace("package:", this._packagePrefix);
56+
}
57+
return resolvedUrl;
58+
}
59+
}
60+
61+
export function getUrlScheme(url: string): string {
62+
var match = _split(url);
63+
return (match && match[_ComponentIndex.Scheme]) || "";
3364
}
3465

3566
// The code below is adapted from Traceur:

modules/angular2/src/core/application_tokens.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,10 @@ export const PLATFORM_INITIALIZER: OpaqueToken =
5858
/**
5959
* A function that will be executed when an application is initialized.
6060
*/
61-
export const APP_INITIALIZER: OpaqueToken = CONST_EXPR(new OpaqueToken("Application Initializer"));
61+
export const APP_INITIALIZER: OpaqueToken = CONST_EXPR(new OpaqueToken("Application Initializer"));
62+
63+
/**
64+
* A token which indicates the root directory of the application
65+
*/
66+
export const PACKAGE_ROOT_URL: OpaqueToken =
67+
CONST_EXPR(new OpaqueToken("Application Packages Root URL"));

modules/angular2/src/facade/lang.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ class StringWrapper {
7272
return s == s2;
7373
}
7474

75+
static String stripLeft(String s, String charVal) {
76+
if (isPresent(s) && s.length > 0) {
77+
var pos = 0;
78+
for (var i = 0; i < s.length; i++) {
79+
if (s[i] != charVal) break;
80+
pos++;
81+
}
82+
s = s.substring(pos);
83+
}
84+
return s;
85+
}
86+
87+
static String stripRight(String s, String charVal) {
88+
if (isPresent(s) && s.length > 0) {
89+
var pos = s.length;
90+
for (var i = s.length - 1; i >= 0; i--) {
91+
if (s[i] != charVal) break;
92+
pos--;
93+
}
94+
s = s.substring(0, pos);
95+
}
96+
return s;
97+
}
98+
7599
static String replace(String s, Pattern from, String replace) {
76100
return s.replaceFirst(from, replace);
77101
}

modules/angular2/src/facade/lang.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,30 @@ export class StringWrapper {
166166

167167
static equals(s: string, s2: string): boolean { return s === s2; }
168168

169+
static stripLeft(s: string, charVal: string): string {
170+
if (s && s.length) {
171+
var pos = 0;
172+
for (var i = 0; i < s.length; i++) {
173+
if (s[i] != charVal) break;
174+
pos++;
175+
}
176+
s = s.substring(pos);
177+
}
178+
return s;
179+
}
180+
181+
static stripRight(s: string, charVal: string): string {
182+
if (s && s.length) {
183+
var pos = s.length;
184+
for (var i = s.length - 1; i >= 0; i--) {
185+
if (s[i] != charVal) break;
186+
pos--;
187+
}
188+
s = s.substring(0, pos);
189+
}
190+
return s;
191+
}
192+
169193
static replace(s: string, from: string, replace: string): string {
170194
return s.replace(from, replace);
171195
}

modules/angular2/test/compiler/url_resolver_spec.ts

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'angular2/testing_internal';
1+
import {
2+
describe,
3+
it,
4+
expect,
5+
beforeEach,
6+
ddescribe,
7+
iit,
8+
xit,
9+
el,
10+
inject
11+
} from 'angular2/testing_internal';
12+
import {IS_DART} from 'angular2/src/facade/lang';
213
import {UrlResolver} from 'angular2/src/compiler/url_resolver';
314

415
export function main() {
@@ -69,14 +80,50 @@ export function main() {
6980
expect(resolver.resolve('foo/baz', '/bar')).toEqual('/bar');
7081
expect(resolver.resolve('foo/baz/', '/bar')).toEqual('/bar');
7182
});
72-
});
7383

74-
describe('corner and error cases', () => {
75-
it('should encode URLs before resolving', () => {
76-
expect(resolver.resolve('foo/baz', `<p #p>Hello
77-
</p>`))
78-
.toEqual('foo/%3Cp%20#p%3EHello%0A%20%20%20%20%20%20%20%20%3C/p%3E');
84+
it('should not resolve urls against the baseUrl when the url contains a scheme', () => {
85+
resolver = new UrlResolver('my_packages_dir');
86+
expect(resolver.resolve("base/", 'package:file')).toEqual('my_packages_dir/file');
87+
expect(resolver.resolve("base/", 'http:super_file')).toEqual('http:super_file');
88+
expect(resolver.resolve("base/", './mega_file')).toEqual('base/mega_file');
7989
});
8090
});
91+
92+
describe('packages',
93+
() => {
94+
it('should resolve a url based on the application package', () => {
95+
resolver = new UrlResolver('my_packages_dir');
96+
expect(resolver.resolve(null, 'package:some/dir/file.txt'))
97+
.toEqual('my_packages_dir/some/dir/file.txt');
98+
expect(resolver.resolve(null, 'some/dir/file.txt')).toEqual('some/dir/file.txt');
99+
});
100+
101+
it('should contain a default value of "/packages" when nothing is provided for DART',
102+
inject([UrlResolver], (resolver) => {
103+
if (IS_DART) {
104+
expect(resolver.resolve(null, 'package:file')).toEqual('/packages/file');
105+
}
106+
}));
107+
108+
it('should contain a default value of "/" when nothing is provided for TS/ESM',
109+
inject([UrlResolver], (resolver) => {
110+
if (!IS_DART) {
111+
expect(resolver.resolve(null, 'package:file')).toEqual('/file');
112+
}
113+
}));
114+
115+
it('should resolve a package value when present within the baseurl', () => {
116+
resolver = new UrlResolver('/my_special_dir');
117+
expect(resolver.resolve('package:some_dir/', 'matias.html'))
118+
.toEqual('/my_special_dir/some_dir/matias.html');
119+
});
120+
})
121+
122+
describe('corner and error cases', () => {
123+
it('should encode URLs before resolving', () => {
124+
expect(resolver.resolve('foo/baz', `<p #p>Hello
125+
</p>`)).toEqual('foo/%3Cp%20#p%3EHello%0A%20%20%20%20%20%20%20%20%3C/p%3E');
126+
});
127+
});
81128
});
82129
}

0 commit comments

Comments
 (0)
X Tutup