refactor(template loading): add support for base URLs, css rewriting

fixes #654
This commit is contained in:
Victor Berchet
2015-02-24 16:05:45 +01:00
parent 26872f60e6
commit 929fc65493
42 changed files with 1147 additions and 634 deletions

View File

@ -19,6 +19,10 @@ import {XHRImpl} from 'angular2/src/core/compiler/xhr/xhr_impl';
import {EventManager, DomEventsPlugin} from 'angular2/src/core/events/event_manager';
import {HammerGesturesPlugin} from 'angular2/src/core/events/hammer_gestures';
import {Binding} from 'angular2/src/di/binding';
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
import {StyleInliner} from 'angular2/src/core/compiler/style_inliner';
var _rootInjector: Injector;
@ -78,7 +82,7 @@ function _injectorBindings(appComponentType): List<Binding> {
var plugins = [new HammerGesturesPlugin(), new DomEventsPlugin()];
return new EventManager(plugins, zone);
}, [VmTurnZone]),
bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy()),
bind(ShadowDomStrategy).toClass(NativeShadowDomStrategy),
Compiler,
CompilerCache,
TemplateResolver,
@ -89,6 +93,10 @@ function _injectorBindings(appComponentType): List<Binding> {
Lexer,
ExceptionHandler,
bind(XHR).toValue(new XHRImpl()),
ComponentUrlMapper,
UrlResolver,
StyleUrlResolver,
StyleInliner,
];
}

View File

