ivi
Version:
Lightweight Embeddable Web UI Library.
437 lines • 18.1 kB
JavaScript
import { NODE_TYPE_ELEMENT, NODE_TYPE_EXPR, NODE_TYPE_TEXT, TEMPLATE_TYPE_SVG, PROPERTY_TYPE_ATTRIBUTE, PROPERTY_TYPE_STYLE, PROPERTY_TYPE_VALUE, PROPERTY_TYPE_DOMVALUE, PROPERTY_TYPE_EVENT, PROPERTY_TYPE_DIRECTIVE, } from "./ir.js";
import { VOID_ELEMENTS, createSNode, } from "./shared.js";
export var TemplateNodeType;
(function (TemplateNodeType) {
TemplateNodeType[TemplateNodeType["Block"] = 0] = "Block";
TemplateNodeType[TemplateNodeType["Text"] = 1] = "Text";
TemplateNodeType[TemplateNodeType["Expr"] = 2] = "Expr";
})(TemplateNodeType || (TemplateNodeType = {}));
export const compileTemplate = (tpl) => {
return {
roots: tpl.children.map((node) => compileTemplateNode(node, tpl.type)),
};
};
export class TemplateCompilerError extends Error {
constructor(msg) {
super(msg);
}
}
const compileTemplateNode = (node, type) => {
switch (node.type) {
case NODE_TYPE_ELEMENT:
return compileRootElement(node, type);
case NODE_TYPE_EXPR:
return {
type: TemplateNodeType.Expr,
value: node.value,
};
case NODE_TYPE_TEXT:
return {
type: TemplateNodeType.Text,
value: node.value,
};
}
};
const compileRootElement = (element, type) => {
// Emits a static template. It can be either a string if it is an element
// without any static parts, or an array of strings and expression indices.
const template = emitStaticTemplate(element);
// Creates a new tree with additional data for compilation.
const sRoot = createSNode(element, 0);
// Assigns state slots in DFS LTR order.
assignStateSlots(sRoot);
// Creates dynamic expressions map that stores expr indices that will be
// used in the current template block.
const exprMap = createExprMap(element);
if (exprMap.size > 64) {
throw Error("Exceeded maximum number (64) of expressions per template block.");
}
// Emits state OpCodes and traverses tree in DFS LTR order.
const state = emitStateOpCodes(sRoot);
// Emits props OpCodes and traverses tree in DFS LTR order.
const data = [];
const props = emitPropsOpCodes(sRoot, data, exprMap);
// Emits child OpCodes and traverses tree in DFS RTL order.
const child = emitChildOpCodes(sRoot, exprMap);
const stateSlots = countStateSlots(state);
if (stateSlots > 64) {
throw Error("Exceeded maximum number (64) of state slots per template block.");
}
const childSlots = countChildSlots(child);
if (childSlots > 64) {
throw Error("Exceeded maximum number (64) of child slots per template block.");
}
return {
type: TemplateNodeType.Block,
flags: ((stateSlots) |
(childSlots << 6 /* TemplateFlags.ChildrenSizeShift */) |
(type === TEMPLATE_TYPE_SVG ? 4096 /* TemplateFlags.Svg */ : 0)),
template,
props,
child,
state,
data,
exprs: Array.from(exprMap.keys()),
};
};
const countStateSlots = (stateOpCodes) => {
let count = 1; // Root node always occupy 1 slot.
for (let i = 0; i < stateOpCodes.length; i++) {
const op = stateOpCodes[i];
if ((op & 1 /* StateOpCode.Save */) ||
((op & 2 /* StateOpCode.EnterOrRemove */) && (op >> 2 /* StateOpCode.OffsetShift */) === 0)) {
count++;
}
}
return count;
};
const countChildSlots = (childOpCodes) => {
let count = 0;
for (let i = 0; i < childOpCodes.length; i++) {
if ((childOpCodes[i] & 3 /* ChildOpCode.Type */) === 0 /* ChildOpCode.Child */) {
count++;
}
}
return count;
};
const createExprMap = (root) => {
const exprMap = new Map();
_createExprMap(exprMap, root);
return exprMap;
};
const _createExprMap = (exprMap, node) => {
const { properties, children } = node;
for (let i = 0; i < properties.length; i++) {
const prop = properties[i];
const value = prop.value;
if (typeof value === "number") {
exprMap.set(value, exprMap.size);
}
}
for (let i = 0; i < children.length; i++) {
const child = children[i];
const type = child.type;
if (type === NODE_TYPE_ELEMENT) {
_createExprMap(exprMap, child);
}
else if (type === NODE_TYPE_EXPR) {
exprMap.set(child.value, exprMap.size);
}
}
};
const emitStaticTemplate = (root) => {
const staticTemplate = [];
_emitStaticTemplate(staticTemplate, root);
if (staticTemplate.length <= 3 && staticTemplate[1] === ">") {
return root.tag;
}
return staticTemplate;
};
const _emitStaticTemplate = (staticTemplate, node) => {
const { tag, properties, children } = node;
let style = "";
staticTemplate.push(`<${tag}`);
for (let i = 0; i < properties.length; i++) {
const prop = properties[i];
const { type, key, value } = prop;
if (type === PROPERTY_TYPE_ATTRIBUTE) {
if (key === "style") {
if (typeof value === "string") {
if (style !== "") {
style += `;${value}`;
}
else {
style = value;
}
}
}
else {
if (value === true) {
staticTemplate.push(` ${key}`);
}
else if (typeof value === "string") {
staticTemplate.push(` ${key}="${value}"`);
}
}
}
else if (type === PROPERTY_TYPE_STYLE) {
if (typeof value === "string") {
if (style !== "") {
style += `;${key}:${value}`;
}
else {
style = `${key}:${value}`;
}
}
}
}
if (style !== "") {
staticTemplate.push(` style="${style}"`);
}
staticTemplate.push(`>`);
if (VOID_ELEMENTS.test(tag)) {
if (children.length > 0) {
throw new TemplateCompilerError(`Invalid template, void element '${tag}' shouldn't have any children.`);
}
return;
}
let state = 0;
for (let i = 0; i < children.length; i++) {
const child = children[i];
switch (child.type) {
case NODE_TYPE_ELEMENT:
_emitStaticTemplate(staticTemplate, child);
state = 0;
break;
case NODE_TYPE_TEXT:
if ((state & 3) === 3) {
staticTemplate.push("<!>");
}
state = 1;
staticTemplate.push(child.value);
break;
case NODE_TYPE_EXPR:
state |= 2;
break;
}
}
staticTemplate.push(`</${tag}>`);
};
const getDataIndex = (data, dataMap, key) => {
let index = dataMap.get(key);
if (index === void 0) {
index = data.length;
data.push(key);
dataMap.set(key, index);
}
return index;
};
const emitPropsOpCodes = (root, data, exprMap) => {
const dataMap = new Map();
const opCodes = [];
_emitPropsOpCodes(opCodes, root, true, data, dataMap, exprMap);
return opCodes;
};
const _emitPropsOpCodes = (opCodes, node, isRoot, data, dataMap, exprMap) => {
const iNode = node.node;
if (iNode.type === NODE_TYPE_ELEMENT) {
if (node.propsExprs > 0) {
if (isRoot === false) {
opCodes.push(0 /* PropOpCode.SetNode */ | (node.stateIndex << 9 /* PropOpCode.DataShift */));
}
const properties = iNode.properties;
for (let i = 0; i < properties.length; i++) {
const prop = properties[i];
const value = prop.value;
if (typeof value === "number") {
const { type, key } = prop;
switch (type) {
case PROPERTY_TYPE_ATTRIBUTE:
const exprIndex = exprMap.get(value);
if (exprIndex !== -1) {
if (key === "class") {
opCodes.push(1 /* PropOpCode.Common */ |
(0 /* CommonPropType.ClassName */ << 9 /* PropOpCode.DataShift */) |
(exprIndex << 3 /* PropOpCode.InputShift */));
}
else {
opCodes.push(2 /* PropOpCode.Attribute */ |
(getDataIndex(data, dataMap, key) << 9 /* PropOpCode.DataShift */) |
(exprIndex << 3 /* PropOpCode.InputShift */));
}
}
break;
case PROPERTY_TYPE_VALUE:
if (key === "textContent") {
opCodes.push(1 /* PropOpCode.Common */ |
(1 /* CommonPropType.TextContent */ << 9 /* PropOpCode.DataShift */) |
(exprMap.get(value) << 3 /* PropOpCode.InputShift */));
}
else if (key === "innerHTML") {
opCodes.push(1 /* PropOpCode.Common */ |
(2 /* CommonPropType.InnerHTML */ << 9 /* PropOpCode.DataShift */) |
(exprMap.get(value) << 3 /* PropOpCode.InputShift */));
}
else {
opCodes.push(3 /* PropOpCode.Property */ |
(getDataIndex(data, dataMap, key) << 9 /* PropOpCode.DataShift */) |
(exprMap.get(value) << 3 /* PropOpCode.InputShift */));
}
break;
case PROPERTY_TYPE_DOMVALUE:
if (key === "textContent") {
opCodes.push(1 /* PropOpCode.Common */ |
(1 /* CommonPropType.TextContent */ << 9 /* PropOpCode.DataShift */) |
(exprMap.get(value) << 3 /* PropOpCode.InputShift */));
}
else if (key === "innerHTML") {
opCodes.push(1 /* PropOpCode.Common */ |
(2 /* CommonPropType.InnerHTML */ << 9 /* PropOpCode.DataShift */) |
(exprMap.get(value) << 3 /* PropOpCode.InputShift */));
}
else {
opCodes.push(4 /* PropOpCode.DiffDOMProperty */ |
(getDataIndex(data, dataMap, key) << 9 /* PropOpCode.DataShift */) |
(exprMap.get(value) << 3 /* PropOpCode.InputShift */));
}
break;
case PROPERTY_TYPE_STYLE:
opCodes.push(5 /* PropOpCode.Style */ |
(getDataIndex(data, dataMap, key) << 9 /* PropOpCode.DataShift */) |
(exprMap.get(value) << 3 /* PropOpCode.InputShift */));
break;
case PROPERTY_TYPE_EVENT:
opCodes.push(6 /* PropOpCode.Event */ |
(getDataIndex(data, dataMap, key) << 9 /* PropOpCode.DataShift */) |
(exprMap.get(value) << 3 /* PropOpCode.InputShift */));
break;
case PROPERTY_TYPE_DIRECTIVE:
opCodes.push(7 /* PropOpCode.Directive */ |
(exprMap.get(value) << 3 /* PropOpCode.InputShift */));
break;
}
}
}
}
const children = node.children;
if (children !== null) {
for (let i = 0; i < children.length; i++) {
_emitPropsOpCodes(opCodes, children[i], false, data, dataMap, exprMap);
}
}
}
};
const emitStateOpCodes = (root) => {
const opCodes = [];
_emitStateOpCodes(opCodes, root);
return opCodes;
};
const _emitStateOpCodes = (opCodes, node) => {
const children = node.children;
if (children !== null) {
let state = 0;
outer: for (let i = 0; i < children.length; i++) {
const child = children[i];
switch (child.node.type) {
case NODE_TYPE_ELEMENT: {
let opCode = 0;
if (state & 2 /* VisitState.PrevExpr */ ||
child.childrenExprs > 0 ||
child.propsExprs > 0) {
opCode = 1 /* StateOpCode.Save */;
}
const currentOpCodeIndex = opCodes.length;
opCodes.push(opCode);
if (child.flags & 1 /* SNodeFlags.HasExpressions */) {
_emitStateOpCodes(opCodes, child);
const childrenOffset = opCodes.length - (currentOpCodeIndex + 1);
if (childrenOffset > 0) {
opCode |= 2 /* StateOpCode.EnterOrRemove */ | (childrenOffset << 2 /* StateOpCode.OffsetShift */);
opCodes[currentOpCodeIndex] = opCode;
}
}
if ((child.flags & (2 /* SNodeFlags.HasNextExpressions */ | 4 /* SNodeFlags.HasNextDOMNode */)) !==
(2 /* SNodeFlags.HasNextExpressions */ | 4 /* SNodeFlags.HasNextDOMNode */)) {
if (opCode === 0) {
opCodes.pop();
}
break outer;
}
state = 0;
break;
}
case NODE_TYPE_TEXT: {
if ((state & (1 /* VisitState.PrevText */ | 2 /* VisitState.PrevExpr */)) === (1 /* VisitState.PrevText */ | 2 /* VisitState.PrevExpr */)) {
opCodes.push(2 /* StateOpCode.EnterOrRemove */);
}
else if (state & 2 /* VisitState.PrevExpr */) {
opCodes.push(1 /* StateOpCode.Save */);
}
else {
if ((child.flags & (2 /* SNodeFlags.HasNextExpressions */ | 4 /* SNodeFlags.HasNextDOMNode */)) !==
(2 /* SNodeFlags.HasNextExpressions */ | 4 /* SNodeFlags.HasNextDOMNode */)) {
break outer;
}
else {
opCodes.push(0);
}
}
state = 1;
break;
}
case NODE_TYPE_EXPR:
state |= 2 /* VisitState.PrevExpr */;
break;
}
}
}
};
const emitChildOpCodes = (root, exprMap) => {
const opCodes = [];
_emitChildOpCodes(opCodes, root, true, exprMap);
return opCodes;
};
const _emitChildOpCodes = (opCodes, node, isRoot, exprMap) => {
if (node.childrenExprs > 0) {
const children = node.children;
if (children !== null) {
// Do not emit SetParent for root nodes
if (isRoot === false) {
opCodes.push(3 /* ChildOpCode.SetParent */ | (node.stateIndex << 2 /* ChildOpCode.ValueShift */));
}
let prev;
let i = children.length;
while (--i >= 0) {
const child = children[i];
if (child.node.type === NODE_TYPE_EXPR) {
if (prev !== void 0 && prev.node.type !== NODE_TYPE_EXPR) {
opCodes.push(1 /* ChildOpCode.SetNext */ | (prev.stateIndex << 2 /* ChildOpCode.ValueShift */));
}
opCodes.push(0 /* ChildOpCode.Child */ | (exprMap.get(child.node.value) << 2 /* ChildOpCode.ValueShift */));
}
prev = child;
}
}
}
const children = node.children;
if (children !== null) {
let i = children.length;
while (--i >= 0) {
const child = children[i];
if (child.node.type === NODE_TYPE_ELEMENT) {
_emitChildOpCodes(opCodes, child, false, exprMap);
}
}
}
};
const assignStateSlots = (root) => (_assignStateSlots(root, 1));
const _assignStateSlots = (node, stateIndex) => {
const children = node.children;
if (children !== null) {
let prevExpr = false;
for (let i = 0; i < children.length; i++) {
const child = children[i];
switch (child.node.type) {
case NODE_TYPE_ELEMENT:
if (prevExpr) {
child.stateIndex = stateIndex++;
prevExpr = false;
}
else if (child.propsExprs > 0 || child.childrenExprs > 0) {
child.stateIndex = stateIndex++;
}
stateIndex = _assignStateSlots(child, stateIndex);
case NODE_TYPE_TEXT:
if (prevExpr) {
child.stateIndex = stateIndex++;
prevExpr = false;
}
break;
case NODE_TYPE_EXPR:
prevExpr = true;
}
}
}
return stateIndex;
};
//# sourceMappingURL=compiler.js.map