refactor(Compiler): inline styles before compiling the template

This commit is contained in:
Victor Berchet 2015-06-15 15:57:42 +02:00
parent 3875f02a52
commit 3ea655918e
30 changed files with 288 additions and 468 deletions

View File

@ -25,6 +25,8 @@ import {
} from 'angular2/change_detection'; } from 'angular2/change_detection';
import {ExceptionHandler} from './exception_handler'; import {ExceptionHandler} from './exception_handler';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner';
import {TemplateResolver} from './compiler/template_resolver'; import {TemplateResolver} from './compiler/template_resolver';
import {DirectiveResolver} from './compiler/directive_resolver'; import {DirectiveResolver} from './compiler/directive_resolver';
import {List, ListWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper} from 'angular2/src/facade/collection';
@ -42,8 +44,6 @@ import {KeyEventsPlugin} from 'angular2/src/render/dom/events/key_events';
import {HammerGesturesPlugin} from 'angular2/src/render/dom/events/hammer_gestures'; import {HammerGesturesPlugin} from 'angular2/src/render/dom/events/hammer_gestures';
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {AppRootUrl} from 'angular2/src/services/app_root_url'; import {AppRootUrl} from 'angular2/src/services/app_root_url';
import { import {
ComponentRef, ComponentRef,
@ -105,9 +105,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
}, },
[NgZone]), [NgZone]),
bind(ShadowDomStrategy) bind(ShadowDomStrategy)
.toFactory((styleInliner, styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy( .toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
styleInliner, styleUrlResolver, doc.head),
[StyleInliner, StyleUrlResolver, DOCUMENT_TOKEN]),
DomRenderer, DomRenderer,
DefaultDomCompiler, DefaultDomCompiler,
bind(Renderer).toAlias(DomRenderer), bind(Renderer).toAlias(DomRenderer),

View File

@ -22,7 +22,7 @@ export class TemplateResolver {
return view; return view;
} }
_resolve(component: Type) { _resolve(component: Type): View {
var annotations = reflector.annotations(component); var annotations = reflector.annotations(component);
for (var i = 0; i < annotations.length; i++) { for (var i = 0; i < annotations.length; i++) {
var annotation = annotations[i]; var annotation = annotations[i];

View File

@ -67,10 +67,10 @@ class Html5LibDomAdapter implements DomAdapter {
throw 'not implemented'; throw 'not implemented';
} }
querySelector(el, String selector) { querySelector(el, String selector) {
throw 'not implemented'; return el.querySelector(selector);
} }
List querySelectorAll(el, String selector) { List querySelectorAll(el, String selector) {
throw 'not implemented'; return el.querySelectorAll(selector);
} }
on(el, evt, listener) { on(el, evt, listener) {
throw 'not implemented'; throw 'not implemented';
@ -94,7 +94,7 @@ class Html5LibDomAdapter implements DomAdapter {
return el.innerHtml; return el.innerHtml;
} }
getOuterHTML(el) { getOuterHTML(el) {
throw 'not implemented'; return el.outerHtml;
} }
String nodeName(node) { String nodeName(node) {
switch (node.nodeType) { switch (node.nodeType) {
@ -130,12 +130,12 @@ class Html5LibDomAdapter implements DomAdapter {
} }
parentElement(el) { parentElement(el) {
throw 'not implemented'; return el.parent;
} }
List childNodes(el) => el.nodes; List childNodes(el) => el.nodes;
List childNodesAsList(el) => el.nodes; List childNodesAsList(el) => el.nodes;
clearNodes(el) { clearNodes(el) {
throw 'not implemented'; el.nodes.forEach((e) => e.remove());
} }
appendChild(el, node) => el.append(node.remove()); appendChild(el, node) => el.append(node.remove());
removeChild(el, node) { removeChild(el, node) {
@ -153,7 +153,7 @@ class Html5LibDomAdapter implements DomAdapter {
throw 'not implemented'; throw 'not implemented';
} }
setInnerHTML(el, value) { setInnerHTML(el, value) {
throw 'not implemented'; el.innerHtml = value;
} }
getText(el) { getText(el) {
return el.text; return el.text;

View File

@ -303,7 +303,7 @@ export class RenderCompiler {
* we don't need to serialize all possible components over the wire, * we don't need to serialize all possible components over the wire,
* but only the needed ones based on previous calls. * but only the needed ones based on previous calls.
*/ */
compile(template: ViewDefinition): Promise<ProtoViewDto> { return null; } compile(view: ViewDefinition): Promise<ProtoViewDto> { return null; }
} }
export interface RenderElementRef { export interface RenderElementRef {

View File

@ -1,6 +1,4 @@
import {List} from 'angular2/src/facade/collection'; import {List} from 'angular2/src/facade/collection';
import {Promise} from 'angular2/src/facade/async';
import {Parser} from 'angular2/change_detection'; import {Parser} from 'angular2/change_detection';
import {ViewDefinition} from '../../api'; import {ViewDefinition} from '../../api';
import {CompileStep} from './compile_step'; import {CompileStep} from './compile_step';
@ -12,21 +10,19 @@ import {ShadowDomCompileStep} from '../shadow_dom/shadow_dom_compile_step';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
export class CompileStepFactory { export class CompileStepFactory {
createSteps(template: ViewDefinition, subTaskPromises: List<Promise<any>>): List<CompileStep> { createSteps(template: ViewDefinition): List<CompileStep> { return null; }
return null;
}
} }
export class DefaultStepFactory extends CompileStepFactory { export class DefaultStepFactory extends CompileStepFactory {
constructor(public _parser: Parser, public _shadowDomStrategy: ShadowDomStrategy) { super(); } constructor(public _parser: Parser, public _shadowDomStrategy: ShadowDomStrategy) { super(); }
createSteps(template: ViewDefinition, subTaskPromises: List<Promise<any>>) { createSteps(template: ViewDefinition): List<CompileStep> {
return [ return [
new ViewSplitter(this._parser), new ViewSplitter(this._parser),
new PropertyBindingParser(this._parser), new PropertyBindingParser(this._parser),
new DirectiveParser(this._parser, template.directives), new DirectiveParser(this._parser, template.directives),
new TextInterpolationParser(this._parser), new TextInterpolationParser(this._parser),
new ShadowDomCompileStep(this._shadowDomStrategy, template, subTaskPromises) new ShadowDomCompileStep(this._shadowDomStrategy, template)
]; ];
} }
} }

View File

@ -28,12 +28,11 @@ export class DomCompiler extends RenderCompiler {
super(); super();
} }
compile(template: ViewDefinition): Promise<ProtoViewDto> { compile(view: ViewDefinition): Promise<ProtoViewDto> {
var tplPromise = this._templateLoader.load(template); var tplPromise = this._templateLoader.load(view);
return PromiseWrapper.then( return PromiseWrapper.then(
tplPromise, (el) => this._compileTemplate(template, el, ViewType.COMPONENT), (e) => { tplPromise, (el) => this._compileTemplate(view, el, ViewType.COMPONENT), (e) => {
throw new BaseException( throw new BaseException(`Failed to load the template for "${view.componentId}" : ${e}`);
`Failed to load the template for "${template.componentId}" : ${e}`);
}); });
} }
@ -51,17 +50,10 @@ export class DomCompiler extends RenderCompiler {
_compileTemplate(viewDef: ViewDefinition, tplElement, _compileTemplate(viewDef: ViewDefinition, tplElement,
protoViewType: ViewType): Promise<ProtoViewDto> { protoViewType: ViewType): Promise<ProtoViewDto> {
var subTaskPromises = []; var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef));
var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef, subTaskPromises));
var compileElements = pipeline.process(tplElement, protoViewType, viewDef.componentId); var compileElements = pipeline.process(tplElement, protoViewType, viewDef.componentId);
var protoView = compileElements[0].inheritedProtoView.build(); return PromiseWrapper.resolve(compileElements[0].inheritedProtoView.build());
if (subTaskPromises.length > 0) {
return PromiseWrapper.all(subTaskPromises).then((_) => protoView);
} else {
return PromiseWrapper.resolve(protoView);
}
} }
} }

View File

@ -1,13 +1,15 @@
import {Injectable} from 'angular2/di'; import {Injectable} from 'angular2/di';
import {isBlank, isPresent, BaseException, stringify} from 'angular2/src/facade/lang'; import {isBlank, isPresent, BaseException, stringify, isPromise} from 'angular2/src/facade/lang';
import {Map, MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {Map, MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {XHR} from 'angular2/src/render/xhr'; import {XHR} from 'angular2/src/render/xhr';
import {ViewDefinition} from '../../api'; import {ViewDefinition} from '../../api';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleInliner} from './style_inliner';
import {StyleUrlResolver} from './style_url_resolver';
/** /**
* Strategy to load component templates. * Strategy to load component templates.
@ -17,37 +19,36 @@ import {UrlResolver} from 'angular2/src/services/url_resolver';
export class TemplateLoader { export class TemplateLoader {
_cache: Map<string, Promise<string>> = new Map(); _cache: Map<string, Promise<string>> = new Map();
constructor(private _xhr: XHR, urlResolver: UrlResolver) {} constructor(private _xhr: XHR, private _styleInliner: StyleInliner,
private _styleUrlResolver: StyleUrlResolver) {}
load(view: ViewDefinition): Promise</*element*/ any> { load(view: ViewDefinition): Promise</*element*/ any> {
let html; let tplElAndStyles: List<string | Promise<string>> = [this._loadHtml(view)];
let fetchedStyles;
// Load the HTML if (isPresent(view.styles)) {
if (isPresent(view.template)) { view.styles.forEach((cssText: string) => {
html = PromiseWrapper.resolve(view.template); let textOrPromise = this._resolveAndInlineCssText(cssText, view.templateAbsUrl);
} else if (isPresent(view.templateAbsUrl)) { tplElAndStyles.push(textOrPromise);
html = this._loadText(view.templateAbsUrl); });
} else {
throw new BaseException('View should have either the templateUrl or template property set');
} }
// Load the styles if (isPresent(view.styleAbsUrls)) {
if (isPresent(view.styleAbsUrls) && view.styleAbsUrls.length > 0) { view.styleAbsUrls.forEach(url => {
fetchedStyles = ListWrapper.map(view.styleAbsUrls, url => this._loadText(url)); let promise = this._loadText(url).then(
} else { cssText => this._resolveAndInlineCssText(cssText, view.templateAbsUrl));
fetchedStyles = []; tplElAndStyles.push(promise);
});
} }
// Inline the styles and return a template element // Inline the styles from the @View annotation and return a template element
return PromiseWrapper.all(ListWrapper.concat([html], fetchedStyles)) return PromiseWrapper.all(tplElAndStyles)
.then((res: List<string>) => { .then((res: List<string>) => {
let html = res[0]; let tplEl = res[0];
let fetchedStyles = ListWrapper.slice(res, 1); let cssTexts = ListWrapper.slice(res, 1);
html = _createStyleTags(view.styles) + _createStyleTags(fetchedStyles) + html; _insertCssTexts(DOM.content(tplEl), cssTexts);
return DOM.createTemplate(html); return tplEl;
}); });
} }
@ -67,10 +68,74 @@ export class TemplateLoader {
return response; return response;
} }
// Load the html and inline any style tags
private _loadHtml(view: ViewDefinition): Promise<any /* element */> {
let html;
// Load the HTML
if (isPresent(view.template)) {
html = PromiseWrapper.resolve(view.template);
} else if (isPresent(view.templateAbsUrl)) {
html = this._loadText(view.templateAbsUrl);
} else {
throw new BaseException('View should have either the templateUrl or template property set');
} }
function _createStyleTags(styles?: List<string>): string { // Inline the style tags from the html
return isBlank(styles) ? return html.then(html => {
'' : var tplEl = DOM.createTemplate(html);
ListWrapper.map(styles, css => `<style type='text/css'>${css}</style>`).join(''); let styleEls = DOM.querySelectorAll(DOM.content(tplEl), 'STYLE');
let promises: List<Promise<string>> = [];
for (let i = 0; i < styleEls.length; i++) {
let promise = this._resolveAndInlineElement(styleEls[i], view.templateAbsUrl);
if (isPromise(promise)) {
promises.push(promise);
}
}
return promises.length > 0 ? PromiseWrapper.all(promises).then(_ => tplEl) : tplEl;
});
}
/**
* Inlines a style element.
*
* @param styleEl The style element
* @param baseUrl The base url
* @returns {Promise<any>} null when no @import rule exist in the css or a Promise
* @private
*/
private _resolveAndInlineElement(styleEl, baseUrl: string): Promise<any> {
let textOrPromise = this._resolveAndInlineCssText(DOM.getText(styleEl), baseUrl);
if (isPromise(textOrPromise)) {
return (<Promise<string>>textOrPromise).then(css => { DOM.setText(styleEl, css); });
} else {
DOM.setText(styleEl, <string>textOrPromise);
return null;
}
}
private _resolveAndInlineCssText(cssText: string, baseUrl: string): string | Promise<string> {
cssText = this._styleUrlResolver.resolveUrls(cssText, baseUrl);
return this._styleInliner.inlineImports(cssText, baseUrl);
}
}
function _insertCssTexts(element, cssTexts: List<string>): void {
if (cssTexts.length == 0) return;
let insertBefore = DOM.firstChild(element);
for (let i = cssTexts.length - 1; i >= 0; i--) {
let styleEl = DOM.createStyleElement(cssTexts[i]);
if (isPresent(insertBefore)) {
DOM.insertBefore(insertBefore, styleEl);
} else {
DOM.appendChild(element, styleEl);
}
insertBefore = styleEl;
}
} }

View File

@ -1,10 +1,7 @@
import {isBlank, isPresent, isPromise} from 'angular2/src/facade/lang'; import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {EmulatedUnscopedShadowDomStrategy} from './emulated_unscoped_shadow_dom_strategy'; import {EmulatedUnscopedShadowDomStrategy} from './emulated_unscoped_shadow_dom_strategy';
import { import {
getContentAttribute, getContentAttribute,
@ -27,39 +24,21 @@ import {
* - see `ShadowCss` for more information and limitations. * - see `ShadowCss` for more information and limitations.
*/ */
export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomStrategy { export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomStrategy {
constructor(styleInliner: StyleInliner, styleUrlResolver: StyleUrlResolver, styleHost) { constructor(styleHost) { super(styleHost); }
super(styleInliner, styleUrlResolver, styleHost);
}
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): Promise<any> {
var cssText = DOM.getText(styleEl);
cssText = this.styleUrlResolver.resolveUrls(cssText, templateUrl);
var inlinedCss = this.styleInliner.inlineImports(cssText, templateUrl);
var ret = null;
if (isPromise(inlinedCss)) {
DOM.setText(styleEl, '');
ret = (<Promise<string>>inlinedCss)
.then((css) => {
css = shimCssForComponent(css, hostComponentId);
DOM.setText(styleEl, css);
});
} else {
var css = shimCssForComponent(<string>inlinedCss, hostComponentId);
DOM.setText(styleEl, css);
}
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): void {
let cssText = DOM.getText(styleEl);
cssText = shimCssForComponent(cssText, hostComponentId);
DOM.setText(styleEl, cssText);
this._moveToStyleHost(styleEl); this._moveToStyleHost(styleEl);
return ret;
} }
_moveToStyleHost(styleEl) { _moveToStyleHost(styleEl): void {
DOM.remove(styleEl); DOM.remove(styleEl);
insertStyleElement(this.styleHost, styleEl); insertStyleElement(this.styleHost, styleEl);
} }
processElement(hostComponentId: string, elementComponentId: string, element) { processElement(hostComponentId: string, elementComponentId: string, element): void {
// Shim the element as a child of the compiled component // Shim the element as a child of the compiled component
if (isPresent(hostComponentId)) { if (isPresent(hostComponentId)) {
var contentAttribute = getContentAttribute(getComponentId(hostComponentId)); var contentAttribute = getContentAttribute(getComponentId(hostComponentId));

View File

@ -1,14 +1,9 @@
import {isPromise} from 'angular2/src/facade/lang';
import {Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import * as viewModule from '../view/view'; import * as viewModule from '../view/view';
import {LightDom} from './light_dom'; import {LightDom} from './light_dom';
import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ShadowDomStrategy} from './shadow_dom_strategy';
import {StyleUrlResolver} from './style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {insertSharedStyleText} from './util'; import {insertSharedStyleText} from './util';
/** /**
@ -21,10 +16,7 @@ import {insertSharedStyleText} from './util';
* - you can **not** use shadow DOM specific selectors in the styles * - you can **not** use shadow DOM specific selectors in the styles
*/ */
export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy { export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
constructor(public styleInliner: StyleInliner, public styleUrlResolver: StyleUrlResolver, constructor(public styleHost) { super(); }
public styleHost) {
super();
}
hasNativeContentElement(): boolean { return false; } hasNativeContentElement(): boolean { return false; }
@ -34,21 +26,8 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
return new LightDom(lightDomView, el); return new LightDom(lightDomView, el);
} }
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): Promise<any> { processStyleElement(hostComponentId: string, templateUrl: string, styleEl): void {
var cssText = DOM.getText(styleEl); var cssText = DOM.getText(styleEl);
cssText = this.styleUrlResolver.resolveUrls(cssText, templateUrl);
var inlinedCss = this.styleInliner.inlineImports(cssText, templateUrl);
var ret = null;
if (isPromise(inlinedCss)) {
DOM.setText(styleEl, '');
ret = (<Promise<string>>inlinedCss).then(css => { DOM.setText(styleEl, css); });
} else {
DOM.setText(styleEl, <string>inlinedCss);
}
insertSharedStyleText(cssText, this.styleHost, styleEl); insertSharedStyleText(cssText, this.styleHost, styleEl);
return ret;
} }
} }

View File

@ -1,12 +1,6 @@
import {isPromise} from 'angular2/src/facade/lang';
import {Promise} from 'angular2/src/facade/async';
import {Injectable} from 'angular2/di'; import {Injectable} from 'angular2/di';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {StyleUrlResolver} from './style_url_resolver';
import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ShadowDomStrategy} from './shadow_dom_strategy';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
/** /**
* This strategies uses the native Shadow DOM support. * This strategies uses the native Shadow DOM support.
@ -16,23 +10,5 @@ import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
*/ */
@Injectable() @Injectable()
export class NativeShadowDomStrategy extends ShadowDomStrategy { export class NativeShadowDomStrategy extends ShadowDomStrategy {
constructor(public styleInliner: StyleInliner, public styleUrlResolver: StyleUrlResolver) {
super();
}
prepareShadowRoot(el) { return DOM.createShadowRoot(el); } prepareShadowRoot(el) { return DOM.createShadowRoot(el); }
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): Promise<any> {
var cssText = DOM.getText(styleEl);
cssText = this.styleUrlResolver.resolveUrls(cssText, templateUrl);
var inlinedCss = this.styleInliner.inlineImports(cssText, templateUrl);
if (isPromise(inlinedCss)) {
return (<Promise<string>>inlinedCss).then(css => { DOM.setText(styleEl, css); });
} else {
DOM.setText(styleEl, <string>inlinedCss);
return null;
}
}
} }

View File

@ -11,7 +11,7 @@ import {
} from 'angular2/src/facade/lang'; } from 'angular2/src/facade/lang';
/** /**
* This file is a port of shadowCSS from webcomponents.js to AtScript. * This file is a port of shadowCSS from webcomponents.js to TypeScript.
* *
* Please make sure to keep to edits in sync with the source file. * Please make sure to keep to edits in sync with the source file.
* *

View File

@ -1,6 +1,4 @@
import {isBlank, isPresent, assertionsEnabled, isPromise} from 'angular2/src/facade/lang'; import {isBlank, isPresent, assertionsEnabled, isPromise} from 'angular2/src/facade/lang';
import {MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
@ -11,8 +9,7 @@ import {ViewDefinition} from '../../api';
import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ShadowDomStrategy} from './shadow_dom_strategy';
export class ShadowDomCompileStep implements CompileStep { export class ShadowDomCompileStep implements CompileStep {
constructor(public _shadowDomStrategy: ShadowDomStrategy, public _template: ViewDefinition, constructor(public _shadowDomStrategy: ShadowDomStrategy, public _view: ViewDefinition) {}
public _subTaskPromises: List<Promise<any>>) {}
process(parent: CompileElement, current: CompileElement, control: CompileControl) { process(parent: CompileElement, current: CompileElement, control: CompileControl) {
var tagName = DOM.tagName(current.element).toUpperCase(); var tagName = DOM.tagName(current.element).toUpperCase();
@ -22,17 +19,13 @@ export class ShadowDomCompileStep implements CompileStep {
this._processContentElement(current); this._processContentElement(current);
} else { } else {
var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null; var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null;
this._shadowDomStrategy.processElement(this._template.componentId, componentId, this._shadowDomStrategy.processElement(this._view.componentId, componentId, current.element);
current.element);
} }
} }
_processStyleElement(current: CompileElement, control: CompileControl) { _processStyleElement(current: CompileElement, control: CompileControl) {
var stylePromise = this._shadowDomStrategy.processStyleElement( this._shadowDomStrategy.processStyleElement(this._view.componentId, this._view.templateAbsUrl,
this._template.componentId, this._template.templateAbsUrl, current.element); current.element);
if (isPresent(stylePromise) && isPromise(stylePromise)) {
this._subTaskPromises.push(stylePromise);
}
// Style elements should not be further processed by the compiler, as they can not contain // Style elements should not be further processed by the compiler, as they can not contain
// bindings. Skipping further compiler steps allow speeding up the compilation process. // bindings. Skipping further compiler steps allow speeding up the compilation process.

View File

@ -1,28 +1,20 @@
import {isBlank, isPresent} from 'angular2/src/facade/lang'; import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {Promise} from 'angular2/src/facade/async';
import * as viewModule from '../view/view'; import * as viewModule from '../view/view';
import {LightDom} from './light_dom'; import {LightDom} from './light_dom';
export class ShadowDomStrategy { export class ShadowDomStrategy {
// Whether the strategy understands the native <content> tag
hasNativeContentElement(): boolean { return true; } hasNativeContentElement(): boolean { return true; }
/** // Prepares and returns the (emulated) shadow root for the given element.
* Prepares and returns the shadow root for the given element.
*/
prepareShadowRoot(el): any { return null; } prepareShadowRoot(el): any { return null; }
constructLightDom(lightDomView: viewModule.DomView, el): LightDom { return null; } constructLightDom(lightDomView: viewModule.DomView, el): LightDom { return null; }
/** // An optional step that can modify the template style elements.
* An optional step that can modify the template style elements. processStyleElement(hostComponentId: string, templateUrl: string, styleElement): void {}
*/
processStyleElement(hostComponentId: string, templateUrl: string, styleElement): Promise<any> {
return null;
}
/** // An optional step that can modify the template elements (style elements exlcuded).
* An optional step that can modify the template elements (style elements exlcuded). processElement(hostComponentId: string, elementComponentId: string, element): void {}
*/
processElement(hostComponentId: string, elementComponentId: string, element) {}
} }

