From 929fc65493f0d50dcb2fe55f8502357abf8aa27d Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 24 Feb 2015 16:05:45 +0100 Subject: [PATCH] refactor(template loading): add support for base URLs, css rewriting fixes #654 --- modules/angular2/src/core/application.js | 10 +- .../angular2/src/core/compiler/compiler.js | 27 ++- .../src/core/compiler/component_url_mapper.js | 8 +- .../core/compiler/pipeline/default_steps.js | 21 +-- .../src/core/compiler/pipeline/resolve_css.js | 47 +++++ .../core/compiler/pipeline/shim_shadow_css.js | 55 ------ .../core/compiler/pipeline/shim_shadow_dom.js | 13 +- .../shadow_dom_emulation/shim_component.js | 99 ---------- .../src/core/compiler/shadow_dom_strategy.js | 131 +++++++++++--- .../src/core/compiler/style_inliner.js | 77 +++++--- .../src/core/compiler/style_url_resolver.js | 4 + .../src/core/compiler/template_loader.js | 47 ++++- .../src/core/compiler/url_resolver.js | 6 +- modules/angular2/src/core/compiler/view.js | 7 +- modules/angular2/src/facade/dom.dart | 4 + modules/angular2/src/facade/dom.es6 | 8 + modules/angular2/src/mock/xhr_mock.js | 2 +- .../test/core/compiler/compiler_spec.js | 160 +++++++++++++---- .../compiler/component_url_mapper_spec.js | 25 +++ .../test/core/compiler/integration_spec.js | 12 +- .../pipeline/element_binder_builder_spec.js | 6 +- .../pipeline/proto_view_builder_spec.js | 2 +- .../compiler/pipeline/resolve_css_spec.js | 137 ++++++++++++++ .../compiler/pipeline/shim_shadow_css_spec.js | 102 ----------- .../compiler/pipeline/shim_shadow_dom_spec.js | 39 ++-- .../shadow_dom_emulation_integration_spec.js | 19 +- .../shadow_dom/shim_component_spec.js | 128 ------------- .../core/compiler/shadow_dom_strategy_spec.js | 169 ++++++++++++++++++ .../test/core/compiler/style_inliner_spec.js | 116 ++++++++---- .../core/compiler/style_url_resolver_spec.js | 4 + .../core/compiler/template_loader_spec.js | 24 ++- .../test/core/compiler/view_container_spec.js | 5 +- .../angular2/test/core/compiler/view_spec.js | 18 +- .../angular2/test/directives/foreach_spec.js | 17 +- modules/angular2/test/directives/if_spec.js | 17 +- .../test/directives/non_bindable_spec.js | 20 ++- .../angular2/test/directives/switch_spec.js | 19 +- .../angular2/test/forms/integration_spec.js | 13 +- .../src/compiler/compiler_benchmark.js | 17 +- .../src/naive_infinite_scroll/index.js | 46 ++++- modules/benchmarks/src/tree/tree_benchmark.js | 53 +++++- .../examples/src/hello_world/index_static.js | 47 ++++- 42 files changed, 1147 insertions(+), 634 deletions(-) create mode 100644 modules/angular2/src/core/compiler/pipeline/resolve_css.js delete mode 100644 modules/angular2/src/core/compiler/pipeline/shim_shadow_css.js delete mode 100644 modules/angular2/src/core/compiler/shadow_dom_emulation/shim_component.js create mode 100644 modules/angular2/test/core/compiler/component_url_mapper_spec.js create mode 100644 modules/angular2/test/core/compiler/pipeline/resolve_css_spec.js delete mode 100644 modules/angular2/test/core/compiler/pipeline/shim_shadow_css_spec.js delete mode 100644 modules/angular2/test/core/compiler/shadow_dom/shim_component_spec.js create mode 100644 modules/angular2/test/core/compiler/shadow_dom_strategy_spec.js diff --git a/modules/angular2/src/core/application.js b/modules/angular2/src/core/application.js index 0915a71d09..1b3ad43e04 100644 --- a/modules/angular2/src/core/application.js +++ b/modules/angular2/src/core/application.js @@ -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 { 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 { Lexer, ExceptionHandler, bind(XHR).toValue(new XHRImpl()), + ComponentUrlMapper, + UrlResolver, + StyleUrlResolver, + StyleInliner, ]; } diff --git a/modules/angular2/src/core/compiler/compiler.js b/modules/angular2/src/core/compiler/compiler.js index bfc06422ad..d9f3361239 100644 --- a/modules/angular2/src/core/compiler/compiler.js +++ b/modules/angular2/src/core/compiler/compiler.js @@ -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; _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 { @@ -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 { @@ -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; } diff --git a/modules/angular2/src/core/compiler/component_url_mapper.js b/modules/angular2/src/core/compiler/component_url_mapper.js index 050ee7f4c3..9fa835e6fd 100644 --- a/modules/angular2/src/core/compiler/component_url_mapper.js +++ b/modules/angular2/src/core/compiler/component_url_mapper.js @@ -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 './'; } diff --git a/modules/angular2/src/core/compiler/pipeline/default_steps.js b/modules/angular2/src/core/compiler/pipeline/default_steps.js index 20cba05ce3..07d2872c19 100644 --- a/modules/angular2/src/core/compiler/pipeline/default_steps.js +++ b/modules/angular2/src/core/compiler/pipeline/default_steps.js @@ -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, - 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); diff --git a/modules/angular2/src/core/compiler/pipeline/resolve_css.js b/modules/angular2/src/core/compiler/pipeline/resolve_css.js new file mode 100644 index 0000000000..7daa7f718f --- /dev/null +++ b/modules/angular2/src/core/compiler/pipeline/resolve_css.js @@ -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); + } + } +} + diff --git a/modules/angular2/src/core/compiler/pipeline/shim_shadow_css.js b/modules/angular2/src/core/compiler/pipeline/shim_shadow_css.js deleted file mode 100644 index 404c50427e..0000000000 --- a/modules/angular2/src/core/compiler/pipeline/shim_shadow_css.js +++ /dev/null @@ -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; - } -} - diff --git a/modules/angular2/src/core/compiler/pipeline/shim_shadow_dom.js b/modules/angular2/src/core/compiler/pipeline/shim_shadow_dom.js index 17eef7d416..743905e350 100644 --- a/modules/angular2/src/core/compiler/pipeline/shim_shadow_dom.js +++ b/modules/angular2/src/core/compiler/pipeline/shim_shadow_dom.js @@ -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); } } } diff --git a/modules/angular2/src/core/compiler/shadow_dom_emulation/shim_component.js b/modules/angular2/src/core/compiler/shadow_dom_emulation/shim_component.js deleted file mode 100644 index 7f7b0566ce..0000000000 --- a/modules/angular2/src/core/compiler/shadow_dom_emulation/shim_component.js +++ /dev/null @@ -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 = 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}`; - } -} diff --git a/modules/angular2/src/core/compiler/shadow_dom_strategy.js b/modules/angular2/src/core/compiler/shadow_dom_strategy.js index 9949659b43..77fd325395 100644 --- a/modules/angular2/src/core/compiler/shadow_dom_strategy.js +++ b/modules/angular2/src/core/compiler/shadow_dom_strategy.js @@ -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{ 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 { return null; } + // TODO(vicb): union types: return either a string or a Promise + 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 = 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; +} diff --git a/modules/angular2/src/core/compiler/style_inliner.js b/modules/angular2/src/core/compiler/style_inliner.js index dce2761299..2bf5c8e473 100644 --- a/modules/angular2/src/core/compiler/style_inliner.js +++ b/modules/angular2/src/core/compiler/style_inliner.js @@ -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 when @import rules are present, a string otherwise + */ // TODO(vicb): Union types: returns either a Promise 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) { + _inlineImports(cssText: string, baseUrl: string, inlinedUrls: List) { 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}`; } diff --git a/modules/angular2/src/core/compiler/style_url_resolver.js b/modules/angular2/src/core/compiler/style_url_resolver.js index f0c528f82c..d02f5e0c87 100644 --- a/modules/angular2/src/core/compiler/style_url_resolver.js +++ b/modules/angular2/src/core/compiler/style_url_resolver.js @@ -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('[\'"]'); diff --git a/modules/angular2/src/core/compiler/template_loader.js b/modules/angular2/src/core/compiler/template_loader.js index 32f5417885..2c8417d275 100644 --- a/modules/angular2/src/core/compiler/template_loader.js +++ b/modules/angular2/src/core/compiler/template_loader.js @@ -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; + _urlCache: Map; + _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 @@ -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); } } diff --git a/modules/angular2/src/core/compiler/url_resolver.js b/modules/angular2/src/core/compiler/url_resolver.js index f0734e2214..a9cc11a238 100644 --- a/modules/angular2/src/core/compiler/url_resolver.js +++ b/modules/angular2/src/core/compiler/url_resolver.js @@ -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)) { diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index 97e547df96..fa4b13728e 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -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; + 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; } } diff --git a/modules/angular2/src/facade/dom.dart b/modules/angular2/src/facade/dom.dart index 43613afbfe..b96260caa1 100644 --- a/modules/angular2/src/facade/dom.dart +++ b/modules/angular2/src/facade/dom.dart @@ -11,9 +11,11 @@ export 'dart:html' show Element, location, Node, + ShadowRoot, StyleElement, TemplateElement, InputElement, + AnchorElement, Text, window; @@ -127,6 +129,8 @@ class DOM { el.text = css; return el; } + static ShadowRoot createShadowRoot(Element el) => el.createShadowRoot(); + static ShadowRoot getShadowRoot(Element el) => el.shadowRoot; static clone(Node node) => node.clone(true); static bool hasProperty(Element element, String name) => new JsObject.fromBrowserObject(element).hasProperty(name); diff --git a/modules/angular2/src/facade/dom.es6 b/modules/angular2/src/facade/dom.es6 index beebeffefa..c3c8dcbfed 100644 --- a/modules/angular2/src/facade/dom.es6 +++ b/modules/angular2/src/facade/dom.es6 @@ -6,8 +6,10 @@ export var Node = window.Node; export var NodeList = window.NodeList; export var Text = window.Text; export var Element = window.HTMLElement; +export var AnchorElement = window.HTMLAnchorElement; export var TemplateElement = window.HTMLTemplateElement; export var StyleElement = window.HTMLStyleElement; +export var ShadowRoot = window.ShadowRoot; export var document = window.document; export var location = window.location; export var gc = window.gc ? () => window.gc() : () => null; @@ -144,6 +146,12 @@ export class DOM { style.innerText = css; return style; } + static createShadowRoot(el: Element): ShadowRoot { + return el.createShadowRoot(); + } + static getShadowRoot(el: Element): ShadowRoot { + return el.shadowRoot; + } static clone(node:Node) { return node.cloneNode(true); } diff --git a/modules/angular2/src/mock/xhr_mock.js b/modules/angular2/src/mock/xhr_mock.js index a4c2d3fd47..3de802cb08 100644 --- a/modules/angular2/src/mock/xhr_mock.js +++ b/modules/angular2/src/mock/xhr_mock.js @@ -60,7 +60,7 @@ export class XHRMock extends XHR { if (this._expectations.length > 0) { var expectation = this._expectations[0]; - if (expectation.url === url) { + if (expectation.url == url) { ListWrapper.remove(this._expectations, expectation); request.complete(expectation.response); return; diff --git a/modules/angular2/test/core/compiler/compiler_spec.js b/modules/angular2/test/core/compiler/compiler_spec.js index 701041da1a..46528dae03 100644 --- a/modules/angular2/test/core/compiler/compiler_spec.js +++ b/modules/angular2/test/core/compiler/compiler_spec.js @@ -15,6 +15,9 @@ import {CompileStep} from 'angular2/src/core/compiler/pipeline/compile_step' import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_control'; import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; +import {ComponentUrlMapper, RuntimeComponentUrlMapper} 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 {Lexer, Parser, dynamicChangeDetection} from 'angular2/change_detection'; import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; @@ -41,7 +44,10 @@ export function main() { function createCompiler(processClosure) { var steps = [new MockStep(processClosure)]; - return new TestableCompiler(reader, steps, new FakeTemplateLoader(), tplResolver); + var urlResolver = new FakeUrlResolver(); + var tplLoader = new FakeTemplateLoader(urlResolver); + return new TestableCompiler(reader, steps,tplLoader, tplResolver, + urlResolver, new ComponentUrlMapper()); } it('should run the steps and return the ProtoView of the root element', (done) => { @@ -66,6 +72,32 @@ export function main() { }); }); + it('should wait for async styles to be resolved', (done) => { + var styleResolved = false; + + var completer = PromiseWrapper.completer(); + + var compiler = createCompiler( (parent, current, control) => { + var protoView = new ProtoView(current.element, null, null); + ListWrapper.push(protoView.stylePromises, completer.promise.then((_) => { + styleResolved = true; + })); + current.inheritedProtoView = protoView; + }); + + // It should always return a Promise because the style is async + var pvPromise = compiler.compile(MainComponent); + expect(pvPromise).toBePromise(); + expect(styleResolved).toEqual(false); + + // The Promise should resolve after the style is ready + completer.resolve(null); + pvPromise.then((protoView) => { + expect(styleResolved).toEqual(true); + done(); + }); + }); + it('should load nested components', (done) => { var compiler = createCompiler( (parent, current, control) => { if (DOM.hasClass(current.element, 'nested')) { @@ -102,8 +134,8 @@ export function main() { it('should re-use components being compiled', (done) => { var nestedElBinders = []; var compiler = createCompiler( (parent, current, control) => { + current.inheritedProtoView = new ProtoView(current.element, null, null); if (DOM.hasClass(current.element, 'nested')) { - current.inheritedProtoView = new ProtoView(current.element, null, null); current.inheritedElementBinder = current.inheritedProtoView.bindElement(null); current.componentDirective = reader.read(NestedComponent); ListWrapper.push(nestedElBinders, current.inheritedElementBinder); @@ -134,9 +166,12 @@ export function main() { describe('(mixed async, sync TemplateLoader)', () => { var reader = new DirectiveMetadataReader(); - function createCompiler(processClosure, resolver: TemplateResolver) { + function createCompiler(processClosure, templateResolver: TemplateResolver) { var steps = [new MockStep(processClosure)]; - return new TestableCompiler(reader, steps, new FakeTemplateLoader(), resolver); + var urlResolver = new FakeUrlResolver(); + var tplLoader = new FakeTemplateLoader(urlResolver); + return new TestableCompiler(reader, steps, tplLoader, templateResolver, + urlResolver, new ComponentUrlMapper()); } function createNestedComponentSpec(name, resolver: TemplateResolver, error:string = null) { @@ -167,47 +202,72 @@ export function main() { }); } - var resolver = new FakeTemplateResolver(); - resolver.setSync(ParentComponent); - resolver.setSync(NestedComponent); - createNestedComponentSpec('(sync -> sync)', resolver); + var templateResolver = new FakeTemplateResolver(); + templateResolver.setSync(ParentComponent); + templateResolver.setSync(NestedComponent); + createNestedComponentSpec('(sync -> sync)', templateResolver); - resolver = new FakeTemplateResolver(); - resolver.setAsync(ParentComponent); - resolver.setSync(NestedComponent); - createNestedComponentSpec('(async -> sync)', resolver); + templateResolver = new FakeTemplateResolver(); + templateResolver.setAsync(ParentComponent); + templateResolver.setSync(NestedComponent); + createNestedComponentSpec('(async -> sync)', templateResolver); - resolver = new FakeTemplateResolver(); - resolver.setSync(ParentComponent); - resolver.setAsync(NestedComponent); - createNestedComponentSpec('(sync -> async)', resolver); + templateResolver = new FakeTemplateResolver(); + templateResolver.setSync(ParentComponent); + templateResolver.setAsync(NestedComponent); + createNestedComponentSpec('(sync -> async)', templateResolver); - resolver = new FakeTemplateResolver(); - resolver.setAsync(ParentComponent); - resolver.setAsync(NestedComponent); - createNestedComponentSpec('(async -> async)', resolver); + templateResolver = new FakeTemplateResolver(); + templateResolver.setAsync(ParentComponent); + templateResolver.setAsync(NestedComponent); + createNestedComponentSpec('(async -> async)', templateResolver); - resolver = new FakeTemplateResolver(); - resolver.setError(ParentComponent); - resolver.setSync(NestedComponent); - createNestedComponentSpec('(error -> sync)', resolver, + templateResolver = new FakeTemplateResolver(); + templateResolver.setError(ParentComponent); + templateResolver.setSync(NestedComponent); + createNestedComponentSpec('(error -> sync)', templateResolver, 'Failed to load the template for ParentComponent'); // TODO(vicb): Check why errors this fails with Dart // TODO(vicb): The Promise is rejected with the correct error but an exc is thrown before - //resolver = new FakeTemplateResolver(); - //resolver.setSync(ParentComponent); - //resolver.setError(NestedComponent); - //createNestedComponentSpec('(sync -> error)', resolver, + //templateResolver = new FakeTemplateResolver(); + //templateResolver.setSync(ParentComponent); + //templateResolver.setError(NestedComponent); + //createNestedComponentSpec('(sync -> error)', templateResolver, // 'Failed to load the template for NestedComponent -> Failed to compile ParentComponent'); // - //resolver = new FakeTemplateResolver(); - //resolver.setAsync(ParentComponent); - //resolver.setError(NestedComponent); - //createNestedComponentSpec('(async -> error)', resolver, + //templateResolver = new FakeTemplateResolver(); + //templateResolver.setAsync(ParentComponent); + //templateResolver.setError(NestedComponent); + //createNestedComponentSpec('(async -> error)', templateResolver, // 'Failed to load the template for NestedComponent -> Failed to compile ParentComponent'); }); + + describe('URL resolution', () => { + it('should resolve template URLs by combining application, component and template URLs', (done) => { + var steps = [new MockStep((parent, current, control) => { + current.inheritedProtoView = new ProtoView(current.element, null, null); + })]; + var reader = new DirectiveMetadataReader(); + var tplResolver = new FakeTemplateResolver(); + var urlResolver = new FakeUrlResolver(); + var tplLoader = new FakeTemplateLoader(urlResolver); + var template = new Template({inline: '
', url: '/tpl.html'}); + var cmpUrlMapper = new RuntimeComponentUrlMapper(); + cmpUrlMapper.setComponentUrl(MainComponent, '/cmp'); + + var compiler = new TestableCompiler(reader, steps, tplLoader, tplResolver, + urlResolver, cmpUrlMapper); + + tplResolver.forceSync(); + tplResolver.setTemplate(MainComponent, template); + compiler.compile(MainComponent).then((protoView) => { + expect(tplLoader.getTemplateUrl(template)).toEqual('http://www.app.com/cmp/tpl.html'); + done(); + }); + }) + }); }); } @@ -231,9 +291,17 @@ class TestableCompiler extends Compiler { steps:List; constructor(reader:DirectiveMetadataReader, steps:List, loader: TemplateLoader, - resolver: TemplateResolver) { - super(dynamicChangeDetection, loader, reader, new Parser(new Lexer()), new CompilerCache(), - new NativeShadowDomStrategy(), resolver); + templateResolver: TemplateResolver, urlResolver: UrlResolver, cmpUrlMapper: ComponentUrlMapper) { + super(dynamicChangeDetection, + loader, + reader, + new Parser(new Lexer()), + new CompilerCache(), + new NativeShadowDomStrategy(new StyleUrlResolver(urlResolver)), + templateResolver, + cmpUrlMapper, + urlResolver); + this.steps = steps; } @@ -253,9 +321,23 @@ class MockStep extends CompileStep { } } -class FakeTemplateLoader extends TemplateLoader { +class FakeUrlResolver extends UrlResolver { constructor() { - super(null); + super(); + } + + resolve(baseUrl: string, url: string): string { + if (baseUrl === null && url == './') { + return 'http://www.app.com'; + }; + + return baseUrl + url; + } +} + +class FakeTemplateLoader extends TemplateLoader { + constructor(urlResolver: UrlResolver) { + super(null, urlResolver); } load(template: Template) { @@ -307,14 +389,14 @@ class FakeTemplateResolver extends TemplateResolver { } if (ListWrapper.contains(this._syncCmp, component)) { - return new Template({inline: html}); + return template; } if (ListWrapper.contains(this._asyncCmp, component)) { return new Template({url: html}); } - if (this._forceSync) return new Template({inline: html}); + if (this._forceSync) return template; if (this._forceAsync) return new Template({url: html}); throw 'No template'; diff --git a/modules/angular2/test/core/compiler/component_url_mapper_spec.js b/modules/angular2/test/core/compiler/component_url_mapper_spec.js new file mode 100644 index 0000000000..47a32e53df --- /dev/null +++ b/modules/angular2/test/core/compiler/component_url_mapper_spec.js @@ -0,0 +1,25 @@ +import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'angular2/test_lib'; + +import { + ComponentUrlMapper, + RuntimeComponentUrlMapper +} from 'angular2/src/core/compiler/component_url_mapper'; + +export function main() { + describe('RuntimeComponentUrlMapper', () => { + it('should return the registered URL', () => { + var url = 'http://path/to/component'; + var mapper = new RuntimeComponentUrlMapper(); + mapper.setComponentUrl(SomeComponent, url); + expect(mapper.getUrl(SomeComponent)).toEqual(url); + }); + + it('should fallback to ComponentUrlMapper', () => { + var mapper = new ComponentUrlMapper(); + var runtimeMapper = new RuntimeComponentUrlMapper(); + expect(runtimeMapper.getUrl(SomeComponent)).toEqual(mapper.getUrl(SomeComponent)); + }); + }); +} + +class SomeComponent {} diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index e18c46326e..bbb95b31ea 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -14,6 +14,9 @@ import {NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_str import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock'; import {BindingPropagationConfig} from 'angular2/src/core/compiler/binding_propagation_config'; +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 {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations'; import {Template} from 'angular2/src/core/annotations/template'; @@ -28,13 +31,16 @@ export function main() { var compiler, tplResolver; function createCompiler(tplResolver, changedDetection) { + var urlResolver = new UrlResolver(); return new Compiler(changedDetection, - new TemplateLoader(null), + new TemplateLoader(null, null), new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache(), - new NativeShadowDomStrategy(), - tplResolver + new NativeShadowDomStrategy(new StyleUrlResolver(urlResolver)), + tplResolver, + new ComponentUrlMapper(), + urlResolver ); } diff --git a/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js b/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js index ca00382b26..3dba4875dc 100644 --- a/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js +++ b/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js @@ -72,7 +72,7 @@ export function main() { current.inheritedProtoView = new ProtoView( current.element, new DynamicProtoChangeDetector(normalizeBlank(registry)), - new NativeShadowDomStrategy()); + new NativeShadowDomStrategy(null)); } else if (isPresent(parent)) { current.inheritedProtoView = parent.inheritedProtoView; } @@ -380,7 +380,7 @@ export function main() { var results = pipeline.process(el('
')); var pv = results[0].inheritedProtoView; results[0].inheritedElementBinder.nestedProtoView = new ProtoView( - el('
'), new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy()); + el('
'), new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy(null)); instantiateView(pv); evalContext.prop1 = 'a'; @@ -416,7 +416,7 @@ export function main() { var results = pipeline.process(el('
')); var pv = results[0].inheritedProtoView; results[0].inheritedElementBinder.nestedProtoView = new ProtoView( - el('
'), new DynamicProtoChangeDetector(registry), new NativeShadowDomStrategy()); + el('
'), new DynamicProtoChangeDetector(registry), new NativeShadowDomStrategy(null)); instantiateView(pv); evalContext.prop1 = 'a'; diff --git a/modules/angular2/test/core/compiler/pipeline/proto_view_builder_spec.js b/modules/angular2/test/core/compiler/pipeline/proto_view_builder_spec.js index 82a2dd1cdd..c1f391918a 100644 --- a/modules/angular2/test/core/compiler/pipeline/proto_view_builder_spec.js +++ b/modules/angular2/test/core/compiler/pipeline/proto_view_builder_spec.js @@ -21,7 +21,7 @@ export function main() { current.variableBindings = MapWrapper.createFromStringMap(variableBindings); } current.inheritedElementBinder = new ElementBinder(null, null, null); - }), new ProtoViewBuilder(dynamicChangeDetection, new NativeShadowDomStrategy())]); + }), new ProtoViewBuilder(dynamicChangeDetection, new NativeShadowDomStrategy(null))]); } it('should not create a ProtoView when the isViewRoot flag is not set', () => { diff --git a/modules/angular2/test/core/compiler/pipeline/resolve_css_spec.js b/modules/angular2/test/core/compiler/pipeline/resolve_css_spec.js new file mode 100644 index 0000000000..1eaf5254b9 --- /dev/null +++ b/modules/angular2/test/core/compiler/pipeline/resolve_css_spec.js @@ -0,0 +1,137 @@ +import { + describe, + beforeEach, + expect, + it, + iit, + ddescribe, + el, + SpyObject, + proxy, +} from 'angular2/test_lib'; + +import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline'; +import {ResolveCss} from 'angular2/src/core/compiler/pipeline/resolve_css'; +import {CompileElement} from 'angular2/src/core/compiler/pipeline/compile_element'; +import {CompileStep} from 'angular2/src/core/compiler/pipeline/compile_step'; +import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_control'; + +import {Component} from 'angular2/src/core/annotations/annotations'; +import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata'; +import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; +import {ProtoView} from 'angular2/src/core/compiler/view'; + +import {IMPLEMENTS, Type, stringify} from 'angular2/src/facade/lang'; +import {DOM} from 'angular2/src/facade/dom'; +import {PromiseWrapper} from 'angular2/src/facade/async'; + +export function main() { + describe('ResolveCss', () => { + function createPipeline(strategy:ShadowDomStrategy) { + var annotation = new Component({selector: 'selector'}); + var meta = new DirectiveMetadata(SomeComponent, annotation); + var resolveCss = new ResolveCss(meta, strategy, 'http://base'); + return new CompilePipeline([ + new MockStep((parent, current, control) => { + current.inheritedProtoView = new ProtoView(null, null, null); + }), + resolveCss + ]); + } + + it('it should set ignoreBindings to true for style elements', () => { + var strategy = new DummyStrategy(); + strategy.spy('transformStyleText').andCallFake((a, b, c) => '.css {}'); + strategy.spy('handleStyleElement'); + + var pipeline = createPipeline(strategy); + var results = pipeline.process(el('
')); + expect(results[0].ignoreBindings).toBe(false); + expect(results[1].ignoreBindings).toBe(true); + }); + + it('should delegate the handling of style elements to the strategy', () => { + var strategy = new DummyStrategy(); + strategy.spy('transformStyleText').andCallFake((a, b, c) => '.css {}'); + strategy.spy('handleStyleElement'); + + var pipeline = createPipeline(strategy); + var template = el('
'); + var styleEl = el(''); + DOM.appendChild(template, styleEl); + + pipeline.process(template); + + expect(strategy.spy('handleStyleElement')).toHaveBeenCalledWith(styleEl); + }); + + it('should handle css transformed synchronously', () => { + var strategy = new DummyStrategy(); + strategy.spy('transformStyleText').andCallFake((css, url, cmp) => { + return `${css}, ${url}, ${stringify(cmp)}`; + }); + strategy.spy('handleStyleElement'); + + var pipeline = createPipeline(strategy); + var template = el('
'); + var styleEl = el(''); + DOM.appendChild(template, styleEl); + + var results = pipeline.process(template); + expect(styleEl).toHaveText('/*css*/, http://base, SomeComponent'); + expect(results[0].inheritedProtoView.stylePromises.length).toBe(0); + }); + + it('should handle css transformed asynchronously', (done) => { + var completer = PromiseWrapper.completer(); + var strategy = new DummyStrategy(); + var futureCss; + strategy.spy('transformStyleText').andCallFake((css, url, cmp) => { + futureCss = `${css}, ${url}, ${stringify(cmp)}`; + return completer.promise; + }); + strategy.spy('handleStyleElement'); + + var pipeline = createPipeline(strategy); + var template = el('
'); + var styleEl = el(''); + DOM.appendChild(template, styleEl); + + var results = pipeline.process(template); + + // The css should be empty before the style promise is resolved + expect(styleEl).toHaveText(''); + expect(results[0].inheritedProtoView.stylePromises[0]).toBe(completer.promise); + + completer.resolve(futureCss); + + // TODO(vicb): refactor once we have better support for async tests + completer.promise.then((_) => { + expect(styleEl).toHaveText('/*css*/, http://base, SomeComponent'); + done(); + }); + }); + }); +} + +@proxy +@IMPLEMENTS(ShadowDomStrategy) +class DummyStrategy extends SpyObject { + noSuchMethod(m) { + return super.noSuchMethod(m) + } +} + +class SomeComponent {} + +class MockStep extends CompileStep { + processClosure:Function; + constructor(process) { + super(); + this.processClosure = process; + } + process(parent:CompileElement, current:CompileElement, control:CompileControl) { + this.processClosure(parent, current, control); + } +} + diff --git a/modules/angular2/test/core/compiler/pipeline/shim_shadow_css_spec.js b/modules/angular2/test/core/compiler/pipeline/shim_shadow_css_spec.js deleted file mode 100644 index 679ebb5259..0000000000 --- a/modules/angular2/test/core/compiler/pipeline/shim_shadow_css_spec.js +++ /dev/null @@ -1,102 +0,0 @@ -import {describe, beforeEach, expect, it, iit, ddescribe, el} from 'angular2/test_lib'; - -import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline'; -import {ShimShadowCss} from 'angular2/src/core/compiler/pipeline/shim_shadow_css'; -import {ShimComponent} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_component'; - -import {Component} from 'angular2/src/core/annotations/annotations'; -import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata'; -import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; - -import {Type} from 'angular2/src/facade/lang'; -import {DOM} from 'angular2/src/facade/dom'; - -export function main() { - describe('ShimShadowCss', () => { - function createPipeline(strategy:ShadowDomStrategy, styleHost) { - var component = new Component({selector: 'selector'}); - var meta = new DirectiveMetadata(null, component); - var shimShadowCss = new ShimShadowCss(meta, strategy, styleHost); - return new CompilePipeline([shimShadowCss]); - } - - it('it should set ignoreBindings to true for style elements', () => { - var host = el('
'); - var pipeline = createPipeline(new FakeStrategy(false), host); - var results = pipeline.process(el('
')); - expect(results[0].ignoreBindings).toBe(false); - expect(results[1].ignoreBindings).toBe(true); - }); - - it('should not extract the styles when extractStyles() is false', () => { - var host = el('
'); - var pipeline = createPipeline(new FakeStrategy(false), host); - var template = el(''); - pipeline.process(template); - expect(template).toHaveText('.s{}'); - }); - - it('should move the styles to the host when extractStyles() is true', () => { - var host = el('
'); - var pipeline = createPipeline(new FakeStrategy(true), host); - var template = el('
'); - pipeline.process(template); - expect(template).toHaveText(''); - expect(host).toHaveText('/* shim */.s{}'); - }); - - it('should preserve original content when moving styles', () => { - var host = el('
original content
'); - var pipeline = createPipeline(new FakeStrategy(true), host); - var template = el('
'); - pipeline.process(template); - expect(template).toHaveText(''); - expect(host).toHaveText('/* shim */.s{}original content'); - }); - - it('should preserve attributes on moved style', () => { - var host = el('
'); - var pipeline = createPipeline(new FakeStrategy(true), host); - var template = el('
'); - pipeline.process(template); - var styleEl = DOM.firstChild(host); - expect(DOM.getAttribute(styleEl, 'media')).toEqual('print'); - }); - - it('should move the styles to the host in the original order', () => { - var host = el('
'); - var pipeline = createPipeline(new FakeStrategy(true), host); - var template = el('
'); - pipeline.process(template); - expect(host).toHaveText('/* shim */.s1{}/* shim */.s2{}'); - }); - - }); -} - -class FakeStrategy extends ShadowDomStrategy { - _extractStyles: boolean; - - constructor(extractStyles: boolean) { - super(); - this._extractStyles = extractStyles; - } - - extractStyles(): boolean { - return this._extractStyles; - } - - getShimComponent(component: Type): ShimComponent { - return new FakeShimComponent(component); - } -} - -class FakeShimComponent extends ShimComponent { - constructor(component: Type) { - super(component); - } - - shimCssText(cssText: string): string { - return '/* shim */' + cssText; - } -} diff --git a/modules/angular2/test/core/compiler/pipeline/shim_shadow_dom_spec.js b/modules/angular2/test/core/compiler/pipeline/shim_shadow_dom_spec.js index b61b64c1ba..6018d21016 100644 --- a/modules/angular2/test/core/compiler/pipeline/shim_shadow_dom_spec.js +++ b/modules/angular2/test/core/compiler/pipeline/shim_shadow_dom_spec.js @@ -5,20 +5,19 @@ import {ShimShadowDom} from 'angular2/src/core/compiler/pipeline/shim_shadow_dom import {CompileElement} from 'angular2/src/core/compiler/pipeline/compile_element'; import {CompileStep} from 'angular2/src/core/compiler/pipeline/compile_step'; import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_control'; -import {ShimComponent} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_component'; import {Component} from 'angular2/src/core/annotations/annotations'; import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata'; import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; -import {Type, isBlank} from 'angular2/src/facade/lang'; +import {Type, isBlank, stringify} from 'angular2/src/facade/lang'; import {DOM, Element} from 'angular2/src/facade/dom'; export function main() { describe('ShimShadowDom', () => { function createPipeline(ignoreBindings: boolean) { - var component = new Component({selector: 'selector'}); - var meta = new DirectiveMetadata(null, component); + var annotation = new Component({selector: 'selector'}); + var meta = new DirectiveMetadata(SomeComponent, annotation); var shimShadowDom = new ShimShadowDom(meta, new FakeStrategy()); return new CompilePipeline([ @@ -38,22 +37,22 @@ export function main() { it('should add the content attribute to content element', () => { var pipeline = createPipeline(false); var results = pipeline.process(el('
')); - expect(DOM.getAttribute(results[0].element, '_ngcontent')).toEqual('content'); - expect(isBlank(DOM.getAttribute(results[0].element, '_nghost'))).toBeTruthy(); + expect(DOM.getAttribute(results[0].element, 'SomeComponent-content')).toEqual(''); + expect(isBlank(DOM.getAttribute(results[0].element, 'SomeComponent-host'))).toBeTruthy(); }); it('should add both the content and host attributes to host element', () => { var pipeline = createPipeline(false); var results = pipeline.process(el('
')); - expect(DOM.getAttribute(results[0].element, '_ngcontent')).toEqual('content'); - expect(DOM.getAttribute(results[0].element, '_nghost')).toEqual('host'); + expect(DOM.getAttribute(results[0].element, 'SomeComponent-content')).toEqual(''); + expect(DOM.getAttribute(results[0].element, 'SomeComponent-host')).toEqual(''); }); it('should do nothing when ignoreBindings is true', () => { var pipeline = createPipeline(true); var results = pipeline.process(el('
')); - expect(isBlank(DOM.getAttribute(results[0].element, '_ngcontent'))).toBeTruthy(); - expect(isBlank(DOM.getAttribute(results[0].element, '_nghost'))).toBeTruthy(); + expect(isBlank(DOM.getAttribute(results[0].element, 'SomeComponent-content'))).toBeTruthy(); + expect(isBlank(DOM.getAttribute(results[0].element, 'SomeComponent-host'))).toBeTruthy(); }); }); } @@ -63,22 +62,14 @@ class FakeStrategy extends ShadowDomStrategy { super(); } - getShimComponent(component: Type): ShimComponent { - return new FakeShimComponent(component); - } -} - -class FakeShimComponent extends ShimComponent { - constructor(component: Type) { - super(component); + shimContentElement(component: Type, element: Element) { + var attrName = stringify(component) + '-content'; + DOM.setAttribute(element, attrName, ''); } - shimContentElement(element: Element) { - DOM.setAttribute(element, '_ngcontent', 'content'); - } - - shimHostElement(element: Element) { - DOM.setAttribute(element, '_nghost', 'host'); + shimHostElement(component: Type, element: Element) { + var attrName = stringify(component) + '-host'; + DOM.setAttribute(element, attrName, ''); } } diff --git a/modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js b/modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js index 1b30c55f2f..07f1ee7e8d 100644 --- a/modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js +++ b/modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js @@ -2,6 +2,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular import {StringMapWrapper, List} from 'angular2/src/facade/collection'; import {Type} from 'angular2/src/facade/lang'; +import {DOM} from 'angular2/src/facade/dom'; import {Injector} from 'angular2/di'; import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'angular2/change_detection'; @@ -14,6 +15,11 @@ import {ShadowDomStrategy, NativeShadowDomStrategy, EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; +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'; + import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock'; import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations'; @@ -23,10 +29,13 @@ import {ViewContainer} from 'angular2/src/core/compiler/view_container'; export function main() { describe('integration tests', function() { + var urlResolver = new UrlResolver(); + var styleUrlResolver = new StyleUrlResolver(urlResolver); + var styleInliner = new StyleInliner(null, styleUrlResolver, urlResolver); StringMapWrapper.forEach({ - "native" : new NativeShadowDomStrategy(), - "emulated" : new EmulatedShadowDomStrategy() + "native" : new NativeShadowDomStrategy(styleUrlResolver), + "emulated" : new EmulatedShadowDomStrategy(styleInliner, styleUrlResolver, DOM.createElement('div')) }, (strategy, name) => { @@ -36,12 +45,14 @@ export function main() { beforeEach(() => { tplResolver = new MockTemplateResolver(); compiler = new Compiler(dynamicChangeDetection, - new TemplateLoader(null), + new TemplateLoader(null, null), new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache(), strategy, - tplResolver + tplResolver, + new ComponentUrlMapper(), + urlResolver ); }); diff --git a/modules/angular2/test/core/compiler/shadow_dom/shim_component_spec.js b/modules/angular2/test/core/compiler/shadow_dom/shim_component_spec.js deleted file mode 100644 index 6d5c25a580..0000000000 --- a/modules/angular2/test/core/compiler/shadow_dom/shim_component_spec.js +++ /dev/null @@ -1,128 +0,0 @@ -import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el} from 'angular2/test_lib'; - -import { - ShimNativeComponent, - ShimEmulatedComponent, - resetShimComponentCache -} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_component'; - -import {ShadowCss} from 'angular2/src/core/compiler/shadow_dom_emulation/shadow_css'; - -import {Type} from 'angular2/src/facade/lang'; -import {DOM} from 'angular2/src/facade/dom'; - -export function main() { - describe('ShimComponent', () => { - - describe('ShimNativeComponent', () => { - function createShim(component: Type) { - return new ShimNativeComponent(component); - } - - it('should not transform the CSS', () => { - var css = '.foo {color: blue;} :host{color: red;}'; - var shim = createShim(SomeComponent); - var shimCss = shim.shimCssText(css); - expect(css).toEqual(shimCss); - }); - - it('should not transform content elements', () => { - var html = '

foo

'; - var element = el(html); - var shim = createShim(SomeComponent); - shim.shimContentElement(element); - expect(DOM.getOuterHTML(element)).toEqual(html); - }); - - it('should not transform host elements', () => { - var html = '

foo

'; - var element = el(html); - var shim = createShim(SomeComponent); - shim.shimHostElement(element); - expect(DOM.getOuterHTML(element)).toEqual(html); - }); - }); - - describe('ShimEmulatedComponent', () => { - beforeEach(() => { - resetShimComponentCache(); - }); - - function createShim(component: Type) { - return new ShimEmulatedComponent(component); - } - - it('should transform the CSS', () => { - var css = '.foo {color: blue;} :host{color: red;}'; - var shim = createShim(SomeComponent); - var shimCss = shim.shimCssText(css); - expect(shimCss).not.toEqual(css); - var shadowCss = new ShadowCss(); - expect(shimCss).toEqual(shadowCss.shimCssText(css, '_ngcontent-0', '_nghost-0')); - }); - - it('should transform content elements', () => { - var html = '

foo

'; - var element = el(html); - var shim = createShim(SomeComponent); - shim.shimContentElement(element); - expect(DOM.getOuterHTML(element)).toEqual('

foo

'); - }); - - it('should not transform host elements', () => { - var html = '

foo

'; - var element = el(html); - var shim = createShim(SomeComponent); - shim.shimHostElement(element); - expect(DOM.getOuterHTML(element)).toEqual('

foo

'); - }); - - it('should generate the same output for the same component', () => { - var html = '

foo

'; - var content1 = el(html); - var host1 = el(html); - var css = '.foo {color: blue;} :host{color: red;}'; - var shim1 = createShim(SomeComponent); - shim1.shimContentElement(content1); - shim1.shimHostElement(host1); - var shimCss1 = shim1.shimCssText(css); - - var content2 = el(html); - var host2 = el(html); - var shim2 = createShim(SomeComponent); - shim2.shimContentElement(content2); - shim2.shimHostElement(host2); - var shimCss2 = shim2.shimCssText(css); - - expect(DOM.getOuterHTML(content1)).toEqual(DOM.getOuterHTML(content2)); - expect(DOM.getOuterHTML(host1)).toEqual(DOM.getOuterHTML(host2)); - expect(shimCss1).toEqual(shimCss2); - }); - - it('should generate different outputs for different components', () => { - var html = '

foo

'; - var content1 = el(html); - var host1 = el(html); - var css = '.foo {color: blue;} :host{color: red;}'; - var shim1 = createShim(SomeComponent); - shim1.shimContentElement(content1); - shim1.shimHostElement(host1); - var shimCss1 = shim1.shimCssText(css); - - var content2 = el(html); - var host2 = el(html); - var shim2 = createShim(SomeComponent2); - shim2.shimContentElement(content2); - shim2.shimHostElement(host2); - var shimCss2 = shim2.shimCssText(css); - - expect(DOM.getOuterHTML(content1)).not.toEqual(DOM.getOuterHTML(content2)); - expect(DOM.getOuterHTML(host1)).not.toEqual(DOM.getOuterHTML(host2)); - expect(shimCss1).not.toEqual(shimCss2); - }); - }); - }); -} - -class SomeComponent {} -class SomeComponent2 {} diff --git a/modules/angular2/test/core/compiler/shadow_dom_strategy_spec.js b/modules/angular2/test/core/compiler/shadow_dom_strategy_spec.js new file mode 100644 index 0000000000..105b62098d --- /dev/null +++ b/modules/angular2/test/core/compiler/shadow_dom_strategy_spec.js @@ -0,0 +1,169 @@ +import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el} from 'angular2/test_lib'; + +import { + NativeShadowDomStrategy, + EmulatedShadowDomStrategy, + resetShadowDomCache, +} from 'angular2/src/core/compiler/shadow_dom_strategy'; +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'; +import {ProtoView} from 'angular2/src/core/compiler/view'; +import {XHR} from 'angular2/src/core/compiler/xhr/xhr'; + +import {isPresent, isBlank} from 'angular2/src/facade/lang'; +import {DOM} from 'angular2/src/facade/dom'; +import {Map, MapWrapper} from 'angular2/src/facade/collection'; +import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; + +import {DynamicProtoChangeDetector} from 'angular2/change_detection'; + +export function main() { + var strategy; + + describe('NativeShadowDomStratgey', () => { + beforeEach(() => { + var urlResolver = new UrlResolver(); + var styleUrlResolver = new StyleUrlResolver(urlResolver); + strategy = new NativeShadowDomStrategy(styleUrlResolver); + }); + + it('should attach the view nodes to the shadow root', () => { + var host = el('
'); + var nodes = el('
view
'); + var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null), null); + var view = pv.instantiate(null, null); + + strategy.attachTemplate(host, view); + var shadowRoot = DOM.getShadowRoot(host); + expect(isPresent(shadowRoot)).toBeTruthy(); + expect(shadowRoot).toHaveText('view'); + }); + + it('should rewrite style urls', () => { + var css = '.foo {background-image: url("img.jpg");}'; + expect(strategy.transformStyleText(css, 'http://base', null)) + .toEqual(".foo {background-image: url('http://base/img.jpg');}"); + }); + + it('should not inline import rules', () => { + var css = '@import "other.css";'; + expect(strategy.transformStyleText(css, 'http://base', null)) + .toEqual("@import 'http://base/other.css';"); + }); + }); + + describe('EmulatedShadowDomStratgey', () => { + var xhr, styleHost; + + beforeEach(() => { + var urlResolver = new UrlResolver(); + var styleUrlResolver = new StyleUrlResolver(urlResolver); + xhr = new FakeXHR(); + var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver); + styleHost = el('
'); + strategy = new EmulatedShadowDomStrategy(styleInliner, styleUrlResolver, styleHost); + resetShadowDomCache(); + }); + + it('should attach the view nodes as child of the host element', () => { + var host = el('
original content
'); + var nodes = el('
view
'); + var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null), null); + var view = pv.instantiate(null, null); + + strategy.attachTemplate(host, view); + var firstChild = DOM.firstChild(host); + expect(DOM.tagName(firstChild)).toEqual('DIV'); + expect(firstChild).toHaveText('view'); + expect(host).toHaveText('view'); + }); + + it('should rewrite style urls', () => { + var css = '.foo {background-image: url("img.jpg");}'; + expect(strategy.transformStyleText(css, 'http://base', SomeComponent)) + .toEqual(".foo[_ngcontent-0] {\nbackground-image: url(http://base/img.jpg);\n}"); + }); + + it('should scope style', () => { + var css = '.foo {} :host {}'; + expect(strategy.transformStyleText(css, 'http://base', SomeComponent)) + .toEqual(".foo[_ngcontent-0] {\n\n}\n\n[_nghost-0] {\n\n}"); + }); + + it('should inline @import rules', (done) => { + xhr.reply('http://base/one.css', '.one {}'); + var css = '@import "one.css";'; + var promise = strategy.transformStyleText(css, 'http://base', SomeComponent); + expect(promise).toBePromise(); + promise.then((css) => { + expect(css).toEqual('.one[_ngcontent-0] {\n\n}'); + done(); + }); + }); + + it('should return the same style given the same component', () => { + var css = '.foo {} :host {}'; + expect(strategy.transformStyleText(css, 'http://base', SomeComponent)) + .toEqual(".foo[_ngcontent-0] {\n\n}\n\n[_nghost-0] {\n\n}"); + expect(strategy.transformStyleText(css, 'http://base', SomeComponent)) + .toEqual(".foo[_ngcontent-0] {\n\n}\n\n[_nghost-0] {\n\n}"); + }); + + it('should return different styles given different components', () => { + var css = '.foo {} :host {}'; + expect(strategy.transformStyleText(css, 'http://base', SomeComponent)) + .toEqual(".foo[_ngcontent-0] {\n\n}\n\n[_nghost-0] {\n\n}"); + expect(strategy.transformStyleText(css, 'http://base', SomeOtherComponent)) + .toEqual(".foo[_ngcontent-1] {\n\n}\n\n[_nghost-1] {\n\n}"); + }); + + it('should move the style element to the style host', () => { + var originalHost = el('
'); + var styleEl = el(''); + DOM.appendChild(originalHost, styleEl); + expect(originalHost).toHaveText('/*css*/'); + + strategy.handleStyleElement(styleEl); + expect(originalHost).toHaveText(''); + expect(styleHost).toHaveText('/*css*/'); + }); + + it('should add an attribute to the content elements', () => { + var elt = el('
'); + strategy.shimContentElement(SomeComponent, elt); + expect(DOM.getAttribute(elt, '_ngcontent-0')).toEqual(''); + }); + + it('should add an attribute to the host elements', () => { + var elt = el('
'); + strategy.shimHostElement(SomeComponent, elt); + expect(DOM.getAttribute(elt, '_nghost-0')).toEqual(''); + }); + }); +} + +class FakeXHR extends XHR { + _responses: Map; + + constructor() { + super(); + this._responses = MapWrapper.create(); + } + + get(url: string): Promise { + var response = MapWrapper.get(this._responses, url); + if (isBlank(response)) { + return PromiseWrapper.reject('xhr error'); + } + + return PromiseWrapper.resolve(response); + } + + reply(url: string, response: string) { + MapWrapper.set(this._responses, url, response); + } +} + +class SomeComponent {} +class SomeOtherComponent {} diff --git a/modules/angular2/test/core/compiler/style_inliner_spec.js b/modules/angular2/test/core/compiler/style_inliner_spec.js index 7e3ca2ccdf..561419f4d5 100644 --- a/modules/angular2/test/core/compiler/style_inliner_spec.js +++ b/modules/angular2/test/core/compiler/style_inliner_spec.js @@ -6,24 +6,50 @@ import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {Map, MapWrapper} from 'angular2/src/facade/collection'; import {XHR} from 'angular2/src/core/compiler/xhr/xhr'; +import {UrlResolver} from 'angular2/src/core/compiler/url_resolver'; +import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver'; export function main() { describe('StyleInliner', () => { + var xhr, inliner; + + beforeEach(() => { + xhr = new FakeXHR(); + var urlResolver = new UrlResolver(); + var styleUrlResolver = new StyleUrlResolver(urlResolver); + inliner = new StyleInliner(xhr, styleUrlResolver, urlResolver); + }); + describe('loading', () => { it('should return a string when there is no import statement', () => { var css = '.main {}'; - var loader = new StyleInliner(null); - var loadedCss = loader.inlineImports(css); + var loadedCss = inliner.inlineImports(css, 'http://base'); expect(loadedCss).not.toBePromise(); expect(loadedCss).toEqual(css); }); it('should inline @import rules', (done) => { - var xhr = new FakeXHR(); - xhr.reply('one.css', '.one {}'); - var css = '@import "one.css";.main {}'; - var loader = new StyleInliner(xhr); - var loadedCss = loader.inlineImports(css); + xhr.reply('http://base/one.css', '.one {}'); + var css = '@import url("one.css");.main {}'; + var loadedCss = inliner.inlineImports(css, 'http://base'); + expect(loadedCss).toBePromise(); + PromiseWrapper.then( + loadedCss, + function(css) { + expect(css).toEqual('.one {}\n.main {}'); + done(); + }, + function(e) { + throw 'fail;' + } + ); + }); + + // TODO(vicb): fix the StyleInliner + xit('should support url([unquoted url]) in @import rules', (done) => { + xhr.reply('http://base/one.css', '.one {}'); + var css = '@import url(one.css);.main {}'; + var loadedCss = inliner.inlineImports(css, 'http://base'); expect(loadedCss).toBePromise(); PromiseWrapper.then( loadedCss, @@ -38,15 +64,13 @@ export function main() { }); it('should handle @import error gracefuly', (done) => { - var xhr = new FakeXHR(); var css = '@import "one.css";.main {}'; - var loader = new StyleInliner(xhr); - var loadedCss = loader.inlineImports(css); + var loadedCss = inliner.inlineImports(css, 'http://base'); expect(loadedCss).toBePromise(); PromiseWrapper.then( loadedCss, function(css) { - expect(css).toEqual('/* failed to import one.css */\n.main {}'); + expect(css).toEqual('/* failed to import http://base/one.css */\n.main {}'); done(); }, function(e) { @@ -56,12 +80,10 @@ export function main() { }); it('should inline multiple @import rules', (done) => { - var xhr = new FakeXHR(); - xhr.reply('one.css', '.one {}'); - xhr.reply('two.css', '.two {}'); + xhr.reply('http://base/one.css', '.one {}'); + xhr.reply('http://base/two.css', '.two {}'); var css = '@import "one.css";@import "two.css";.main {}'; - var loader = new StyleInliner(xhr); - var loadedCss = loader.inlineImports(css); + var loadedCss = inliner.inlineImports(css, 'http://base'); expect(loadedCss).toBePromise(); PromiseWrapper.then( loadedCss, @@ -76,12 +98,10 @@ export function main() { }); it('should inline nested @import rules', (done) => { - var xhr = new FakeXHR(); - xhr.reply('one.css', '@import "two.css";.one {}'); - xhr.reply('two.css', '.two {}'); + xhr.reply('http://base/one.css', '@import "two.css";.one {}'); + xhr.reply('http://base/two.css', '.two {}'); var css = '@import "one.css";.main {}'; - var loader = new StyleInliner(xhr); - var loadedCss = loader.inlineImports(css); + var loadedCss = inliner.inlineImports(css, 'http://base/'); expect(loadedCss).toBePromise(); PromiseWrapper.then( loadedCss, @@ -96,12 +116,10 @@ export function main() { }); it('should handle circular dependencies gracefuly', (done) => { - var xhr = new FakeXHR(); - xhr.reply('one.css', '@import "two.css";.one {}'); - xhr.reply('two.css', '@import "one.css";.two {}'); + xhr.reply('http://base/one.css', '@import "two.css";.one {}'); + xhr.reply('http://base/two.css', '@import "one.css";.two {}'); var css = '@import "one.css";.main {}'; - var loader = new StyleInliner(xhr); - var loadedCss = loader.inlineImports(css); + var loadedCss = inliner.inlineImports(css, 'http://base/'); expect(loadedCss).toBePromise(); PromiseWrapper.then( loadedCss, @@ -115,15 +133,29 @@ export function main() { ); }); + it('should handle invalid @import fracefuly', (done) => { + // Invalid rule: the url is not quoted + var css = '@import one.css;.main {}'; + var loadedCss = inliner.inlineImports(css, 'http://base/'); + expect(loadedCss).toBePromise(); + PromiseWrapper.then( + loadedCss, + function(css) { + expect(css).toEqual('/* Invalid import rule: "@import one.css;" */.main {}'); + done(); + }, + function(e) { + throw 'fail;' + } + ); + }); }); describe('media query', () => { it('should wrap inlined content in media query', (done) => { - var xhr = new FakeXHR(); - xhr.reply('one.css', '.one {}'); + xhr.reply('http://base/one.css', '.one {}'); var css = '@import "one.css" (min-width: 700px) and (orientation: landscape);'; - var loader = new StyleInliner(xhr); - var loadedCss = loader.inlineImports(css); + var loadedCss = inliner.inlineImports(css, 'http://base/'); expect(loadedCss).toBePromise(); PromiseWrapper.then( loadedCss, @@ -136,9 +168,31 @@ export function main() { } ); }); - }); + describe('url rewritting', () => { + it('should rewrite url in inlined content', (done) => { + // it should rewrite both '@import' and 'url()' + xhr.reply('http://base/one.css', '@import "./nested/two.css";.one {background-image: url("one.jpg");}'); + xhr.reply('http://base/nested/two.css', '.two {background-image: url("../img/two.jpg");}'); + var css = '@import "one.css";' + var loadedCss = inliner.inlineImports(css, 'http://base/'); + expect(loadedCss).toBePromise(); + PromiseWrapper.then( + loadedCss, + function(css) { + expect(css).toEqual( + ".two {background-image: url('http://base/img/two.jpg');}\n" + + ".one {background-image: url('http://base/one.jpg');}\n" + ); + done(); + }, + function(e) { + throw 'fail;' + } + ); + }); + }); }); } diff --git a/modules/angular2/test/core/compiler/style_url_resolver_spec.js b/modules/angular2/test/core/compiler/style_url_resolver_spec.js index d22b87d9a5..dd86316fa6 100644 --- a/modules/angular2/test/core/compiler/style_url_resolver_spec.js +++ b/modules/angular2/test/core/compiler/style_url_resolver_spec.js @@ -12,20 +12,24 @@ export function main() { @import "2.css"; @import url('3.css'); @import url("4.css"); + @import url(5.css); .foo { background-image: url("double.jpg"); background-image: url('simple.jpg'); + background-image: url(noquote.jpg); }`; var expectedCss = ` @import 'base/1.css'; @import 'base/2.css'; @import url('base/3.css'); @import url('base/4.css'); + @import url('base/5.css'); .foo { background-image: url('base/double.jpg'); background-image: url('base/simple.jpg'); + background-image: url('base/noquote.jpg'); }`; var resolvedCss = styleUrlResolver.resolveUrls(css, 'base'); diff --git a/modules/angular2/test/core/compiler/template_loader_spec.js b/modules/angular2/test/core/compiler/template_loader_spec.js index 4a9f20e074..0dac934bfd 100644 --- a/modules/angular2/test/core/compiler/template_loader_spec.js +++ b/modules/angular2/test/core/compiler/template_loader_spec.js @@ -1,6 +1,7 @@ import {describe, it, expect, beforeEach, ddescribe, iit, xit, el} from 'angular2/test_lib'; import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; +import {UrlResolver} from 'angular2/src/core/compiler/url_resolver'; import {Template} from 'angular2/src/core/annotations/template'; @@ -14,7 +15,7 @@ export function main() { beforeEach(() => { xhr = new XHRMock() - loader = new TemplateLoader(xhr); + loader = new TemplateLoader(xhr, new FakeUrlResolver()); }); it('should load inline templates synchronously', () => { @@ -23,8 +24,9 @@ export function main() { }); it('should load templates through XHR', (done) => { - xhr.expect('/foo', 'xhr template'); + xhr.expect('base/foo', 'xhr template'); var template = new Template({url: '/foo'}); + loader.setBaseUrl(template, 'base'); loader.load(template).then((el) => { expect(el.content).toHaveText('xhr template'); done(); @@ -34,8 +36,9 @@ export function main() { it('should cache template loaded through XHR', (done) => { var firstEl; - xhr.expect('/foo', 'xhr template'); + xhr.expect('base/foo', 'xhr template'); var template = new Template({url: '/foo'}); + loader.setBaseUrl(template, 'base'); loader.load(template) .then((el) => { firstEl = el; @@ -56,12 +59,13 @@ export function main() { }); it('should return a rejected Promise when xhr loading fails', (done) => { - xhr.expect('/foo', null); + xhr.expect('base/foo', null); var template = new Template({url: '/foo'}); + loader.setBaseUrl(template, 'base'); PromiseWrapper.then(loader.load(template), function(_) { throw 'Unexpected response'; }, function(error) { - expect(error).toEqual('Failed to load /foo'); + expect(error).toEqual('Failed to load base/foo'); done(); } ) @@ -73,3 +77,13 @@ export function main() { class SomeComponent { } + +class FakeUrlResolver extends UrlResolver { + constructor() { + super(); + } + + resolve(baseUrl: string, url: string): string { + return baseUrl + url; + } +} diff --git a/modules/angular2/test/core/compiler/view_container_spec.js b/modules/angular2/test/core/compiler/view_container_spec.js index 7e0a4b0260..a51dec2a96 100644 --- a/modules/angular2/test/core/compiler/view_container_spec.js +++ b/modules/angular2/test/core/compiler/view_container_spec.js @@ -69,7 +69,8 @@ export function main() { dom = el(`
`); var insertionElement = dom.childNodes[1]; parentView = createView([dom.childNodes[0]]); - protoView = new ProtoView(el('
hi
'), new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy()); + protoView = new ProtoView(el('
hi
'), new DynamicProtoChangeDetector(null), + new NativeShadowDomStrategy(null)); elementInjector = new ElementInjector(null, null, null, null); viewContainer = new ViewContainer(parentView, insertionElement, protoView, elementInjector, null); customViewWithOneNode = createView([el('
single
')]); @@ -213,7 +214,7 @@ export function main() { viewContainer.hydrate(new Injector([]), null); var pv = new ProtoView(el('
{{}}
'), - new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy()); + new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy(null)); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindTextNode(0, parser.parseBinding('foo', null)); fancyView = pv.instantiate(null, null); diff --git a/modules/angular2/test/core/compiler/view_spec.js b/modules/angular2/test/core/compiler/view_spec.js index b30e57137e..762f23279a 100644 --- a/modules/angular2/test/core/compiler/view_spec.js +++ b/modules/angular2/test/core/compiler/view_spec.js @@ -195,7 +195,7 @@ export function main() { it('should be supported.', () => { var template = el('
'); var pv = new ProtoView(template, new DynamicProtoChangeDetector(null), - new NativeShadowDomStrategy()); + new NativeShadowDomStrategy(null)); pv.instantiateInPlace = true; var view = pv.instantiate(null, null); view.hydrate(null, null, null); @@ -205,7 +205,7 @@ export function main() { it('should be off by default.', () => { var template = el('
') var view = new ProtoView(template, new DynamicProtoChangeDetector(null), - new NativeShadowDomStrategy()) + new NativeShadowDomStrategy(null)) .instantiate(null, null); view.hydrate(null, null, null); expect(view.nodes[0]).not.toBe(template); @@ -312,7 +312,7 @@ export function main() { function createComponentWithSubPV(subProtoView) { var pv = new ProtoView(el(''), - new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy()); + new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy(null)); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true)); binder.componentDirective = someComponentDirective; binder.nestedProtoView = subProtoView; @@ -396,7 +396,7 @@ export function main() { new DynamicProtoChangeDetector(null), null); var pv = new ProtoView(el(''), - new DynamicProtoChangeDetector(null), new EmulatedShadowDomStrategy()); + new DynamicProtoChangeDetector(null), new EmulatedShadowDomStrategy(null, null, null)); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true)); binder.componentDirective = new DirectiveMetadataReader().read(SomeComponent); binder.nestedProtoView = subpv; @@ -412,7 +412,7 @@ export function main() { var templateProtoView = new ProtoView( el('
'), new DynamicProtoChangeDetector(null), null); var pv = new ProtoView(el(''), - new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy()); + new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy(null)); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeViewport])); binder.viewportDirective = someViewportDirective; binder.nestedProtoView = templateProtoView; @@ -606,12 +606,13 @@ export function main() { beforeEach(() => { element = DOM.createElement('div'); pv = new ProtoView(el('
hi
'), new DynamicProtoChangeDetector(null), - new NativeShadowDomStrategy()); + new NativeShadowDomStrategy(null)); }); it('should create the root component when instantiated', () => { var rootProtoView = ProtoView.createRootProtoView(pv, element, - someComponentDirective, new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy()); + someComponentDirective, new DynamicProtoChangeDetector(null), + new NativeShadowDomStrategy(null)); var view = rootProtoView.instantiate(null, null); view.hydrate(new Injector([]), null, null); expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null); @@ -619,7 +620,8 @@ export function main() { it('should inject the protoView into the shadowDom', () => { var rootProtoView = ProtoView.createRootProtoView(pv, element, - someComponentDirective, new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy()); + someComponentDirective, new DynamicProtoChangeDetector(null), + new NativeShadowDomStrategy(null)); var view = rootProtoView.instantiate(null, null); view.hydrate(new Injector([]), null, null); expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi'); diff --git a/modules/angular2/test/directives/foreach_spec.js b/modules/angular2/test/directives/foreach_spec.js index cb88a91dca..d17fadfef9 100644 --- a/modules/angular2/test/directives/foreach_spec.js +++ b/modules/angular2/test/directives/foreach_spec.js @@ -10,6 +10,9 @@ import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; +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 {Template} from 'angular2/src/core/annotations/template'; import {Decorator, Component, Viewport} from 'angular2/src/core/annotations/annotations'; @@ -22,10 +25,18 @@ export function main() { describe('foreach', () => { var view, cd, compiler, component, tplResolver; beforeEach(() => { + var urlResolver = new UrlResolver(); tplResolver = new MockTemplateResolver(); - compiler = new Compiler(dynamicChangeDetection, new TemplateLoader(null), - new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache(), - new NativeShadowDomStrategy(), tplResolver); + compiler = new Compiler( + dynamicChangeDetection, + new TemplateLoader(null, null), + new DirectiveMetadataReader(), + new Parser(new Lexer()), + new CompilerCache(), + new NativeShadowDomStrategy(new StyleUrlResolver(urlResolver)), + tplResolver, + new ComponentUrlMapper(), + urlResolver); }); function createView(pv) { diff --git a/modules/angular2/test/directives/if_spec.js b/modules/angular2/test/directives/if_spec.js index b7baead930..37297af517 100644 --- a/modules/angular2/test/directives/if_spec.js +++ b/modules/angular2/test/directives/if_spec.js @@ -9,6 +9,9 @@ import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; +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 {Component} from 'angular2/src/core/annotations/annotations'; import {Template} from 'angular2/src/core/annotations/template'; @@ -22,10 +25,18 @@ export function main() { var view, cd, compiler, component, tplResolver; beforeEach(() => { + var urlResolver = new UrlResolver(); tplResolver = new MockTemplateResolver(); - compiler = new Compiler(dynamicChangeDetection, new TemplateLoader(null), - new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache(), - new NativeShadowDomStrategy(), tplResolver); + compiler = new Compiler( + dynamicChangeDetection, + new TemplateLoader(null, null), + new DirectiveMetadataReader(), + new Parser(new Lexer()), + new CompilerCache(), + new NativeShadowDomStrategy(new StyleUrlResolver(urlResolver)), + tplResolver, + new ComponentUrlMapper(), + urlResolver); }); function createView(pv) { diff --git a/modules/angular2/test/directives/non_bindable_spec.js b/modules/angular2/test/directives/non_bindable_spec.js index fc1c35db13..71d51dc2a3 100644 --- a/modules/angular2/test/directives/non_bindable_spec.js +++ b/modules/angular2/test/directives/non_bindable_spec.js @@ -2,11 +2,17 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular import {DOM} from 'angular2/src/facade/dom'; import {Injector} from 'angular2/di'; import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'angular2/change_detection'; + import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; +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 {Decorator, Component} from 'angular2/src/core/annotations/annotations'; import {Template} from 'angular2/src/core/annotations/template'; + import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; import {NgElement} from 'angular2/src/core/dom/element'; import {NonBindable} from 'angular2/src/directives/non_bindable'; @@ -16,10 +22,18 @@ export function main() { describe('non-bindable', () => { var view, cd, compiler, component, tplResolver; beforeEach(() => { + var urlResolver = new UrlResolver(); tplResolver = new MockTemplateResolver(); - compiler = new Compiler(dynamicChangeDetection, new TemplateLoader(null), - new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache(), - new NativeShadowDomStrategy(), tplResolver); + compiler = new Compiler( + dynamicChangeDetection, + new TemplateLoader(null, null), + new DirectiveMetadataReader(), + new Parser(new Lexer()), + new CompilerCache(), + new NativeShadowDomStrategy(new StyleUrlResolver(urlResolver)), + tplResolver, + new ComponentUrlMapper(), + urlResolver); }); function createView(pv) { diff --git a/modules/angular2/test/directives/switch_spec.js b/modules/angular2/test/directives/switch_spec.js index c8ab1c62ae..e719ecc697 100644 --- a/modules/angular2/test/directives/switch_spec.js +++ b/modules/angular2/test/directives/switch_spec.js @@ -2,9 +2,14 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular import {DOM} from 'angular2/src/facade/dom'; import {Injector} from 'angular2/di'; import {Lexer, Parser, dynamicChangeDetection} from 'angular2/change_detection'; + import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; +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 {Component} from 'angular2/src/core/annotations/annotations'; import {Template} from 'angular2/src/core/annotations/template'; import {TemplateLoader} from 'angular2/core'; @@ -15,10 +20,18 @@ export function main() { describe('switch', () => { var view, cd, compiler, component, tplResolver; beforeEach(() => { + var urlResolver = new UrlResolver(); tplResolver = new MockTemplateResolver(); - compiler = new Compiler(dynamicChangeDetection, new TemplateLoader(null), - new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache(), - new NativeShadowDomStrategy(), tplResolver); + compiler = new Compiler( + dynamicChangeDetection, + new TemplateLoader(null, null), + new DirectiveMetadataReader(), + new Parser(new Lexer()), + new CompilerCache(), + new NativeShadowDomStrategy(new StyleUrlResolver(urlResolver)), + tplResolver, + new ComponentUrlMapper(), + urlResolver); }); function createView(pv) { diff --git a/modules/angular2/test/forms/integration_spec.js b/modules/angular2/test/forms/integration_spec.js index 0ed81dc28a..7f4ad6743d 100644 --- a/modules/angular2/test/forms/integration_spec.js +++ b/modules/angular2/test/forms/integration_spec.js @@ -6,6 +6,10 @@ import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; +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 {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock'; import {Injector} from 'angular2/di'; @@ -22,14 +26,17 @@ export function main() { function compile(componentType, template, context, callback) { var tplResolver = new MockTemplateResolver(); + var urlResolver = new UrlResolver(); var compiler = new Compiler(dynamicChangeDetection, - new TemplateLoader(null), + new TemplateLoader(null, null), new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache(), - new NativeShadowDomStrategy(), - tplResolver + new NativeShadowDomStrategy(new StyleUrlResolver(urlResolver)), + tplResolver, + new ComponentUrlMapper(), + urlResolver ); tplResolver.setTemplate(componentType, new Template({ diff --git a/modules/benchmarks/src/compiler/compiler_benchmark.js b/modules/benchmarks/src/compiler/compiler_benchmark.js index 3ea9c85d67..22b2eb4d44 100644 --- a/modules/benchmarks/src/compiler/compiler_benchmark.js +++ b/modules/benchmarks/src/compiler/compiler_benchmark.js @@ -14,6 +14,9 @@ import {Decorator} from 'angular2/src/core/annotations/annotations'; import {Template} from 'angular2/src/core/annotations/template'; import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; +import {UrlResolver} from 'angular2/src/core/compiler/url_resolver'; +import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver'; +import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {reflector} from 'angular2/src/reflection/reflection'; import {getIntParameter, bindAction} from 'angular2/src/test_lib/benchmark_util'; @@ -83,8 +86,18 @@ export function main() { var reader = new DirectiveMetadataReader(); var cache = new CompilerCache(); var templateResolver = new FakeTemplateResolver(); - var compiler = new Compiler(dynamicChangeDetection, new TemplateLoader(null), - reader, new Parser(new Lexer()), cache, new NativeShadowDomStrategy(), templateResolver); + var urlResolver = new UrlResolver(); + var styleUrlResolver = new StyleUrlResolver(urlResolver); + var compiler = new Compiler( + dynamicChangeDetection, + new TemplateLoader(null, urlResolver), + reader, + new Parser(new Lexer()), + cache, + new NativeShadowDomStrategy(styleUrlResolver), + templateResolver, + new ComponentUrlMapper(), + urlResolver); var templateNoBindings = createTemplateHtml('templateNoBindings', count); var templateWithBindings = createTemplateHtml('templateWithBindings', count); diff --git a/modules/benchmarks/src/naive_infinite_scroll/index.js b/modules/benchmarks/src/naive_infinite_scroll/index.js index 06dd5080ee..2dfe3c5be5 100644 --- a/modules/benchmarks/src/naive_infinite_scroll/index.js +++ b/modules/benchmarks/src/naive_infinite_scroll/index.js @@ -17,6 +17,10 @@ import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; import {XHR} from 'angular2/src/core/compiler/xhr/xhr'; import {XHRImpl} from 'angular2/src/core/compiler/xhr/xhr_impl'; +import {UrlResolver} from 'angular2/src/core/compiler/url_resolver'; +import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver'; +import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; +import {StyleInliner} from 'angular2/src/core/compiler/style_inliner'; import {If, Foreach} from 'angular2/directives'; import {App, setupReflectorForApp} from './app'; @@ -176,11 +180,12 @@ export function setupReflectorForAngular() { reflector.registerType(Compiler, { "factory": (changeDetection, templateLoader, reader, parser, compilerCache, shadowDomStrategy, - resolver) => + tplResolver, cmpUrlMapper, urlResolver) => new Compiler(changeDetection, templateLoader, reader, parser, compilerCache, shadowDomStrategy, - resolver), + tplResolver, cmpUrlMapper, urlResolver), "parameters": [[ChangeDetection], [TemplateLoader], [DirectiveMetadataReader], [Parser], - [CompilerCache], [ShadowDomStrategy], [TemplateResolver]], + [CompilerCache], [ShadowDomStrategy], [TemplateResolver], [ComponentUrlMapper], + [UrlResolver]], "annotations": [] }); @@ -197,8 +202,8 @@ export function setupReflectorForAngular() { }); reflector.registerType(TemplateLoader, { - "factory": (xhr) => new TemplateLoader(xhr), - "parameters": [[XHR]], + "factory": (xhr, urlResolver) => new TemplateLoader(xhr, urlResolver), + "parameters": [[XHR], [UrlResolver]], "annotations": [] }); @@ -239,9 +244,38 @@ export function setupReflectorForAngular() { }); reflector.registerType(ShadowDomStrategy, { - "factory": () => new NativeShadowDomStrategy(), + "factory": (strategy) => strategy, + "parameters": [[NativeShadowDomStrategy]], + "annotations": [] + }); + + reflector.registerType(NativeShadowDomStrategy, { + "factory": (styleUrlResolver) => new NativeShadowDomStrategy(styleUrlResolver), + "parameters": [[StyleUrlResolver]], + "annotations": [] + }); + + reflector.registerType(StyleUrlResolver, { + "factory": (urlResolver) => new StyleUrlResolver(urlResolver), + "parameters": [[UrlResolver]], + "annotations": [] + }); + + reflector.registerType(UrlResolver, { + "factory": () => new UrlResolver(), "parameters": [], "annotations": [] }); + reflector.registerType(ComponentUrlMapper, { + "factory": () => new ComponentUrlMapper(), + "parameters": [], + "annotations": [] + }); + + reflector.registerType(StyleInliner, { + "factory": () => new StyleInliner(), + "parameters": [], + "annotations": [] + }); } diff --git a/modules/benchmarks/src/tree/tree_benchmark.js b/modules/benchmarks/src/tree/tree_benchmark.js index 58269ca489..b111de635a 100644 --- a/modules/benchmarks/src/tree/tree_benchmark.js +++ b/modules/benchmarks/src/tree/tree_benchmark.js @@ -10,6 +10,10 @@ import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; +import {UrlResolver} from 'angular2/src/core/compiler/url_resolver'; +import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver'; +import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; +import {StyleInliner} from 'angular2/src/core/compiler/style_inliner'; import {reflector} from 'angular2/src/reflection/reflection'; import {DOM, document, window, Element, gc} from 'angular2/src/facade/dom'; @@ -62,10 +66,13 @@ function setupReflector() { }); reflector.registerType(Compiler, { - 'factory': (cd, templateLoader, reader, parser, compilerCache, strategy, resolver) => - new Compiler(cd, templateLoader, reader, parser, compilerCache, strategy, resolver), + 'factory': (cd, templateLoader, reader, parser, compilerCache, strategy, tplResolver, + cmpUrlMapper, urlResolver) => + new Compiler(cd, templateLoader, reader, parser, compilerCache, strategy, tplResolver, + cmpUrlMapper, urlResolver), 'parameters': [[ChangeDetection], [TemplateLoader], [DirectiveMetadataReader], - [Parser], [CompilerCache], [ShadowDomStrategy], [TemplateResolver]], + [Parser], [CompilerCache], [ShadowDomStrategy], [TemplateResolver], + [ComponentUrlMapper], [UrlResolver]], 'annotations': [] }); @@ -82,8 +89,8 @@ function setupReflector() { }); reflector.registerType(TemplateLoader, { - 'factory': (xhr) => new TemplateLoader(xhr), - 'parameters': [[XHR]], + 'factory': (xhr, urlResolver) => new TemplateLoader(xhr, urlResolver), + 'parameters': [[XHR], [UrlResolver]], 'annotations': [] }); @@ -106,9 +113,27 @@ function setupReflector() { }); reflector.registerType(ShadowDomStrategy, { - 'factory': () => new NativeShadowDomStrategy(), - 'parameters': [], - 'annotations': [] + "factory": (strategy) => strategy, + "parameters": [[NativeShadowDomStrategy]], + "annotations": [] + }); + + reflector.registerType(NativeShadowDomStrategy, { + "factory": (styleUrlResolver) => new NativeShadowDomStrategy(styleUrlResolver), + "parameters": [[StyleUrlResolver]], + "annotations": [] + }); + + reflector.registerType(StyleUrlResolver, { + "factory": (urlResolver) => new StyleUrlResolver(urlResolver), + "parameters": [[UrlResolver]], + "annotations": [] + }); + + reflector.registerType(UrlResolver, { + "factory": () => new UrlResolver(), + "parameters": [], + "annotations": [] }); reflector.registerType(Lexer, { @@ -130,6 +155,18 @@ function setupReflector() { }); + reflector.registerType(ComponentUrlMapper, { + "factory": () => new ComponentUrlMapper(), + "parameters": [], + "annotations": [] + }); + + reflector.registerType(StyleInliner, { + "factory": () => new StyleInliner(), + "parameters": [], + "annotations": [] + }); + reflector.registerGetters({ 'value': (a) => a.value, 'left': (a) => a.left, diff --git a/modules/examples/src/hello_world/index_static.js b/modules/examples/src/hello_world/index_static.js index ce3b594186..b26af1365f 100644 --- a/modules/examples/src/hello_world/index_static.js +++ b/modules/examples/src/hello_world/index_static.js @@ -12,6 +12,10 @@ import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; import {XHR} from 'angular2/src/core/compiler/xhr/xhr'; import {XHRImpl} from 'angular2/src/core/compiler/xhr/xhr_impl'; +import {UrlResolver} from 'angular2/src/core/compiler/url_resolver'; +import {StyleUrlResolver} from 'angular2/src/core/compiler/style_url_resolver'; +import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; +import {StyleInliner} from 'angular2/src/core/compiler/style_inliner'; import {reflector} from 'angular2/src/reflection/reflection'; @@ -45,11 +49,12 @@ function setup() { reflector.registerType(Compiler, { "factory": (changeDetection, templateLoader, reader, parser, compilerCache, shadowDomStrategy, - resolver) => + tplResolver, cmpUrlMapper, urlResolver) => new Compiler(changeDetection, templateLoader, reader, parser, compilerCache, shadowDomStrategy, - resolver), + tplResolver, cmpUrlMapper, urlResolver), "parameters": [[ChangeDetection], [TemplateLoader], [DirectiveMetadataReader], [Parser], - [CompilerCache], [ShadowDomStrategy], [TemplateResolver]], + [CompilerCache], [ShadowDomStrategy], [TemplateResolver], [ComponentUrlMapper], + [UrlResolver]], "annotations": [] }); @@ -66,8 +71,8 @@ function setup() { }); reflector.registerType(TemplateLoader, { - "factory": (xhr) => new TemplateLoader(xhr), - "parameters": [[XHR]], + "factory": (xhr, urlResolver) => new TemplateLoader(xhr, urlResolver), + "parameters": [[XHR], [UrlResolver]], "annotations": [] }); @@ -108,7 +113,37 @@ function setup() { }); reflector.registerType(ShadowDomStrategy, { - "factory": () => new NativeShadowDomStrategy(), + "factory": (strategy) => strategy, + "parameters": [[NativeShadowDomStrategy]], + "annotations": [] + }); + + reflector.registerType(NativeShadowDomStrategy, { + "factory": (styleUrlResolver) => new NativeShadowDomStrategy(styleUrlResolver), + "parameters": [[StyleUrlResolver]], + "annotations": [] + }); + + reflector.registerType(StyleUrlResolver, { + "factory": (urlResolver) => new StyleUrlResolver(urlResolver), + "parameters": [[UrlResolver]], + "annotations": [] + }); + + reflector.registerType(UrlResolver, { + "factory": () => new UrlResolver(), + "parameters": [], + "annotations": [] + }); + + reflector.registerType(ComponentUrlMapper, { + "factory": () => new ComponentUrlMapper(), + "parameters": [], + "annotations": [] + }); + + reflector.registerType(StyleInliner, { + "factory": () => new StyleInliner(), "parameters": [], "annotations": [] });