@ -16,6 +16,8 @@ import {DirectiveMetadata} from './directive_metadata';
import {Template} from '../annotations/template';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {CompileStep} from './pipeline/compile_step';
import {ComponentUrlMapper} from './component_url_mapper';
import {UrlResolver} from './url_resolver';
/**
@ -57,6 +59,9 @@ export class Compiler {
_shadowDomStrategy: ShadowDomStrategy;
_shadowDomDirectives: List<DirectiveMetadata>;
_templateResolver: TemplateResolver;
_componentUrlMapper: ComponentUrlMapper;
_urlResolver: UrlResolver;
_appUrl: string;
constructor(changeDetection:ChangeDetection,
templateLoader:TemplateLoader,
@ -64,7 +69,9 @@ export class Compiler {
parser:Parser,
cache:CompilerCache,
shadowDomStrategy: ShadowDomStrategy,
templateResolver: TemplateResolver) {
templateResolver: TemplateResolver,
componentUrlMapper: ComponentUrlMapper,
urlResolver: UrlResolver) {
this._changeDetection = changeDetection;
this._reader = reader;
this._parser = parser;
@ -78,6 +85,9 @@ export class Compiler {
ListWrapper.push(this._shadowDomDirectives, reader.read(types[i]));
}
this._templateResolver = templateResolver;
this._componentUrlMapper = componentUrlMapper;
this._urlResolver = urlResolver;
this._appUrl = urlResolver.resolve(null, './');
}
createSteps(component:Type, template: Template):List<CompileStep> {
@ -90,8 +100,10 @@ export class Compiler {
var cmpMetadata = this._reader.read(component);
var templateUrl = this._templateLoader.getTemplateUrl(template);
return createDefaultSteps(this._changeDetection, this._parser, cmpMetadata, dirMetadata,
this._shadowDomStrategy);
this._shadowDomStrategy, templateUrl);
}
compile(component: Type):Promise<ProtoView> {
@ -118,6 +130,10 @@ export class Compiler {
var template = this._templateResolver.resolve(component);
var componentUrl = this._componentUrlMapper.getUrl(component);
var baseUrl = this._urlResolver.resolve(this._appUrl, componentUrl);
this._templateLoader.setBaseUrl(template, baseUrl);
var tplElement = this._templateLoader.load(template);
if (PromiseWrapper.isPromise(tplElement)) {
@ -160,6 +176,12 @@ export class Compiler {
}
}
if (protoView.stylePromises.length > 0) {
// The protoView is ready after all asynchronous styles are ready
var syncProtoView = protoView;
protoView = PromiseWrapper.all(syncProtoView.stylePromises).then((_) => syncProtoView);
}
if (nestedPVPromises.length > 0) {
// Returns ProtoView Promise when there are any asynchronous nested ProtoViews.
// The promise will resolved after nested ProtoViews are compiled.
@ -169,7 +191,6 @@ export class Compiler {
);
}
// When there is no asynchronous nested ProtoViews, return the ProtoView
return protoView;
}

View File

@ -1,11 +1,11 @@
import {Type, isPresent} from 'angular2/src/facade/lang';
import {Map, MapWrapper} from 'angular2/src/facade/lang';
import {Map, MapWrapper} from 'angular2/src/facade/collection';
export class ComponentUrlMapper {
// Returns the url to the component source file.
// The returned url could be:
// Returns the base URL to the component source file.
// The returned URL could be:
// - an absolute URL,
// - a URL relative to the application
// - a path relative to the application
getUrl(component: Type): string {
return './';
}

View File

@ -9,11 +9,10 @@ import {ElementBindingMarker} from './element_binding_marker';
import {ProtoViewBuilder} from './proto_view_builder';
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
import {ElementBinderBuilder} from './element_binder_builder';
import {ShimShadowCss} from './shim_shadow_css';
import {ResolveCss} from './resolve_css';
import {ShimShadowDom} from './shim_shadow_dom';
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
import {ShadowDomStrategy, EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {DOM} from 'angular2/src/facade/dom';
/**
* Default steps used for compiling a template.
@ -25,24 +24,20 @@ export function createDefaultSteps(
parser:Parser,
compiledComponent: DirectiveMetadata,
directives: List<DirectiveMetadata>,
shadowDomStrategy: ShadowDomStrategy) {
shadowDomStrategy: ShadowDomStrategy,
templateUrl: string) {
var steps = [new ViewSplitter(parser)];
if (shadowDomStrategy instanceof EmulatedShadowDomStrategy) {
var step = new ShimShadowCss(compiledComponent, shadowDomStrategy, DOM.defaultDoc().head);
ListWrapper.push(steps, step);
}
steps = ListWrapper.concat(steps,[
var steps = [
new ViewSplitter(parser),
new ResolveCss(compiledComponent, shadowDomStrategy, templateUrl),
new PropertyBindingParser(parser),
new DirectiveParser(directives),
new TextInterpolationParser(parser),
new ElementBindingMarker(),
new ProtoViewBuilder(changeDetection, shadowDomStrategy),
new ProtoElementInjectorBuilder(),
new ElementBinderBuilder(parser)
]);
new ElementBinderBuilder(parser),
]
if (shadowDomStrategy instanceof EmulatedShadowDomStrategy) {
var step = new ShimShadowDom(compiledComponent, shadowDomStrategy);

View File

@ -0,0 +1,47 @@
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {DOM} from 'angular2/src/facade/dom';
import {Type} from 'angular2/src/facade/lang';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {ListWrapper} from 'angular2/src/facade/collection';
export class ResolveCss extends CompileStep {
_strategy: ShadowDomStrategy;
_component: Type;
_templateUrl: string;
constructor(cmpMetadata: DirectiveMetadata, strategy: ShadowDomStrategy, templateUrl: string) {
super();
this._strategy = strategy;
this._component = cmpMetadata.type;
this._templateUrl = templateUrl;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
// May be remove the styles
if (DOM.tagName(current.element) == 'STYLE') {
current.ignoreBindings = true;
var styleEl = current.element;
var css = DOM.getText(styleEl);
css = this._strategy.transformStyleText(css, this._templateUrl, this._component);
if (PromiseWrapper.isPromise(css)) {
ListWrapper.push(parent.inheritedProtoView.stylePromises, css);
DOM.setText(styleEl, '');
css.then((css) => {
DOM.setText(styleEl, css);
})
} else {
DOM.setText(styleEl, css);
}
this._strategy.handleStyleElement(styleEl);
}
}
}

View File

@ -1,55 +0,0 @@
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {DOM, Element, StyleElement} from 'angular2/src/facade/dom';
import {isPresent, isBlank, Type} from 'angular2/src/facade/lang';
export class ShimShadowCss extends CompileStep {
_strategy: ShadowDomStrategy;
_styleHost: Element;
_lastInsertedStyle: Element;
_component: Type;
constructor(cmpMetadata: DirectiveMetadata, strategy: ShadowDomStrategy, styleHost: Element) {
super();
this._strategy = strategy;
this._component = cmpMetadata.type;
this._styleHost = styleHost;
this._lastInsertedStyle = null;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
// May be remove the styles
if (DOM.tagName(current.element) == 'STYLE') {
current.ignoreBindings = true;
if (this._strategy.extractStyles()) {
var styleEl = current.element;
DOM.remove(styleEl);
var css = DOM.getText(styleEl);
var shimComponent = this._strategy.getShimComponent(this._component);
css = shimComponent.shimCssText(css);
DOM.setText(styleEl, css);
this._insertStyle(this._styleHost, styleEl);
}
}
}
_insertStyle(host: Element, style: StyleElement) {
if (isBlank(this._lastInsertedStyle)) {
var firstChild = DOM.firstChild(host);
if (isPresent(firstChild)) {
DOM.insertBefore(firstChild, style);
} else {
DOM.appendChild(host, style);
}
} else {
DOM.insertAfter(this._lastInsertedStyle, style);
}
this._lastInsertedStyle = style;
}
}

View File

@ -2,21 +2,19 @@ import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {isPresent} from 'angular2/src/facade/lang';
import {isPresent, Type} from 'angular2/src/facade/lang';
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
import {ShimComponent} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_component';
export class ShimShadowDom extends CompileStep {
_strategy: ShadowDomStrategy;
_shimComponent: ShimComponent;
_component: Type;
constructor(cmpMetadata: DirectiveMetadata, strategy: ShadowDomStrategy) {
super();
this._strategy = strategy;
this._shimComponent = strategy.getShimComponent(cmpMetadata.type);
this._component = cmpMetadata.type;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
@ -25,13 +23,12 @@ export class ShimShadowDom extends CompileStep {
}
// Shim the element as a child of the compiled component
this._shimComponent.shimContentElement(current.element);
this._strategy.shimContentElement(this._component, current.element);
// If the current element is also a component, shim it as a host
var host = current.componentDirective;
if (isPresent(host)) {
var shimComponent = this._strategy.getShimComponent(host.type);
shimComponent.shimHostElement(current.element);
this._strategy.shimHostElement(host.type, current.element);
}
}
}

View File

@ -1,99 +0,0 @@
import {Element, DOM} from 'angular2/src/facade/dom';
import {Map, MapWrapper} from 'angular2/src/facade/collection';
import {int, isBlank, Type} from 'angular2/src/facade/lang';
import {ShadowCss} from './shadow_css';
/**
* Used to shim component CSS & DOM
*/
export class ShimComponent {
constructor(component: Type) {
}
shimCssText(cssText: string): string {
return null
}
shimContentElement(element: Element) {}
shimHostElement(element: Element) {}
}
/**
* Native components does not need to the shim.
*
* All methods are no-ops.
*/
export class ShimNativeComponent extends ShimComponent {
constructor(component: Type) {
super(component);
};
shimCssText(cssText: string): string {
return cssText;
}
shimContentElement(element: Element) {
}
shimHostElement(element: Element) {
}
}
var _componentCache: Map<Type, int> = MapWrapper.create();
var _componentId: int = 0;
// Reset the component cache - used for tests only
export function resetShimComponentCache() {
MapWrapper.clear(_componentCache);
_componentId = 0;
}
/**
* Emulated components need to be shimmed:
* - An attribute needs to be added to the host,
* - An attribute needs to be added to all nodes in their content,
* - The CSS needs to be scoped.
*/
export class ShimEmulatedComponent extends ShimComponent {
_cmpId: int;
constructor(component: Type) {
super(component);
// Generates a unique ID for components
var componentId = MapWrapper.get(_componentCache, component);
if (isBlank(componentId)) {
componentId = _componentId++;
MapWrapper.set(_componentCache, component, componentId);
}
this._cmpId = componentId;
};
// Scope the CSS
shimCssText(cssText: string): string {
var shadowCss = new ShadowCss();
return shadowCss.shimCssText(cssText, this._getContentAttribute(), this._getHostAttribute());
}
// Add an attribute on a content element
shimContentElement(element: Element) {
DOM.setAttribute(element, this._getContentAttribute(), '');
}
// Add an attribute to the host
shimHostElement(element: Element) {
DOM.setAttribute(element, this._getHostAttribute(), '');
}
// Return the attribute to be added to the component
_getHostAttribute() {
return `_nghost-${this._cmpId}`;
}
// Returns the attribute to be added on every single nodes in the component
_getContentAttribute() {
return `_ngcontent-${this._cmpId}`;
}
}