View File

@ -23,8 +23,8 @@ import {XHR} from 'angular2/src/render/xhr';
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
import {AppRootUrl} from 'angular2/src/services/app_root_url'; import {AppRootUrl} from 'angular2/src/services/app_root_url';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner'; import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner';
import {NgZone} from 'angular2/src/core/zone/ng_zone'; import {NgZone} from 'angular2/src/core/zone/ng_zone';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
@ -88,9 +88,7 @@ function _getAppBindings() {
bind(DOCUMENT_TOKEN) bind(DOCUMENT_TOKEN)
.toValue(appDoc), .toValue(appDoc),
bind(ShadowDomStrategy) bind(ShadowDomStrategy)
.toFactory((styleInliner, styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy( .toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
styleInliner, styleUrlResolver, doc.head),
[StyleInliner, StyleUrlResolver, DOCUMENT_TOKEN]),
DomRenderer, DomRenderer,
DefaultDomCompiler, DefaultDomCompiler,
bind(Renderer).toAlias(DomRenderer), bind(Renderer).toAlias(DomRenderer),

View File

@ -1,6 +1,5 @@
library angular2.transform.template_compiler.compile_step_factory; library angular2.transform.template_compiler.compile_step_factory;
import 'dart:async';
import 'package:angular2/src/change_detection/parser/parser.dart' as ng; import 'package:angular2/src/change_detection/parser/parser.dart' as ng;
import 'package:angular2/src/render/api.dart'; import 'package:angular2/src/render/api.dart';
import 'package:angular2/src/render/dom/compiler/compile_step.dart'; import 'package:angular2/src/render/dom/compiler/compile_step.dart';
@ -15,8 +14,7 @@ class CompileStepFactory implements base.CompileStepFactory {
final ng.Parser _parser; final ng.Parser _parser;
CompileStepFactory(this._parser); CompileStepFactory(this._parser);
List<CompileStep> createSteps( List<CompileStep> createSteps(ViewDefinition template) {
ViewDefinition template, List<Future> subTaskPromises) {
return [ return [
new ViewSplitter(_parser), new ViewSplitter(_parser),
new PropertyBindingParser(_parser), new PropertyBindingParser(_parser),

View File

@ -7,6 +7,8 @@ import 'package:angular2/src/change_detection/parser/parser.dart' as ng;
import 'package:angular2/src/core/compiler/proto_view_factory.dart'; import 'package:angular2/src/core/compiler/proto_view_factory.dart';
import 'package:angular2/src/render/api.dart'; import 'package:angular2/src/render/api.dart';
import 'package:angular2/src/render/dom/compiler/compile_pipeline.dart'; import 'package:angular2/src/render/dom/compiler/compile_pipeline.dart';
import 'package:angular2/src/render/dom/compiler/style_inliner.dart';
import 'package:angular2/src/render/dom/compiler/style_url_resolver.dart';
import 'package:angular2/src/render/dom/compiler/template_loader.dart'; import 'package:angular2/src/render/dom/compiler/template_loader.dart';
import 'package:angular2/src/render/xhr.dart' show XHR; import 'package:angular2/src/render/xhr.dart' show XHR;
import 'package:angular2/src/reflection/reflection.dart'; import 'package:angular2/src/reflection/reflection.dart';
@ -79,11 +81,17 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint,
/// reflectively accessed from that template. /// reflectively accessed from that template.
class _TemplateExtractor { class _TemplateExtractor {
final CompileStepFactory _factory; final CompileStepFactory _factory;
final TemplateLoader _loader; TemplateLoader _loader;
_TemplateExtractor(XHR xhr) _TemplateExtractor(XHR xhr)
: _factory = new CompileStepFactory(new ng.Parser(new ng.Lexer())), : _factory = new CompileStepFactory(new ng.Parser(new ng.Lexer())) {
_loader = new TemplateLoader(xhr, new UrlResolver());
var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver);
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
_loader = new TemplateLoader(xhr, styleInliner, styleUrlResolver);
}
Future<_ExtractResult> extractTemplates(ViewDefinition viewDef) async { Future<_ExtractResult> extractTemplates(ViewDefinition viewDef) async {
// Check for "imperative views". // Check for "imperative views".
@ -98,9 +106,7 @@ class _TemplateExtractor {
var recordingCapabilities = new RecordingReflectionCapabilities(); var recordingCapabilities = new RecordingReflectionCapabilities();
reflector.reflectionCapabilities = recordingCapabilities; reflector.reflectionCapabilities = recordingCapabilities;
var subtaskPromises = []; var pipeline = new CompilePipeline(_factory.createSteps(viewDef));
var pipeline =
new CompilePipeline(_factory.createSteps(viewDef, subtaskPromises));
var compileElements = var compileElements =
pipeline.process(templateEl, ViewType.COMPONENT, viewDef.componentId); pipeline.process(templateEl, ViewType.COMPONENT, viewDef.componentId);
@ -109,9 +115,7 @@ class _TemplateExtractor {
reflector.reflectionCapabilities = savedReflectionCapabilities; reflector.reflectionCapabilities = savedReflectionCapabilities;
return Future return new _ExtractResult(recordingCapabilities, protoViewDto);
.wait(subtaskPromises)
.then((_) => new _ExtractResult(recordingCapabilities, protoViewDto));
} }
} }

View File

@ -62,8 +62,8 @@ export function main() {
protoViewFactoryResults: List<List<AppProtoView>>) { protoViewFactoryResults: List<List<AppProtoView>>) {
var urlResolver = new FakeUrlResolver(); var urlResolver = new FakeUrlResolver();
renderCompileRequests = []; renderCompileRequests = [];
renderCompiler.spy('compile').andCallFake((template) => { renderCompiler.spy('compile').andCallFake((view) => {
renderCompileRequests.push(template); renderCompileRequests.push(view);
return PromiseWrapper.resolve(ListWrapper.removeAt(renderCompileResults, 0)); return PromiseWrapper.resolve(ListWrapper.removeAt(renderCompileResults, 0));
}); });
@ -467,12 +467,12 @@ export function main() {
}); });
} }
function createDirectiveBinding(directiveResolver, type) { function createDirectiveBinding(directiveResolver, type): DirectiveBinding {
var annotation = directiveResolver.resolve(type); var annotation = directiveResolver.resolve(type);
return DirectiveBinding.createFromType(type, annotation); return DirectiveBinding.createFromType(type, annotation);
} }
function createProtoView(elementBinders = null) { function createProtoView(elementBinders = null): AppProtoView {
var pv = new AppProtoView(null, null, new Map(), null); var pv = new AppProtoView(null, null, new Map(), null);
if (isBlank(elementBinders)) { if (isBlank(elementBinders)) {
elementBinders = []; elementBinders = [];
@ -481,18 +481,19 @@ function createProtoView(elementBinders = null) {
return pv; return pv;
} }
function createComponentElementBinder(directiveResolver, type) { function createComponentElementBinder(directiveResolver, type): ElementBinder {
var binding = createDirectiveBinding(directiveResolver, type); var binding = createDirectiveBinding(directiveResolver, type);
return new ElementBinder(0, null, 0, null, binding); return new ElementBinder(0, null, 0, null, binding);
} }
function createViewportElementBinder(nestedProtoView) { function createViewportElementBinder(nestedProtoView): ElementBinder {
var elBinder = new ElementBinder(0, null, 0, null, null); var elBinder = new ElementBinder(0, null, 0, null, null);
elBinder.nestedProtoView = nestedProtoView; elBinder.nestedProtoView = nestedProtoView;
return elBinder; return elBinder;
} }
function createRenderProtoView(elementBinders = null, type: renderApi.ViewType = null) { function createRenderProtoView(elementBinders = null,
type: renderApi.ViewType = null): renderApi.ProtoViewDto {
if (isBlank(type)) { if (isBlank(type)) {
type = renderApi.ViewType.COMPONENT; type = renderApi.ViewType.COMPONENT;
} }
@ -502,16 +503,16 @@ function createRenderProtoView(elementBinders = null, type: renderApi.ViewType =
return new renderApi.ProtoViewDto({elementBinders: elementBinders, type: type}); return new renderApi.ProtoViewDto({elementBinders: elementBinders, type: type});
} }
function createRenderComponentElementBinder(directiveIndex) { function createRenderComponentElementBinder(directiveIndex): renderApi.ElementBinder {
return new renderApi.ElementBinder( return new renderApi.ElementBinder(
{directives: [new renderApi.DirectiveBinder({directiveIndex: directiveIndex})]}); {directives: [new renderApi.DirectiveBinder({directiveIndex: directiveIndex})]});
} }
function createRenderViewportElementBinder(nestedProtoView) { function createRenderViewportElementBinder(nestedProtoView): renderApi.ElementBinder {
return new renderApi.ElementBinder({nestedProtoView: nestedProtoView}); return new renderApi.ElementBinder({nestedProtoView: nestedProtoView});
} }
function createRootProtoView(directiveResolver, type) { function createRootProtoView(directiveResolver, type): AppProtoView {
return createProtoView([createComponentElementBinder(directiveResolver, type)]); return createProtoView([createComponentElementBinder(directiveResolver, type)]);
} }
@ -577,23 +578,18 @@ class FakeAppRootUrl extends AppRootUrl {
class FakeTemplateResolver extends TemplateResolver { class FakeTemplateResolver extends TemplateResolver {
_cmpTemplates: Map<Type, viewAnn.View>; _cmpViews: Map<Type, viewAnn.View> = new Map();
constructor() { constructor() { super(); }
super();
this._cmpTemplates = new Map();
}
resolve(component: Type): viewAnn.View { resolve(component: Type): viewAnn.View {
var template = this._cmpTemplates.get(component); // returns null for dynamic components
if (isBlank(template)) { return this._cmpViews.has(component) ? this._cmpViews.get(component) : null;
// dynamic component
return null;
}
return template;
} }
setView(component: Type, template: viewAnn.View) { this._cmpTemplates.set(component, template); } setView(component: Type, template: viewAnn.View): void {
this._cmpViews.set(component, template);
}
} }
class FakeProtoViewFactory extends ProtoViewFactory { class FakeProtoViewFactory extends ProtoViewFactory {

View File

@ -21,7 +21,7 @@ import {Component, View} from 'angular2/angular2';
import {NgIf} from 'angular2/src/directives/ng_if'; import {NgIf} from 'angular2/src/directives/ng_if';
export function main() { export function main() {
describe('if directive', () => { describe('ng-if directive', () => {
it('should work in a template attribute', it('should work in a template attribute',
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => { inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
var html = '<div><copy-me template="ng-if booleanCondition">hello</copy-me></div>'; var html = '<div><copy-me template="ng-if booleanCondition">hello</copy-me></div>';

View File

@ -24,8 +24,6 @@ import {CompileStepFactory} from 'angular2/src/render/dom/compiler/compile_step_
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control'; import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view'; import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
export function runCompilerCommonTests() { export function runCompilerCommonTests() {
@ -111,30 +109,6 @@ export function runCompilerCommonTests() {
}); });
})); }));
it('should wait for async subtasks to be resolved', inject([AsyncTestCompleter], (async) => {
var subTasksCompleted = false;
var completer = PromiseWrapper.completer();
var compiler = createCompiler((parent, current, control) => {
mockStepFactory.subTaskPromises.push(
completer.promise.then((_) => { subTasksCompleted = true; }));
});
// It should always return a Promise because the subtask is async
var pvPromise = compiler.compile(
new ViewDefinition({componentId: 'someId', template: 'some component'}));
expect(pvPromise).toBePromise();
expect(subTasksCompleted).toEqual(false);
// The Promise should resolve after the subtask is ready
completer.resolve(null);
pvPromise.then((protoView) => {
expect(subTasksCompleted).toEqual(true);
async.done();
});
}));
it('should return ProtoViews of type COMPONENT_VIEW_TYPE', it('should return ProtoViews of type COMPONENT_VIEW_TYPE',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(EMPTY_STEP); var compiler = createCompiler(EMPTY_STEP);
@ -174,10 +148,8 @@ class MockStepFactory extends CompileStepFactory {
super(); super();
this.steps = steps; this.steps = steps;
} }
createSteps(viewDef, subTaskPromises) { createSteps(viewDef): List<CompileStep> {
this.viewDef = viewDef; this.viewDef = viewDef;
this.subTaskPromises = subTaskPromises;
ListWrapper.forEach(this.subTaskPromises, (p) => this.subTaskPromises.push(p));
return this.steps; return this.steps;
} }
} }
@ -199,20 +171,20 @@ var EMPTY_STEP = (parent, current, control) => {
class FakeTemplateLoader extends TemplateLoader { class FakeTemplateLoader extends TemplateLoader {
_urlData: Map<string, string>; _urlData: Map<string, string>;
constructor(urlData) { constructor(urlData) {
super(null, new UrlResolver()); super(null, null, null);
this._urlData = urlData; this._urlData = urlData;
} }
load(template: ViewDefinition) { load(view: ViewDefinition): Promise<any> {
if (isPresent(template.template)) { if (isPresent(view.template)) {
return PromiseWrapper.resolve(DOM.createTemplate(template.template)); return PromiseWrapper.resolve(DOM.createTemplate(view.template));
} }
if (isPresent(template.templateAbsUrl)) { if (isPresent(view.templateAbsUrl)) {
var content = this._urlData.get(template.templateAbsUrl); var content = this._urlData.get(view.templateAbsUrl);
return isPresent(content) ? return isPresent(content) ?
PromiseWrapper.resolve(DOM.createTemplate(content)) : PromiseWrapper.resolve(DOM.createTemplate(content)) :
PromiseWrapper.reject(`Failed to fetch url "${template.templateAbsUrl}"`, null); PromiseWrapper.reject(`Failed to fetch url "${view.templateAbsUrl}"`, null);
} }
throw new BaseException('View should have either the templateUrl or template property set'); throw new BaseException('View should have either the templateUrl or template property set');

View File

@ -11,7 +11,7 @@ import {
it, it,
xit, xit,
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner'; import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner';
import {isBlank} from 'angular2/src/facade/lang'; import {isBlank} from 'angular2/src/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async';

View File

@ -1,5 +1,5 @@
import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'angular2/test_lib'; import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'angular2/test_lib';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';

View File

@ -12,19 +12,27 @@ import {
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner';
import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
import {ViewDefinition} from 'angular2/src/render/api'; import {ViewDefinition} from 'angular2/src/render/api';
import {PromiseWrapper} from 'angular2/src/facade/async'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {StringWrapper, isBlank, isPresent} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {XHR} from 'angular2/src/render/xhr';
import {MockXHR} from 'angular2/src/render/xhr_mock'; import {MockXHR} from 'angular2/src/render/xhr_mock';
export function main() { export function main() {
describe('TemplateLoader', () => { describe('TemplateLoader', () => {
var loader, xhr; var loader, xhr, styleUrlResolver, urlResolver;
beforeEach(() => { beforeEach(() => {
xhr = new MockXHR(); xhr = new MockXHR();
loader = new TemplateLoader(xhr, new FakeUrlResolver()); urlResolver = new FakeUrlResolver();
styleUrlResolver = new StyleUrlResolver(urlResolver);
let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
loader = new TemplateLoader(xhr, styleInliner, styleUrlResolver);
}); });
describe('html', () => { describe('html', () => {
@ -46,6 +54,33 @@ export function main() {
xhr.flush(); xhr.flush();
})); }));
it('should resolve urls in styles', inject([AsyncTestCompleter], (async) => {
xhr.expect('base/foo.html',
'<style>.foo { background-image: url("double.jpg"); }</style>');
var template = new ViewDefinition({templateAbsUrl: 'base/foo.html'});
loader.load(template).then((el) => {
expect(DOM.content(el))
.toHaveText(".foo { background-image: url('/base/double.jpg'); }");
async.done();
});
xhr.flush();
}));
it('should inline styles', inject([AsyncTestCompleter], (async) => {
let xhr = new FakeXHR();
xhr.reply('base/foo.html', '<style>@import "foo.css";</style>');
xhr.reply('/base/foo.css', '/* foo.css */');
let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
let loader = new TemplateLoader(xhr, styleInliner, styleUrlResolver);
var template = new ViewDefinition({templateAbsUrl: 'base/foo.html'});
loader.load(template).then((el) => {
expect(DOM.getInnerHTML(el)).toEqual("<style>/* foo.css */\n</style>");
async.done();
});
}));
it('should return a new template element on each call', it('should return a new template element on each call',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
var firstEl; var firstEl;
@ -91,8 +126,17 @@ export function main() {
var template = new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']}); var template = new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']});
loader.load(template).then((el) => { loader.load(template).then((el) => {
expect(DOM.getInnerHTML(el)) expect(DOM.getInnerHTML(el))
.toEqual( .toEqual('<style>style 1</style><style>style 2</style>html');
'<style type="text/css">style 1</style><style type="text/css">style 2</style>html'); async.done();
});
}));
it('should resolve urls in inline styles', inject([AsyncTestCompleter], (async) => {
var template = new ViewDefinition(
{template: 'html', styles: ['.foo { background-image: url("double.jpg"); }']});
loader.load(template).then((el) => {
expect(DOM.getInnerHTML(el))
.toEqual("<style>.foo { background-image: url('/double.jpg'); }</style>html");
async.done(); async.done();
}); });
})); }));
@ -108,13 +152,29 @@ export function main() {
}); });
loader.load(template).then((el) => { loader.load(template).then((el) => {
expect(DOM.getInnerHTML(el)) expect(DOM.getInnerHTML(el))
.toEqual( .toEqual('<style>i1</style><style>1</style><style>2</style>xhr template');
'<style type="text/css">i1</style><style type="text/css">1</style><style type="text/css">2</style>xhr template');
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
})); }));
it('should inline styles', inject([AsyncTestCompleter], (async) => {
let xhr = new FakeXHR();
xhr.reply('base/foo.html', '<p>template</p>');
xhr.reply('/base/foo.css', '/* foo.css */');
let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
let loader = new TemplateLoader(xhr, styleInliner, styleUrlResolver);
var template = new ViewDefinition(
{templateAbsUrl: 'base/foo.html', styles: ['@import "foo.css";']});
loader.load(template).then((el) => {
expect(DOM.getInnerHTML(el)).toEqual("<style>/* foo.css */\n</style><p>template</p>");
async.done();
});
}));
it('should return a rejected Promise when XHR loading fails', it('should return a rejected Promise when XHR loading fails',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
xhr.expect('base/foo.css', null); xhr.expect('base/foo.css', null);
@ -136,5 +196,27 @@ class SomeComponent {}
class FakeUrlResolver extends UrlResolver { class FakeUrlResolver extends UrlResolver {
constructor() { super(); } constructor() { super(); }
resolve(baseUrl: string, url: string): string { return baseUrl + url; } resolve(baseUrl: string, url: string): string {
if (url.length > 0 && url[0] == '/') return url;
if (!isPresent(baseUrl)) return `/${url}`;
var parts: List<string> = baseUrl.split('/');
if (parts.length > 1) {
ListWrapper.removeLast(parts);
}
parts.push(url);
return '/' + parts.join('/');
}
}
class FakeXHR extends XHR {
_responses: Map<string, string> = new Map();
constructor() { super(); }
get(url: string): Promise<string> {
return this._responses.has(url) ? PromiseWrapper.resolve(this._responses.get(url)) :
PromiseWrapper.reject('xhr error', null);
}
reply(url: string, response: string): void { this._responses.set(url, response); }
} }

View File

@ -13,12 +13,7 @@ import {
normalizeCSS normalizeCSS
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {Map, MapWrapper} from 'angular2/src/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {XHR} from 'angular2/src/render/xhr';
import { import {
EmulatedScopedShadowDomStrategy, EmulatedScopedShadowDomStrategy,
@ -26,21 +21,14 @@ import {
import { import {
resetShadowDomCache, resetShadowDomCache,
} from 'angular2/src/render/dom/shadow_dom/util'; } from 'angular2/src/render/dom/shadow_dom/util';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
export function main() { export function main() {
describe('EmulatedScopedShadowDomStrategy', () => { describe('EmulatedScopedShadowDomStrategy', () => {
var xhr, styleHost, strategy; var styleHost, strategy;
beforeEach(() => { beforeEach(() => {
var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver);
xhr = new FakeXHR();
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
styleHost = el('<div></div>'); styleHost = el('<div></div>');
strategy = new EmulatedScopedShadowDomStrategy(styleInliner, styleUrlResolver, styleHost); strategy = new EmulatedScopedShadowDomStrategy(styleHost);
resetShadowDomCache(); resetShadowDomCache();
}); });
@ -49,34 +37,12 @@ export function main() {
expect(strategy.prepareShadowRoot(host)).toBe(host); expect(strategy.prepareShadowRoot(host)).toBe(host);
}); });
it('should rewrite style urls', () => {
var styleElement = el('<style>.foo {background-image: url("img.jpg");}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(normalizeCSS(DOM.getText(styleElement)))
.toEqual(".foo[_ngcontent-0] { background-image:url(http://base/img.jpg); }");
});
it('should scope styles', () => { it('should scope styles', () => {
var styleElement = el('<style>.foo {} :host {}</style>'); var styleElement = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement); strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(styleElement).toHaveText(".foo[_ngcontent-0] {\n\n}\n\n[_nghost-0] {\n\n}"); expect(styleElement).toHaveText(".foo[_ngcontent-0] {\n\n}\n\n[_nghost-0] {\n\n}");
}); });
it('should inline @import rules', inject([AsyncTestCompleter], (async) => {
xhr.reply('http://base/one.css', '.one {}');
var styleElement = el('<style>@import "one.css";</style>');
var stylePromise =
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(stylePromise).toBePromise();
expect(styleElement).toHaveText('');
stylePromise.then((_) => {
expect(styleElement).toHaveText('.one[_ngcontent-0] {\n\n}');
async.done();
});
}));
it('should return the same style given the same component', () => { it('should return the same style given the same component', () => {
var styleElement = el('<style>.foo {} :host {}</style>'); var styleElement = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement); strategy.processStyleElement('someComponent', 'http://base', styleElement);
@ -97,22 +63,6 @@ export function main() {
expect(DOM.getText(styleElement)).not.toEqual(DOM.getText(styleElement2)); expect(DOM.getText(styleElement)).not.toEqual(DOM.getText(styleElement2));
}); });
it('should move the style element to the style host when @imports are present',
inject([AsyncTestCompleter], (async) => {
xhr.reply('http://base/one.css', '.one {}');
var compileElement = el('<div><style>@import "one.css";</style></div>');
var styleElement = DOM.firstChild(compileElement);
var stylePromise =
strategy.processStyleElement('someComponent', 'http://base', styleElement);
stylePromise.then((_) => {
expect(compileElement).toHaveText('');
expect(styleHost).toHaveText('.one[_ngcontent-0] {\n\n}');
async.done();
});
}));
it('should move the style element to the style host', () => { it('should move the style element to the style host', () => {
var compileElement = el('<div><style>.one {}</style></div>'); var compileElement = el('<div><style>.one {}</style></div>');
var styleElement = DOM.firstChild(compileElement); var styleElement = DOM.firstChild(compileElement);
@ -136,23 +86,3 @@ export function main() {
}); });
} }
class FakeXHR extends XHR {
_responses: Map<string, string>;
constructor() {
super();
this._responses = new Map();
}
get(url: string): Promise<string> {
var response = this._responses.get(url);
if (isBlank(response)) {
return PromiseWrapper.reject('xhr error', null);
}
return PromiseWrapper.resolve(response);
}
reply(url: string, response: string) { this._responses.set(url, response); }
}

View File

@ -13,7 +13,7 @@ import {
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {Map, ListWrapper} from 'angular2/src/facade/collection'; import {ListWrapper} from 'angular2/src/facade/collection';
import { import {
EmulatedUnscopedShadowDomStrategy, EmulatedUnscopedShadowDomStrategy,
@ -21,28 +21,16 @@ import {
import { import {
resetShadowDomCache, resetShadowDomCache,
} from 'angular2/src/render/dom/shadow_dom/util'; } from 'angular2/src/render/dom/shadow_dom/util';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {isBlank} from 'angular2/src/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {XHR} from 'angular2/src/render/xhr';
export function main() { export function main() {
var strategy; var strategy;
describe('EmulatedUnscopedShadowDomStrategy', () => { describe('EmulatedUnscopedShadowDomStrategy', () => {
var xhr, styleHost; var styleHost;
beforeEach(() => { beforeEach(() => {
var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver);
xhr = new FakeXHR();
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
styleHost = el('<div></div>'); styleHost = el('<div></div>');
strategy = new EmulatedUnscopedShadowDomStrategy(styleInliner, styleUrlResolver, styleHost); strategy = new EmulatedUnscopedShadowDomStrategy(styleHost);
resetShadowDomCache(); resetShadowDomCache();
}); });
@ -51,27 +39,6 @@ export function main() {
expect(strategy.prepareShadowRoot(host)).toBe(host); expect(strategy.prepareShadowRoot(host)).toBe(host);
}); });
it('should rewrite style urls', () => {
var styleElement = el('<style>.foo {background-image: url("img.jpg");}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(styleElement).toHaveText(".foo {background-image: url('http://base/img.jpg');}");
});
it('should inline @import rules', inject([AsyncTestCompleter], (async) => {
xhr.reply('http://base/one.css', '.one {}');
var styleElement = el('<style>@import "one.css";</style>');
var stylePromise =
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(stylePromise).toBePromise();
expect(styleElement).toHaveText('');
stylePromise.then((_) => {
expect(styleElement).toHaveText('.one {}\n');
async.done();
});
}));
it('should move the style element to the style host', () => { it('should move the style element to the style host', () => {
var compileElement = el('<div><style>.one {}</style></div>'); var compileElement = el('<div><style>.one {}</style></div>');
var styleElement = DOM.firstChild(compileElement); var styleElement = DOM.firstChild(compileElement);
@ -96,23 +63,3 @@ export function main() {
}); });
} }
class FakeXHR extends XHR {
_responses: Map<string, string>;
constructor() {
super();
this._responses = <Map<string, string>>{};
}
get(url: string): Promise<string> {
var response = this._responses[url];
if (isBlank(response)) {
return PromiseWrapper.reject('xhr error', null);
}
return PromiseWrapper.resolve(response);
}
reply(url: string, response: string) { this._responses[url] = response; }
}

View File

@ -15,15 +15,6 @@ import {
import { import {
NativeShadowDomStrategy NativeShadowDomStrategy
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy'; } from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {XHR} from 'angular2/src/render/xhr';
import {isBlank} from 'angular2/src/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {Map} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
@ -31,14 +22,7 @@ export function main() {
var strategy; var strategy;
describe('NativeShadowDomStrategy', () => { describe('NativeShadowDomStrategy', () => {
var xhr; beforeEach(() => { strategy = new NativeShadowDomStrategy(); });
beforeEach(() => {
var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver);
xhr = new FakeXHR();
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
strategy = new NativeShadowDomStrategy(styleInliner, styleUrlResolver);
});
if (DOM.supportsNativeShadowDOM()) { if (DOM.supportsNativeShadowDOM()) {
it('should use the native shadow root', () => { it('should use the native shadow root', () => {
@ -46,47 +30,5 @@ export function main() {
expect(strategy.prepareShadowRoot(host)).toBe(DOM.getShadowRoot(host)); expect(strategy.prepareShadowRoot(host)).toBe(DOM.getShadowRoot(host));
}); });
} }
it('should rewrite style urls', () => {
var styleElement = el('<style>.foo {background-image: url("img.jpg");}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(styleElement)
.toHaveText(".foo {" + "background-image: url('http://base/img.jpg');" + "}");
});
it('should inline @import rules', inject([AsyncTestCompleter], (async) => {
xhr.reply('http://base/one.css', '.one {}');
var styleElement = el('<style>@import "one.css";</style>');
var stylePromise =
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(stylePromise).toBePromise();
stylePromise.then((_) => {
expect(styleElement).toHaveText('.one {}\n');
async.done();
});
}));
}); });
} }
class FakeXHR extends XHR {
_responses: Map<string, string>;
constructor() {
super();
this._responses = <Map<string, string>>{};
}
get(url: string): Promise<string> {
var response = this._responses[url];
if (isBlank(response)) {
return PromiseWrapper.reject('xhr error', null);
}
return PromiseWrapper.resolve(response);
}
reply(url: string, response: string) { this._responses[url] = response; }
}

View File

@ -29,35 +29,21 @@ import {
import { import {
NativeShadowDomStrategy NativeShadowDomStrategy
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy'; } from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {DomTestbed, elRef} from './dom_testbed'; import {DomTestbed, elRef} from './dom_testbed';
export function main() { export function main() {
describe('ShadowDom integration tests', function() { describe('ShadowDom integration tests', function() {
var styleHost; var styleHost = DOM.createElement('div');
var strategies = { var strategies = {
"scoped": "scoped": bind(ShadowDomStrategy).toValue(new EmulatedScopedShadowDomStrategy(styleHost)),
bind(ShadowDomStrategy) "unscoped": bind(ShadowDomStrategy).toValue(new EmulatedUnscopedShadowDomStrategy(styleHost))
.toFactory((styleInliner, styleUrlResolver) => new EmulatedScopedShadowDomStrategy(
styleInliner, styleUrlResolver, styleHost),
[StyleInliner, StyleUrlResolver]),
"unscoped":
bind(ShadowDomStrategy)
.toFactory((styleInliner, styleUrlResolver) => new EmulatedUnscopedShadowDomStrategy(
styleInliner, styleUrlResolver, null),
[StyleInliner, StyleUrlResolver])
}; };
if (DOM.supportsNativeShadowDOM()) { if (DOM.supportsNativeShadowDOM()) {
StringMapWrapper.set( StringMapWrapper.set(strategies, "native",
strategies, "native", bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy()));
bind(ShadowDomStrategy)
.toFactory((styleInliner, styleUrlResolver) =>
new NativeShadowDomStrategy(styleInliner, styleUrlResolver),
[StyleInliner, StyleUrlResolver]));
} }
beforeEach(() => { styleHost = el('<div></div>'); });
StringMapWrapper.forEach(strategies, (strategyBinding, name) => { StringMapWrapper.forEach(strategies, (strategyBinding, name) => {
describe(`${name} shadow dom strategy`, () => { describe(`${name} shadow dom strategy`, () => {

View File

@ -16,10 +16,7 @@ import {Component, Directive, View} from 'angular2/angular2';
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
import {AppRootUrl} from 'angular2/src/services/app_root_url'; import {AppRootUrl} from 'angular2/src/services/app_root_url';
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
import {reflector} from 'angular2/src/reflection/reflection'; import {reflector} from 'angular2/src/reflection/reflection';
@ -39,11 +36,9 @@ export function main() {
var templateResolver = new MultipleTemplateResolver( var templateResolver = new MultipleTemplateResolver(
count, [BenchmarkComponentNoBindings, BenchmarkComponentWithBindings]); count, [BenchmarkComponentNoBindings, BenchmarkComponentWithBindings]);
var urlResolver = new UrlResolver(); var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver); var shadowDomStrategy = new NativeShadowDomStrategy();
var styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver);
var shadowDomStrategy = new NativeShadowDomStrategy(styleInliner, styleUrlResolver);
var renderCompiler = new rc.DefaultDomCompiler(new Parser(new Lexer()), shadowDomStrategy, var renderCompiler = new rc.DefaultDomCompiler(new Parser(new Lexer()), shadowDomStrategy,
new TemplateLoader(null, urlResolver)); new TemplateLoader(null, null, null));
var compiler = new Compiler( var compiler = new Compiler(
reader, cache, templateResolver, new ComponentUrlMapper(), urlResolver, renderCompiler, reader, cache, templateResolver, new ComponentUrlMapper(), urlResolver, renderCompiler,
new ProtoViewFactory(new DynamicChangeDetection(null)), new FakeAppRootUrl()); new ProtoViewFactory(new DynamicChangeDetection(null)), new FakeAppRootUrl());

View File

@ -27,16 +27,16 @@ export class DemoUrlResolver extends UrlResolver {
constructor() { constructor() {
super(); super();
if (isBlank(UrlResolver.a)) { if (isBlank(DemoUrlResolver.a)) {
UrlResolver.a = DOM.createElement('a'); DemoUrlResolver.a = DOM.createElement('a');
} }
this.isInPubServe = _isInPubServe(); this.isInPubServe = _isInPubServe();
} }
resolve(baseUrl: string, url: string): string { resolve(baseUrl: string, url: string): string {
if (isBlank(baseUrl)) { if (isBlank(baseUrl)) {
DOM.resolveAndSetHref(UrlResolver.a, url, null); DOM.resolveAndSetHref(DemoUrlResolver.a, url, null);
return DOM.getHref(UrlResolver.a); return DOM.getHref(DemoUrlResolver.a);
} }
if (isBlank(url) || url == '') { if (isBlank(url) || url == '') {