Skip to content

Commit

Permalink
feat(core): add initial view engine (#14014)
Browse files Browse the repository at this point in the history
The new view engine allows our codegen to produce less code,
as it can interpret view definitions during runtime.

The view engine is not feature complete yet, but already
allows to implement a tree benchmark based on it.

Part of #14013
  • Loading branch information
tbosch authored and alxhub committed Jan 20, 2017
1 parent 9d8c467 commit 2f87eb5
Show file tree
Hide file tree
Showing 26 changed files with 3,133 additions and 11 deletions.
50 changes: 50 additions & 0 deletions modules/@angular/core/src/view/anchor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewDefinition} from './types';

export function anchorDef(
flags: NodeFlags, childCount: number, template?: ViewDefinition): NodeDef {
return {
type: NodeType.Anchor,
// will bet set by the view definition
index: undefined,
reverseChildIndex: undefined,
parent: undefined,
childFlags: undefined,
bindingIndex: undefined,
providerIndices: undefined,
// regular values
flags,
childCount,
bindings: [],
element: undefined,
provider: undefined,
text: undefined,
component: undefined, template
};
}

export function createAnchor(view: ViewData, renderHost: any, def: NodeDef): NodeData {
const parentNode = def.parent != null ? view.nodes[def.parent].renderNode : renderHost;
let renderNode: any;
if (view.renderer) {
renderNode = view.renderer.createTemplateAnchor(parentNode);
} else {
renderNode = document.createComment('');
if (parentNode) {
parentNode.appendChild(renderNode);
}
}
return {
renderNode,
provider: undefined,
embeddedViews: (def.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined,
componentView: undefined
};
}
212 changes: 212 additions & 0 deletions modules/@angular/core/src/view/element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {SecurityContext} from '../security';

import {BindingDef, BindingType, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewFlags} from './types';
import {checkAndUpdateBinding, setBindingDebugInfo} from './util';

export function elementDef(
flags: NodeFlags, childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
bindings: ([BindingType.ElementClass, string] | [BindingType.ElementStyle, string, string] | [
BindingType.ElementAttribute | BindingType.ElementProperty, string, SecurityContext
])[] = []): NodeDef {
const bindingDefs = new Array(bindings.length);
for (let i = 0; i < bindings.length; i++) {
const entry = bindings[i];
let bindingDef: BindingDef;
const bindingType = entry[0];
const name = entry[1];
let securityContext: SecurityContext;
let suffix: string;
switch (bindingType) {
case BindingType.ElementStyle:
suffix = <string>entry[2];
break;
case BindingType.ElementAttribute:
case BindingType.ElementProperty:
securityContext = <SecurityContext>entry[2];
break;
}
bindingDefs[i] = {type: bindingType, name, nonMinfiedName: name, securityContext, suffix};
}
return {
type: NodeType.Element,
// will bet set by the view definition
index: undefined,
reverseChildIndex: undefined,
parent: undefined,
childFlags: undefined,
bindingIndex: undefined,
providerIndices: undefined,
// regular values
flags,
childCount,
bindings: bindingDefs,
element: {name, attrs: fixedAttrs},
provider: undefined,
text: undefined,
component: undefined,
template: undefined
};
}

export function createElement(view: ViewData, renderHost: any, def: NodeDef): NodeData {
const parentNode = def.parent != null ? view.nodes[def.parent].renderNode : renderHost;
const elDef = def.element;
let el: any;
if (view.renderer) {
el = view.renderer.createElement(parentNode, elDef.name);
if (elDef.attrs) {
for (let attrName in elDef.attrs) {
view.renderer.setElementAttribute(el, attrName, elDef.attrs[attrName]);
}
}
} else {
el = document.createElement(elDef.name);
if (parentNode) {
parentNode.appendChild(el);
}
if (elDef.attrs) {
for (let attrName in elDef.attrs) {
el.setAttribute(attrName, elDef.attrs[attrName]);
}
}
}
return {
renderNode: el,
provider: undefined,
embeddedViews: (def.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined,
componentView: undefined
};
}

export function checkAndUpdateElementInline(
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
v7: any, v8: any, v9: any) {
// Note: fallthrough is intended!
switch (def.bindings.length) {
case 10:
checkAndUpdateElementValue(view, def, 9, v9);
case 9:
checkAndUpdateElementValue(view, def, 8, v8);
case 8:
checkAndUpdateElementValue(view, def, 7, v7);
case 7:
checkAndUpdateElementValue(view, def, 6, v6);
case 6:
checkAndUpdateElementValue(view, def, 5, v5);
case 5:
checkAndUpdateElementValue(view, def, 4, v4);
case 4:
checkAndUpdateElementValue(view, def, 3, v3);
case 3:
checkAndUpdateElementValue(view, def, 2, v2);
case 2:
checkAndUpdateElementValue(view, def, 1, v1);
case 1:
checkAndUpdateElementValue(view, def, 0, v0);
}
}

export function checkAndUpdateElementDynamic(view: ViewData, def: NodeDef, values: any[]) {
for (let i = 0; i < values.length; i++) {
checkAndUpdateElementValue(view, def, i, values[i]);
}
}

function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
if (!checkAndUpdateBinding(view, def, bindingIdx, value)) {
return;
}

const binding = def.bindings[bindingIdx];
const name = binding.name;
const renderNode = view.nodes[def.index].renderNode;
switch (binding.type) {
case BindingType.ElementAttribute:
setElementAttribute(view, binding, renderNode, name, value);
break;
case BindingType.ElementClass:
setElementClass(view, renderNode, name, value);
break;
case BindingType.ElementStyle:
setElementStyle(view, binding, renderNode, name, value);
break;
case BindingType.ElementProperty:
setElementProperty(view, binding, renderNode, name, value);
break;
}
}

function setElementAttribute(
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
const securityContext = binding.securityContext;
let renderValue = securityContext ? view.services.sanitize(securityContext, value) : value;
renderValue = renderValue != null ? renderValue.toString() : null;
if (view.renderer) {
view.renderer.setElementAttribute(renderNode, name, renderValue);
} else {
if (value != null) {
renderNode.setAttribute(name, renderValue);
} else {
renderNode.removeAttribute(name);
}
}
}

function setElementClass(view: ViewData, renderNode: any, name: string, value: boolean) {
if (view.renderer) {
view.renderer.setElementClass(renderNode, name, value);
} else {
if (value) {
renderNode.classList.add(name);
} else {
renderNode.classList.remove(name);
}
}
}

function setElementStyle(
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
let renderValue = view.services.sanitize(SecurityContext.STYLE, value);
if (renderValue != null) {
renderValue = renderValue.toString();
const unit = binding.suffix;
if (unit != null) {
renderValue = renderValue + unit;
}
} else {
renderValue = null;
}
if (view.renderer) {
view.renderer.setElementStyle(renderNode, name, renderValue);
} else {
if (renderValue != null) {
renderNode.style[name] = renderValue;
} else {
// IE requires '' instead of null
// see https://github.com/angular/angular/issues/7916
(renderNode.style as any)[name] = '';
}
}
}

function setElementProperty(
view: ViewData, binding: BindingDef, renderNode: any, name: string, value: any) {
const securityContext = binding.securityContext;
let renderValue = securityContext ? view.services.sanitize(securityContext, value) : value;
if (view.renderer) {
view.renderer.setElementProperty(renderNode, name, renderValue);
if (view.def.flags & ViewFlags.LogBindingUpdate) {
setBindingDebugInfo(view.renderer, renderNode, name, renderValue);
}
} else {
renderNode[name] = renderValue;
}
}
17 changes: 17 additions & 0 deletions modules/@angular/core/src/view/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

export {anchorDef} from './anchor';
export {elementDef} from './element';
export {providerDef} from './provider';
export {textDef} from './text';
export {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, viewDef} from './view';
export {attachEmbeddedView, detachEmbeddedView, rootRenderNodes} from './view_attach';

export * from './types';
export {DefaultServices} from './services';
Loading

0 comments on commit 2f87eb5

Please sign in to comment.