View File

@ -1,31 +1,44 @@
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
import {DOM, Element} from 'angular2/src/facade/dom';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {Type, isBlank, isPresent, int} from 'angular2/src/facade/lang';
import {DOM, Element, StyleElement} from 'angular2/src/facade/dom';
import {List, ListWrapper, MapWrapper, Map} from 'angular2/src/facade/collection';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {View} from './view';
import {Content} from './shadow_dom_emulation/content_tag';
import {LightDom} from './shadow_dom_emulation/light_dom';
import {ShimComponent, ShimEmulatedComponent, ShimNativeComponent} from './shadow_dom_emulation/shim_component';
import {ShadowCss} from './shadow_dom_emulation/shadow_css';
import {StyleInliner} from './style_inliner';
import {StyleUrlResolver} from './style_url_resolver';
export class ShadowDomStrategy {
attachTemplate(el:Element, view:View){}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
polyfillDirectives():List<Type>{ return null; }
extractStyles(): boolean { return false; }
getShimComponent(component: Type): ShimComponent {
return null;
}
attachTemplate(el:Element, view:View) {}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element) {}
polyfillDirectives():List<Type> { return null; }
// TODO(vicb): union types: return either a string or a Promise<string>
transformStyleText(cssText: string, baseUrl: string, component: Type) {}
handleStyleElement(styleEl: StyleElement) {};
shimContentElement(component: Type, element: Element) {}
shimHostElement(component: Type, element: Element) {}
}
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
constructor() {
_styleInliner: StyleInliner;
_styleUrlResolver: StyleUrlResolver;
_styleHost: Element;
_lastInsertedStyle: StyleElement;
constructor(styleInliner: StyleInliner, styleUrlResolver: StyleUrlResolver, styleHost: Element) {
super();
this._styleInliner = styleInliner;
this._styleUrlResolver = styleUrlResolver;
this._styleHost = styleHost;
}
attachTemplate(el:Element, view:View){
DOM.clearNodes(el);
moveViewNodesIntoParent(el, view);
_moveViewNodesIntoParent(el, view);
}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){
@ -36,22 +49,58 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
return [Content];
}
extractStyles(): boolean {
return true;
transformStyleText(cssText: string, baseUrl: string, component: Type) {
cssText = this._styleUrlResolver.resolveUrls(cssText, baseUrl);
var css = this._styleInliner.inlineImports(cssText, baseUrl);
if (PromiseWrapper.isPromise(css)) {
return css.then((css) => _shimCssForComponent(css, component));
} else {
return _shimCssForComponent(css, component);
}
}
getShimComponent(component: Type): ShimComponent {
return new ShimEmulatedComponent(component);
handleStyleElement(styleEl: StyleElement) {
DOM.remove(styleEl);
this._insertStyleElement(this._styleHost, styleEl);
};
shimContentElement(component: Type, element: Element) {
var id = _getComponentId(component);
var attrName = _getContentAttribute(id);
DOM.setAttribute(element, attrName, '');
}
shimHostElement(component: Type, element: Element) {
var id = _getComponentId(component);
var attrName = _getHostAttribute(id);
DOM.setAttribute(element, attrName, '');
}
_insertStyleElement(host: Element, style: StyleElement) {
if (isBlank(this._lastInsertedStyle)) {
var firstChild = DOM.firstChild(host);
if (isPresent(firstChild)) {
DOM.insertBefore(firstChild, style);
} else {
DOM.appendChild(host, style);
}
} else {
DOM.insertAfter(this._lastInsertedStyle, style);
}
this._lastInsertedStyle = style;
}
}
export class NativeShadowDomStrategy extends ShadowDomStrategy {
constructor() {
_styleUrlResolver: StyleUrlResolver;
constructor(styleUrlResolver: StyleUrlResolver) {
super();
this._styleUrlResolver = styleUrlResolver;
}
attachTemplate(el:Element, view:View){
moveViewNodesIntoParent(el.createShadowRoot(), view);
_moveViewNodesIntoParent(DOM.createShadowRoot(el), view);
}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){
@ -62,17 +111,47 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
return [];
}
extractStyles(): boolean {
return false;
}
getShimComponent(component: Type): ShimComponent {
return new ShimNativeComponent(component);
transformStyleText(cssText: string, baseUrl: string, component: Type) {
return this._styleUrlResolver.resolveUrls(cssText, baseUrl);
}
}
function moveViewNodesIntoParent(parent, view) {
function _moveViewNodesIntoParent(parent, view) {
for (var i = 0; i < view.nodes.length; ++i) {
DOM.appendChild(parent, view.nodes[i]);
}
}
var _componentUIDs: Map<Type, int> = MapWrapper.create();
var _nextComponentUID: int = 0;
function _getComponentId(component: Type) {
var id = MapWrapper.get(_componentUIDs, component);
if (isBlank(id)) {
id = _nextComponentUID++;
MapWrapper.set(_componentUIDs, component, id);
}
return id;
}
// Return the attribute to be added to the component
function _getHostAttribute(id: int) {
return `_nghost-${id}`;
}
// Returns the attribute to be added on every single nodes in the component
function _getContentAttribute(id: int) {
return `_ngcontent-${id}`;
}
function _shimCssForComponent(cssText: string, component: Type): string {
var id = _getComponentId(component);
var shadowCss = new ShadowCss();
return shadowCss.shimCssText(cssText, _getContentAttribute(id), _getHostAttribute(id));
}
// Reset the component cache - used for tests only
export function resetShadowDomCache() {
MapWrapper.clear(_componentUIDs);
_nextComponentUID = 0;
}

View File

@ -1,8 +1,11 @@
import {XHR} from 'angular2/src/core/compiler/xhr/xhr';
import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver';
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
import {ListWrapper} from 'angular2/src/facade/collection';
import {
isBlank,
isPresent,
RegExp,
RegExpWrapper,
StringWrapper,
@ -13,20 +16,38 @@ import {
PromiseWrapper,
} from 'angular2/src/facade/async';
/**
* Inline @import rules in the given CSS.
*
* When an @import rules is inlined, it's url are rewritten.
*/
export class StyleInliner {
_xhr: XHR;
_urlResolver: UrlResolver;
_styleUrlResolver: StyleUrlResolver;
constructor(xhr: XHR) {
constructor(xhr: XHR, styleUrlResolver: StyleUrlResolver, urlResolver: UrlResolver) {
this._xhr = xhr;
this._urlResolver = urlResolver;
this._styleUrlResolver = styleUrlResolver;
}
// TODO(vicb): handle base url
/**
* Inline the @imports rules in the given CSS text.
*
* The baseUrl is required to rewrite URLs in the inlined content.
*
* @param {string} cssText
* @param {string} baseUrl
* @returns {*} a Promise<string> when @import rules are present, a string otherwise
*/
// TODO(vicb): Union types: returns either a Promise<string> or a string
inlineImports(cssText: string) {
return this._inlineImports(cssText, []);
// TODO(vicb): commented out @import rules should not be inlined
inlineImports(cssText: string, baseUrl: string) {
return this._inlineImports(cssText, baseUrl, []);
}
_inlineImports(cssText: string, inlinedUrls: List<string>) {
_inlineImports(cssText: string, baseUrl: string, inlinedUrls: List<string>) {
var partIndex = 0;
var parts = StringWrapper.split(cssText, _importRe);
@ -38,13 +59,20 @@ export class StyleInliner {
var promises = [];
while (partIndex < parts.length - 1) {
// prefix is the content before the @import rule
var prefix = parts[partIndex];
// rule is the parameter of the @import rule
var rule = parts[partIndex + 1];
var url = _extractUrl(rule);
if (isPresent(url)) {
url = this._urlResolver.resolve(baseUrl, url);
}
var mediaQuery = _extractMediaQuery(rule);
var promise;
if (isBlank(url) || ListWrapper.contains(inlinedUrls, url)) {
if (isBlank(url)) {
promise = PromiseWrapper.resolve(`/* Invalid import rule: "@import ${rule};" */`);
} else if (ListWrapper.contains(inlinedUrls, url)) {
// The current import rule has already been inlined, return the prefix only
// Importing again might cause a circular dependency
promise = PromiseWrapper.resolve(prefix);
@ -54,13 +82,15 @@ export class StyleInliner {
this._xhr.get(url),
(css) => {
// resolve nested @import rules
css = this._inlineImports(css, inlinedUrls);
css = this._inlineImports(css, url, inlinedUrls);
if (PromiseWrapper.isPromise(css)) {
// wait until nested @import are inlined
return css.then((css) => prefix + _wrapInMediaRule(css, mediaQuery)+ '\n') ;
return css.then((css) => {
return prefix + this._transformImportedCss(css, mediaQuery, url) + '\n'
}) ;
} else {
// there are no nested @import, return the css
return prefix + _wrapInMediaRule(css, mediaQuery) + '\n';
return prefix + this._transformImportedCss(css, mediaQuery, url) + '\n';
}
},
(error) => `/* failed to import ${url} */\n`
@ -70,20 +100,19 @@ export class StyleInliner {
partIndex += 2;
}
return PromiseWrapper.then(
PromiseWrapper.all(promises),
function (cssParts) {
var cssText = cssParts.join('');
if (partIndex < parts.length) {
// append whatever css located after the last @import rule
cssText += parts[partIndex];
}
return cssText;
},
function(e) {
throw 'error';
return PromiseWrapper.all(promises).then(function (cssParts) {
var cssText = cssParts.join('');
if (partIndex < parts.length) {
// append then content located after the last @import rule
cssText += parts[partIndex];
}
);
return cssText;
});
}
_transformImportedCss(css: string, mediaQuery: string, url: string): string {
css = this._styleUrlResolver.resolveUrls(css, url);
return _wrapInMediaRule(css, mediaQuery);
}
}
@ -106,7 +135,7 @@ function _extractMediaQuery(importRule: string): string {
}
// Wraps the css in a media rule when the media query is not null
function _wrapInMediaRule(css: string, query: string) {
function _wrapInMediaRule(css: string, query: string): string {
return (isBlank(query)) ? css : `@media ${query} {\n${css}\n}`;
}

View File

@ -4,6 +4,9 @@
import {RegExp, RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
import {UrlResolver} from './url_resolver';
/**
* Rewrites URLs by resolving '@import' and 'url()' URLs from the given base URL.
*/
export class StyleUrlResolver {
_resolver: UrlResolver;
@ -31,5 +34,6 @@ export class StyleUrlResolver {
}
var _cssUrlRe = RegExpWrapper.create('(url\\()([^)]*)(\\))');
// TODO(vicb): handle the media query part
var _cssImportRe = RegExpWrapper.create('(@import[\\s]+(?!url\\())([^;]*)(;)');
var _quoteRe = RegExpWrapper.create('[\'"]');

View File

@ -1,21 +1,29 @@
import {isBlank, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
import {DOM, Element} from 'angular2/src/facade/dom';
import {StringMapWrapper, StringMap} from 'angular2/src/facade/collection';
import {Map, MapWrapper, StringMapWrapper, StringMap} from 'angular2/src/facade/collection';
import {XHR} from './xhr/xhr';
import {Template} from 'angular2/src/core/annotations/template';
import {UrlResolver} from './url_resolver';
/**
* Strategy to load component templates.
*/
export class TemplateLoader {
_xhr: XHR;
_cache: StringMap;
_htmlCache: StringMap;
_baseUrls: Map<Type, string>;
_urlCache: Map<Type, string>;
_urlResolver: UrlResolver;
constructor(xhr: XHR) {
constructor(xhr: XHR, urlResolver: UrlResolver) {
this._xhr = xhr;
this._cache = StringMapWrapper.create();
this._urlResolver = urlResolver;
this._htmlCache = StringMapWrapper.create();
this._baseUrls = MapWrapper.create();
this._urlCache = MapWrapper.create();
}
// TODO(vicb): union type: return an Element or a Promise<Element>
@ -25,20 +33,43 @@ export class TemplateLoader {
}
if (isPresent(template.url)) {
var url = template.url;
var promise = StringMapWrapper.get(this._cache, url);
var url = this.getTemplateUrl(template);
var promise = StringMapWrapper.get(this._htmlCache, url);
if (isBlank(promise)) {
promise = this._xhr.get(url).then(function (html) {
var template = DOM.createTemplate(html);
return template;
});
StringMapWrapper.set(this._cache, url, promise);
StringMapWrapper.set(this._htmlCache, url, promise);
}
return promise;
}
throw new BaseException(`Templates should have either their url or inline property set`);
throw new BaseException('Templates should have either their url or inline property set');
}
setBaseUrl(template: Template, baseUrl: string) {
MapWrapper.set(this._baseUrls, template, baseUrl);
MapWrapper.delete(this._urlCache, template);
}
getTemplateUrl(template: Template) {
if (!MapWrapper.contains(this._urlCache, template)) {
var baseUrl = MapWrapper.get(this._baseUrls, template);
if (isBlank(baseUrl)) {
throw new BaseException('The template base URL is not set');
}
var templateUrl;
if (isPresent(template.url)) {
templateUrl = this._urlResolver.resolve(baseUrl, template.url);
} else {
templateUrl = baseUrl;
}
MapWrapper.set(this._urlCache, template, templateUrl);
}
return MapWrapper.get(this._urlCache, template);
}
}

View File

@ -1,8 +1,8 @@
import {isPresent, isBlank, RegExpWrapper} from 'angular2/src/facade/lang';
import {DOM, Element} from 'angular2/src/facade/dom';
import {isPresent, isBlank, RegExpWrapper, BaseException} from 'angular2/src/facade/lang';
import {DOM, AnchorElement} from 'angular2/src/facade/dom';
export class UrlResolver {
static a: Element;
static a: AnchorElement;
constructor() {
if (isBlank(UrlResolver.a)) {

View File

@ -1,4 +1,5 @@
import {DOM, Element, Node, Text, DocumentFragment} from 'angular2/src/facade/dom';
import {Promise} from 'angular2/src/facade/async';
import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {AST, ContextWithVariableBindings, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, ChangeRecord}
from 'angular2/change_detection';
@ -273,6 +274,8 @@ export class ProtoView {
isTemplateElement:boolean;
shadowDomStrategy: ShadowDomStrategy;
_viewPool: ViewPool;
stylePromises: List<Promise>;
constructor(
template:Element,
protoChangeDetector:ProtoChangeDetector,
@ -290,6 +293,7 @@ export class ProtoView {
this.isTemplateElement = DOM.isTemplateElement(this.element);
this.shadowDomStrategy = shadowDomStrategy;
this._viewPool = new ViewPool(VIEW_POOL_CAPACITY);
this.stylePromises = [];
}
// TODO(rado): hostElementInjector should be moved to hydrate phase.
@ -539,8 +543,7 @@ export class ProtoView {
new ProtoElementInjector(null, 0, [cmpType], true));
binder.componentDirective = rootComponentAnnotatedType;
binder.nestedProtoView = protoView;
var shimComponent = shadowDomStrategy.getShimComponent(cmpType);
shimComponent.shimHostElement(insertionElement);
shadowDomStrategy.shimHostElement(cmpType, insertionElement);
return rootProtoView;
}
}