UNPKG

ivi

Version:

Lightweight Embeddable Web UI Library.

437 lines 18.1 kB
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