feat(compiler): implement style encapsulation for new view engine (#14518)

Included refactoring:
- splits the `RendererV2` into a `RendererFactoryV2` and a `RendererV2`
- makes the `DebugRendererV2` a private class in `@angular/core`
- remove `setBindingDebugInfo` from `RendererV2`, but rename `RendererV2.setText` to 
  `RendererV2.setValue` and allow it on comments and text nodes.

Part of #14013
This commit is contained in:
Tobias Bosch
2017-02-16 13:55:55 -08:00
committed by Igor Minar
parent ba17dcbf2b
commit 0fa3895d5b
38 changed files with 828 additions and 595 deletions

View File

@ -21,8 +21,6 @@ export type RenderDebugInfo = typeof r._RenderDebugInfo;
export const RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
export type DebugDomRootRenderer = typeof r._DebugDomRootRenderer;
export const DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
export type DebugDomRendererV2 = typeof r._DebugDomRendererV2;
export const DebugDomRendererV2: typeof r.DebugDomRendererV2 = r.DebugDomRendererV2;
export type ALLOW_MULTIPLE_PLATFORMS = typeof r.ALLOW_MULTIPLE_PLATFORMS;
export const ALLOW_MULTIPLE_PLATFORMS: typeof r.ALLOW_MULTIPLE_PLATFORMS =
r.ALLOW_MULTIPLE_PLATFORMS;

View File

@ -8,7 +8,7 @@
import {PlatformLocation} from '@angular/common';
import {platformCoreDynamic} from '@angular/compiler';
import {APP_BOOTSTRAP_LISTENER, Injectable, InjectionToken, Injector, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RENDERER_V2_DIRECT, RendererV2, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {APP_BOOTSTRAP_LISTENER, Injectable, InjectionToken, Injector, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactoryV2, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {HttpModule} from '@angular/http';
import {BrowserModule, DOCUMENT} from '@angular/platform-browser';
@ -16,9 +16,9 @@ import {SERVER_HTTP_PROVIDERS} from './http';
import {ServerPlatformLocation} from './location';
import {Parse5DomAdapter, parseDocument} from './parse5_adapter';
import {PlatformState} from './platform_state';
import {ALLOW_MULTIPLE_PLATFORMS, DebugDomRendererV2, DebugDomRootRenderer} from './private_import_core';
import {ALLOW_MULTIPLE_PLATFORMS, DebugDomRootRenderer} from './private_import_core';
import {SharedStylesHost, getDOM} from './private_import_platform-browser';
import {ServerRendererV2, ServerRootRenderer} from './server_renderer';
import {ServerRendererFactoryV2, ServerRootRenderer} from './server_renderer';
import {ServerStylesHost} from './styles_host';
import {INITIAL_CONFIG, PlatformConfig} from './tokens';
@ -42,10 +42,6 @@ export function _createConditionalRootRenderer(rootRenderer: any) {
return isDevMode() ? new DebugDomRootRenderer(rootRenderer) : rootRenderer;
}
export function _createDebugRendererV2(renderer: RendererV2): RendererV2 {
return isDevMode() ? new DebugDomRendererV2(renderer) : renderer;
}
export function _addStylesToRootComponentFactory(stylesHost: ServerStylesHost) {
const initializer = () => stylesHost.rootComponentIsReady();
return initializer;
@ -53,9 +49,9 @@ export function _addStylesToRootComponentFactory(stylesHost: ServerStylesHost) {
export const SERVER_RENDER_PROVIDERS: Provider[] = [
ServerRootRenderer,
{provide: RENDERER_V2_DIRECT, useClass: ServerRendererV2},
{provide: RendererV2, useFactory: _createDebugRendererV2, deps: [RENDERER_V2_DIRECT]},
{provide: RootRenderer, useFactory: _createConditionalRootRenderer, deps: [ServerRootRenderer]},
ServerRendererFactoryV2,
{provide: RendererFactoryV2, useExisting: ServerRendererFactoryV2},
ServerStylesHost,
{provide: SharedStylesHost, useExisting: ServerStylesHost},
{

View File

@ -7,7 +7,7 @@
*/
import {DomElementSchemaRegistry} from '@angular/compiler';
import {APP_ID, Inject, Injectable, NgZone, RenderComponentType, Renderer, RendererV2, RootRenderer, ViewEncapsulation} from '@angular/core';
import {APP_ID, ComponentRenderTypeV2, Inject, Injectable, NgZone, RenderComponentType, Renderer, RendererFactoryV2, RendererV2, RootRenderer, ViewEncapsulation} from '@angular/core';
import {AnimationDriver, DOCUMENT} from '@angular/platform-browser';
import {isBlank, isPresent, stringify} from './facade/lang';
@ -204,6 +204,7 @@ export class ServerRenderer implements Renderer {
renderElement,
TEMPLATE_COMMENT_TEXT.replace('{}', JSON.stringify(parsedBindings, null, 2)));
} else {
propertyName = propertyName.replace(/\$/g, '_');
this.setElementAttribute(renderElement, propertyName, propertyValue);
}
}
@ -262,8 +263,51 @@ function appendNodes(parent: any, nodes: any) {
}
@Injectable()
export class ServerRendererV2 implements RendererV2 {
constructor(private ngZone: NgZone, @Inject(DOCUMENT) private document: any) {}
export class ServerRendererFactoryV2 implements RendererFactoryV2 {
private rendererByCompId = new Map<string, RendererV2>();
private defaultRenderer: RendererV2;
constructor(
private ngZone: NgZone, @Inject(DOCUMENT) private document: any,
private sharedStylesHost: SharedStylesHost) {
this.defaultRenderer = new DefaultServerRendererV2(document, ngZone);
};
createRenderer(element: any, type: ComponentRenderTypeV2): RendererV2 {
if (!element || !type) {
return this.defaultRenderer;
}
switch (type.encapsulation) {
case ViewEncapsulation.Emulated: {
let renderer = this.rendererByCompId.get(type.id);
if (!renderer) {
renderer = new EmulatedEncapsulationServerRendererV2(
this.document, this.ngZone, this.sharedStylesHost, type);
this.rendererByCompId.set(type.id, renderer);
}
(<EmulatedEncapsulationServerRendererV2>renderer).applyToHost(element);
return renderer;
}
case ViewEncapsulation.Native:
throw new Error('Native encapsulation is not supported on the server!');
default: {
if (!this.rendererByCompId.has(type.id)) {
const styles = flattenStyles(type.id, type.styles, []);
this.sharedStylesHost.addStyles(styles);
this.rendererByCompId.set(type.id, this.defaultRenderer);
}
return this.defaultRenderer;
}
}
}
}
class DefaultServerRendererV2 implements RendererV2 {
constructor(private document: any, private ngZone: NgZone) {}
destroy(): void {}
destroyNode: null;
createElement(name: string, namespace?: string, debugInfo?: any): any {
if (namespace) {
@ -325,28 +369,6 @@ export class ServerRendererV2 implements RendererV2 {
}
}
setBindingDebugInfo(el: any, propertyName: string, propertyValue: string): void {
if (getDOM().isCommentNode(el)) {
const m = getDOM().getText(el).replace(/\n/g, '').match(TEMPLATE_BINDINGS_EXP);
const obj = m === null ? {} : JSON.parse(m[1]);
obj[propertyName] = propertyValue;
getDOM().setText(el, TEMPLATE_COMMENT_TEXT.replace('{}', JSON.stringify(obj, null, 2)));
} else {
this.setAttribute(el, propertyName, propertyValue);
}
}
removeBindingDebugInfo(el: any, propertyName: string): void {
if (getDOM().isCommentNode(el)) {
const m = getDOM().getText(el).replace(/\n/g, '').match(TEMPLATE_BINDINGS_EXP);
const obj = m === null ? {} : JSON.parse(m[1]);
delete obj[propertyName];
getDOM().setText(el, TEMPLATE_COMMENT_TEXT.replace('{}', JSON.stringify(obj, null, 2)));
} else {
this.removeAttribute(el, propertyName);
}
}
addClass(el: any, name: string): void { getDOM().addClass(el, name); }
removeClass(el: any, name: string): void { getDOM().removeClass(el, name); }
@ -362,7 +384,7 @@ export class ServerRendererV2 implements RendererV2 {
setProperty(el: any, name: string, value: any): void { getDOM().setProperty(el, name, value); }
setText(node: any, value: string): void { getDOM().setText(node, value); }
setValue(node: any, value: string): void { getDOM().setText(node, value); }
listen(
target: 'document'|'window'|'body'|any, eventName: string,
@ -375,3 +397,27 @@ export class ServerRendererV2 implements RendererV2 {
return this.ngZone.runOutsideAngular(() => getDOM().onAndCancel(el, eventName, outsideHandler));
}
}
class EmulatedEncapsulationServerRendererV2 extends DefaultServerRendererV2 {
private contentAttr: string;
private hostAttr: string;
constructor(
document: any, ngZone: NgZone, sharedStylesHost: SharedStylesHost,
private component: ComponentRenderTypeV2) {
super(document, ngZone);
const styles = flattenStyles(component.id, component.styles, []);
sharedStylesHost.addStyles(styles);
this.contentAttr = shimContentAttribute(component.id);
this.hostAttr = shimHostAttribute(component.id);
}
applyToHost(element: any) { super.setAttribute(element, this.hostAttr, ''); }
createElement(parent: any, name: string): Element {
const el = super.createElement(parent, name);
super.setAttribute(el, this.contentAttr, '');
return el;
}
}