Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 8fd235c

Browse files
Diana Salsburychirayuk
Diana Salsbury
authored andcommitted
feat(urls): support relative CSS / template URLs in components
Example: When defining a component called 'foo' in file '/myproject/has/many/folders/foo.dart', prior to the change you would do so thusly: @component( selector: 'foo', templateUrl: '/myproject/has/many/folders/foo.html', cssUrl: '/myproject/has/many/folders/foo.css' ) The above is still valid, but now can also be defined rather more succinctly as: @component( selector: 'foo', templateUrl: 'foo.html', cssUrl: 'foo.css' ) A full table of what URLs will be changed/unchanged is below: You Say: Transformer replies: 'packages/foo/bar.html' 'packages/foo/bar.html' 'foo.html' 'packages/foo/bar.html' './foo.html' 'packages/foo/bar.html' '/foo.html' '/foo.html' 'http://google.com/foo.html' 'http://google.com/foo.html' *Note that as shown any absolute paths you define will not be bothered by the transformer, so if you have a templateUrl living at <root>/foo.html, you can still reach it with '/foo.html'! This feature is defaulted to an "off" state through the addition of the ResourceResolverConfig class. To turn the feature on, bind a new ResourceResolverConfig in your module: module.bind(ResourceResolverConfig, toValue: new ResourceResolverConfig(useRelativeUrls: true)); *Testing*: - e2e transformer tests can now be done on the sample application found in the test_transformers folder
1 parent 4e23817 commit 8fd235c

38 files changed

+1356
-183
lines changed

lib/application_factory.dart

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import 'package:angular/change_detection/change_detection.dart';
1616
import 'package:angular/change_detection/dirty_checking_change_detector_dynamic.dart';
1717
import 'package:angular/core/registry_dynamic.dart';
1818
import 'package:angular/core/parser/dynamic_closure_map.dart';
19+
import 'package:angular/core_dom/type_to_uri_mapper.dart';
20+
import 'package:angular/core_dom/type_to_uri_mapper_dynamic.dart';
1921
import 'dart:html';
2022

2123
/**
@@ -42,6 +44,7 @@ import 'dart:html';
4244
'angular.core.parser.Parser',
4345
'angular.core.parser',
4446
'angular.core.parser.lexer',
47+
'angular.core_dom.type_to_uri_mapper_dynamic',
4548
'angular.core_dynamic.DynamicMetadataExtractor',
4649
'perf_api',
4750
List,
@@ -58,6 +61,7 @@ import 'dart:mirrors' show MirrorsUsed;
5861
class _DynamicApplication extends Application {
5962
_DynamicApplication() {
6063
ngModule
64+
..bind(TypeToUriMapper, toImplementation: DynamicTypeToUriMapper)
6165
..bind(MetadataExtractor, toImplementation: DynamicMetadataExtractor)
6266
..bind(FieldGetterFactory, toImplementation: DynamicFieldGetterFactory)
6367
..bind(ClosureMap, toImplementation: DynamicClosureMap);

lib/application_factory_static.dart

+21-3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import 'package:angular/application.dart';
2828
import 'package:angular/core/registry.dart';
2929
import 'package:angular/core/parser/parser.dart';
3030
import 'package:angular/core/parser/static_closure_map.dart';
31+
import 'package:angular/core_dom/type_to_uri_mapper.dart';
3132
import 'package:angular/core/registry_static.dart';
3233
import 'package:angular/change_detection/change_detection.dart';
3334
import 'package:angular/change_detection/dirty_checking_change_detector_static.dart';
@@ -41,8 +42,10 @@ class _StaticApplication extends Application {
4142
Map<Type, Object> metadata,
4243
Map<String, FieldGetter> fieldGetters,
4344
Map<String, FieldSetter> fieldSetters,
44-
Map<String, Symbol> symbols) {
45+
Map<String, Symbol> symbols,
46+
TypeToUriMapper uriMapper) {
4547
ngModule
48+
..bind(TypeToUriMapper, toValue: uriMapper)
4649
..bind(MetadataExtractor, toValue: new StaticMetadataExtractor(metadata))
4750
..bind(FieldGetterFactory, toValue: new StaticFieldGetterFactory(fieldGetters))
4851
..bind(ClosureMap, toValue: new StaticClosureMap(fieldGetters, fieldSetters, symbols));
@@ -76,10 +79,25 @@ class _StaticApplication extends Application {
7679
* .run();
7780
*
7881
*/
82+
83+
class _NullUriMapper extends TypeToUriMapper {
84+
Uri uriForType(Type type) {
85+
throw "You did not pass in a TypeToUriMapper to your StaticApplicationFactory."
86+
"(This would have been automatic if you used Dart transformers.) You must pass "
87+
"in a valid TypeTpUriMapper when constructing your Static Application";
88+
}
89+
}
7990
Application staticApplicationFactory(
8091
Map<Type, Object> metadata,
8192
Map<String, FieldGetter> fieldGetters,
8293
Map<String, FieldSetter> fieldSetters,
83-
Map<String, Symbol> symbols) {
84-
return new _StaticApplication(metadata, fieldGetters, fieldSetters, symbols);
94+
Map<String, Symbol> symbols,
95+
[TypeToUriMapper uriMapper]) {
96+
// TODO(chirayu): uriMapper is optional for backwards compatibility. Make it
97+
// a required parameter by the next release.
98+
if (uriMapper == null) {
99+
uriMapper = new _NullUriMapper();
100+
}
101+
return new _StaticApplication(metadata, fieldGetters, fieldSetters,
102+
symbols, uriMapper);
85103
}

