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:
@ -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;
|
||||
|
@ -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},
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user