lib/core/module.dart

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export "package:angular/core_dom/module_internal.dart" show
5656
RequestInterceptor,
5757
Response,
5858
ResponseError,
59+
ResourceResolverConfig,
60+
ResourceUrlResolver,
5961
UrlRewriter,
6062
TemplateCache,
6163
View,

lib/core_dom/module_internal.dart

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import 'package:angular/core_dom/static_keys.dart';
1919
import 'package:angular/core_dom/directive_injector.dart';
2020
export 'package:angular/core_dom/directive_injector.dart' show DirectiveInjector;
2121

22+
import 'package:angular/core_dom/type_to_uri_mapper.dart';
23+
import 'package:angular/core_dom/resource_url_resolver.dart';
24+
export 'package:angular/core_dom/resource_url_resolver.dart'
25+
show ResourceUrlResolver, ResourceResolverConfig;
26+
2227
import 'package:angular/change_detection/watch_group.dart' show Watch, PrototypeMap;
2328
import 'package:angular/change_detection/ast_parser.dart';
2429
import 'package:angular/core/registry.dart';
@@ -80,13 +85,15 @@ class CoreDomModule extends Module {
8085
bind(ComponentCssRewriter);
8186
bind(WebPlatform);
8287

88+
bind(ResourceUrlResolver);
8389
bind(Http);
8490
bind(UrlRewriter);
8591
bind(HttpBackend);
8692
bind(HttpDefaultHeaders);
8793
bind(HttpDefaults);
8894
bind(HttpInterceptors);
8995
bind(HttpConfig);
96+
bind(ResourceResolverConfig);
9097
bind(Animate);
9198
bind(ViewCache);
9299
bind(BrowserCookies);
+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* Dart port of
3+
* https://github.com/Polymer/platform-dev/blob/896e245a0046a397bfc0190d958d2bd162e8f53c/src/url.js
4+
*
5+
* This converts URIs within a document from relative URIs to being absolute URIs.
6+
*/
7+
8+
library angular.core_dom.resource_url_resolver;
9+
10+
import 'dart:html';
11+
import 'dart:js' as js;
12+
13+
import 'package:di/di.dart';
14+
import 'package:di/annotations.dart';
15+
16+
import 'package:angular/core_dom/type_to_uri_mapper.dart';
17+
18+
@Injectable()
19+
class ResourceUrlResolver {
20+
static final RegExp cssUrlRegexp = new RegExp(r'''(\burl\((?:[\s]+)?)(['"]?)([^]*)(\2(?:[\s]+)?\))''');
21+
static final RegExp cssImportRegexp = new RegExp(r'(@import[\s]+(?!url\())([^;]*)(;)');
22+
static const List<String> urlAttrs = const ['href', 'src', 'action'];
23+
static final String urlAttrsSelector = '[${urlAttrs.join('],[')}]';
24+
static final RegExp urlTemplateSearch = new RegExp('{{.*}}');
25+
static final RegExp quotes = new RegExp("[\"\']");
26+
27+
// Ensures that Uri.base is http/https.
28+
final _baseUri = Uri.base.origin + ("/");
29+
30+
final TypeToUriMapper _uriMapper;
31+
final ResourceResolverConfig _config;
32+
33+
ResourceUrlResolver(this._uriMapper, this._config);
34+
35+
String resolveHtml(String html, [Uri baseUri]) {
36+
if (baseUri == null) {
37+
return html;
38+
}
39+
HtmlDocument document = new DomParser().parseFromString(
40+
"<!doctype html><html><body>$html</body></html>", "text/html");
41+
_resolveDom(document.body, baseUri);
42+
return document.body.innerHtml;
43+
}
44+
45+
/**
46+
* Resolves all relative URIs within the DOM from being relative to
47+
* [originalBase] to being absolute.
48+
*/
49+
void _resolveDom(Node root, Uri baseUri) {
50+
_resolveAttributes(root, baseUri);
51+
_resolveStyles(root, baseUri);
52+
53+
// handle template.content
54+
for (var template in _querySelectorAll(root, 'template')) {
55+
if (template.content != null) {
56+
_resolveDom(template.content, baseUri);
57+
}
58+
}
59+
}
60+
61+
Iterable<Element> _querySelectorAll(Node node, String selectors) {
62+
if (node is DocumentFragment) {
63+
return node.querySelectorAll(selectors);
64+
}
65+
if (node is Element) {
66+
return node.querySelectorAll(selectors);
67+
}
68+
return const [];
69+
}
70+
71+
void _resolveStyles(Node node, Uri baseUri) {
72+
var styles = _querySelectorAll(node, 'style');
73+
for (var style in styles) {
74+
_resolveStyle(style, baseUri);
75+
}
76+
}
77+
78+
void _resolveStyle(StyleElement style, Uri baseUri) {
79+
style.text = resolveCssText(style.text, baseUri);
80+
}
81+
82+
String resolveCssText(String cssText, Uri baseUri) {
83+
cssText = _replaceUrlsInCssText(cssText, baseUri, cssUrlRegexp);
84+
return _replaceUrlsInCssText(cssText, baseUri, cssImportRegexp);
85+
}
86+
87+
void _resolveAttributes(Node root, Uri baseUri) {
88+
if (root is Element) {
89+
_resolveElementAttributes(root, baseUri);
90+
}
91+
92+
for (var node in _querySelectorAll(root, urlAttrsSelector)) {
93+
_resolveElementAttributes(node, baseUri);
94+
}
95+
}
96+
97+
void _resolveElementAttributes(Element element, Uri baseUri) {
98+
var attrs = element.attributes;
99+
for (var attr in urlAttrs) {
100+
if (attrs.containsKey(attr)) {
101+
var value = attrs[attr];
102+
if (!value.contains(urlTemplateSearch)) {
103+
attrs[attr] = combine(baseUri, value).toString();
104+
}
105+
}
106+
}
107+
}
108+
109+
String _replaceUrlsInCssText(String cssText, Uri baseUri, RegExp regexp) {
110+
return cssText.replaceAllMapped(regexp, (match) {
111+
var url = match[3].trim();
112+
var urlPath = combine(baseUri, url).toString();
113+
return '${match[1].trim()}${match[2]}${urlPath}${match[2]})';
114+
});
115+
}
116+
/// Combines a type-based URI with a relative URI.
117+
///
118+
/// [baseUri] is assumed to use package: syntax for package-relative
119+
/// URIs, while [uri] is assumed to use 'packages/' syntax for
120+
/// package-relative URIs. Resulting URIs will use 'packages/' to indicate
121+
/// package-relative URIs.
122+
String combine(Uri baseUri, String uri) {
123+
if (!_config.useRelativeUrls) {
124+
return uri;
125+
}
126+
127+
if (uri == null) {
128+
uri = baseUri.path;
129+
} else {
130+
// if it's absolute but not package-relative, then just use that
131+
// The "packages/" test is just for backward compatibility. It's ok to
132+
// not resolve them, even through they're relative URLs, because in a Dart
133+
// application, "packages/" is managed by pub which creates a symlinked
134+
// hierarchy and they should all resolve to the same file at any level
135+
// that a "packages/" exists.
136+
if (uri.startsWith("/") || uri.startsWith('packages/')) {
137+
return uri;
138+
}
139+
}
140+
// If it's not absolute, then resolve it first
141+
Uri resolved = baseUri.resolve(uri);
142+
143+
// If it's package-relative, tack on 'packages/' - Note that eventually
144+
// we may want to change this to be '/packages/' to make it truly absolute
145+
if (resolved.scheme == 'package') {
146+
return 'packages/${resolved.path}';
147+
} else if (resolved.isAbsolute && resolved.toString().startsWith(_baseUri)) {
148+
var path = resolved.path;
149+
return path.startsWith("/") ? path.substring(1) : path;
150+
} else {
151+
return resolved.toString();
152+
}
153+
}
154+
155+
String combineWithType(Type type, String uri) {
156+
if (_config.useRelativeUrls) {
157+
return combine(_uriMapper.uriForType(type), uri);
158+
} else {
159+
return uri;
160+
}
161+
}
162+
}
163+
164+
@Injectable()
165+
class ResourceResolverConfig {
166+
bool useRelativeUrls;
167+
168+
ResourceResolverConfig(): useRelativeUrls = true;
169+
170+
ResourceResolverConfig.resolveRelativeUrls(this.useRelativeUrls);
171+
}

lib/core_dom/shadow_dom_component_factory.dart

+28-8
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,21 @@ abstract class BoundComponentFactory {
1212
Function call(dom.Element element);
1313

1414
static async.Future<ViewFactory> _viewFuture(
15-
Component component, ViewCache viewCache, DirectiveMap directives) {
15+
Component component, ViewCache viewCache, DirectiveMap directives,
16+
TypeToUriMapper uriMapper, ResourceUrlResolver resourceResolver, Type type) {
17+
1618
if (component.template != null) {
17-
return new async.Future.value(viewCache.fromHtml(component.template, directives));
19+
// TODO(chirayu): Replace this line with
20+
// var baseUri = uriMapper.uriForType(type);
21+
// once we have removed _NullUriMapper.
22+
var baseUriString = resourceResolver.combineWithType(type, null);
23+
var baseUri = (baseUriString != null) ? Uri.parse(baseUriString) : null;
24+
return new async.Future.value(viewCache.fromHtml(component.template, directives, baseUri));
1825
}
1926
if (component.templateUrl != null) {
20-
return viewCache.fromUrl(component.templateUrl, directives);
27+
var url = resourceResolver.combineWithType(type, component.templateUrl);
28+
var baseUri = Uri.parse(url);
29+
return viewCache.fromUrl(url, directives, baseUri);
2130
}
2231
return null;
2332
}
@@ -43,12 +52,15 @@ class ShadowDomComponentFactory implements ComponentFactory {
4352
final dom.NodeTreeSanitizer treeSanitizer;
4453
final Expando expando;
4554
final CompilerConfig config;
55+
final TypeToUriMapper uriMapper;
56+
final ResourceUrlResolver resourceResolver;
4657

4758
final Map<_ComponentAssetKey, async.Future<dom.StyleElement>> styleElementCache = {};
4859

4960
ShadowDomComponentFactory(this.viewCache, this.http, this.templateCache, this.platform,
5061
this.componentCssRewriter, this.treeSanitizer, this.expando,
51-
this.config, CacheRegister cacheRegister) {
62+
this.config, this.uriMapper, this.resourceResolver,
63+
CacheRegister cacheRegister) {
5264
cacheRegister.registerCache("ShadowDomComponentFactoryStyles", styleElementCache);
5365
}
5466

@@ -76,10 +88,16 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
7688
_viewFuture = BoundComponentFactory._viewFuture(
7789
_component,
7890
new PlatformViewCache(_componentFactory.viewCache, _tag, _componentFactory.platform),
79-
_directives);
91+
_directives,
92+
_componentFactory.uriMapper,
93+
_componentFactory.resourceResolver,
94+
_ref.type);
8095
}
8196

82-
async.Future<dom.StyleElement> _styleFuture(cssUrl) {
97+
async.Future<dom.StyleElement> _styleFuture(cssUrl, {resolveUri: true}) {
98+
if (resolveUri)
99+
cssUrl = _componentFactory.resourceResolver.combineWithType(_ref.type, cssUrl);
100+
83101
Http http = _componentFactory.http;
84102
TemplateCache templateCache = _componentFactory.templateCache;
85103
WebPlatform platform = _componentFactory.platform;
@@ -89,7 +107,7 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
89107
return _componentFactory.styleElementCache.putIfAbsent(
90108
new _ComponentAssetKey(_tag, cssUrl), () =>
91109
http.get(cssUrl, cache: templateCache)
92-
.then((resp) => resp.responseText,
110+
.then((resp) => _componentFactory.resourceResolver.resolveCssText(resp.responseText, Uri.parse(cssUrl)),
93111
onError: (e) => '/*\n$e\n*/\n')
94112
.then((String css) {
95113

@@ -135,7 +153,9 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
135153

136154
async.Future<Iterable<dom.StyleElement>> cssFuture;
137155
if (_component.useNgBaseCss == true) {
138-
cssFuture = async.Future.wait([async.Future.wait(baseCss.urls.map(_styleFuture)), _styleElementsFuture]).then((twoLists) {
156+
cssFuture = async.Future.wait([async.Future.wait(baseCss.urls.map(
157+
(cssUrl) => _styleFuture(cssUrl, resolveUri: false))), _styleElementsFuture])
158+
.then((twoLists) {
139159
assert(twoLists.length == 2);return []
140160
..addAll(twoLists[0])
141161
..addAll(twoLists[1]);

lib/core_dom/transcluding_component_factory.dart

+8-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ class TranscludingComponentFactory implements ComponentFactory {
66
final Expando expando;
77
final ViewCache viewCache;
88
final CompilerConfig config;
9+
final TypeToUriMapper uriMapper;
10+
final ResourceUrlResolver resourceResolver;
911

10-
TranscludingComponentFactory(this.expando, this.viewCache, this.config);
12+
TranscludingComponentFactory(this.expando, this.viewCache, this.config,
13+
this.uriMapper, this.resourceResolver);
1114

1215
bind(DirectiveRef ref, directives, injector) =>
1316
new BoundTranscludingComponentFactory(this, ref, directives, injector);
@@ -26,7 +29,10 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory {
2629
_viewFuture = BoundComponentFactory._viewFuture(
2730
_component,
2831
_f.viewCache,
29-
_directives);
32+
_directives,
33+
_f.uriMapper,
34+
_f.resourceResolver,
35+
_ref.type);
3036
}
3137

3238
List<Key> get callArgs => _CALL_ARGS;

0 commit comments

Comments
 (0)