From 287aa85b33059ea539cf2712c667d6d8fc41a51f Mon Sep 17 00:00:00 2001 From: Kabir Shah Date: Sun, 28 Apr 2019 11:56:02 -0700 Subject: [PATCH] hoist static nodes --- packages/moon/dist/moon.js | 211 ++++++++++++++---- packages/moon/dist/moon.min.js | 2 +- .../src/compiler/generator/components/for.js | 33 ++- .../src/compiler/generator/components/if.js | 67 ++++-- .../moon/src/compiler/generator/generator.js | 105 +++++++-- .../src/compiler/generator/util/globals.js | 14 ++ packages/moon/src/index.js | 15 +- packages/moon/src/util/globals.js | 5 + packages/moon/src/util/util.js | 10 +- 9 files changed, 364 insertions(+), 98 deletions(-) diff --git a/packages/moon/dist/moon.js b/packages/moon/dist/moon.js index 98a22014..174a9044 100644 --- a/packages/moon/dist/moon.js +++ b/packages/moon/dist/moon.js @@ -52,12 +52,14 @@ function defaultObject(obj, fallback) { var full = {}; - for (var key in fallback) { - full[key] = fallback[key]; + for (var key in obj) { + full[key] = obj[key]; } - for (var _key in obj) { - full[_key] = obj[_key]; + for (var _key in fallback) { + if (!(_key in obj)) { + full[_key] = fallback[_key]; + } } return full; @@ -541,6 +543,11 @@ * Global variable number */ var generateVariable; + /** + * Global static variable number + */ + + var generateStatic = 0; /** * Set variable number to a new number. * @@ -550,24 +557,59 @@ function setGenerateVariable(newGenerateVariable) { generateVariable = newGenerateVariable; } + /** + * Set static variable number to a new number. + * + * @param {number} newGenerateStatic + */ + + function setGenerateStatic(newGenerateStatic) { + generateStatic = newGenerateStatic; + } /** - * Generates view function code and prelude code for an `if` element. + * Generates code for an `if`/`else-if`/`else` clause body. + * + * @param {number} variable + * @param {Object} element + * @param {Array} staticNodes + * @returns {string} clause body + */ + + function generateClause(variable, element, staticNodes) { + var generateBody = generateNode(element.children[0], element, 0, staticNodes); + var clause; + + if (generateBody.isStatic) { + // If the clause is static, then use a static node in place of it. + clause = variable + "=m[" + generateStatic + "];"; + staticNodes.push(generateBody); + setGenerateStatic(generateStatic + 1); + } else { + // If the clause is dynamic, then use the dynamic node. + clause = "" + generateBody.prelude + variable + "=" + generateBody.node + ";"; + } + + return clause; + } + /** + * Generates code for a node from an `if` element. * * @param {Object} element * @param {Object} parent * @param {number} index - * @returns {Object} View function code and prelude code + * @param {Array} staticNodes + * @returns {Object} Prelude code, view function code, and static status */ - function generateNodeIf(element, parent, index) { + + function generateNodeIf(element, parent, index, staticNodes) { var variable = "m" + generateVariable; var prelude = ""; var emptyElseClause = true; setGenerateVariable(generateVariable + 1); // Generate the initial `if` clause. - var generateIf = generateNode(element.children[0], element, 0); - prelude += "var " + variable + ";if(" + element.attributes[""] + "){" + generateIf.prelude + variable + "=" + generateIf.node + ";}"; // Search for `else-if` and `else` clauses if there are siblings. + prelude += "var " + variable + ";if(" + element.attributes[""] + "){" + generateClause(variable, element, staticNodes) + "}"; // Search for `else-if` and `else` clauses if there are siblings. if (parent !== null) { var siblings = parent.children; @@ -577,15 +619,13 @@ if (sibling.type === "else-if") { // Generate the `else-if` clause. - var generateElseIf = generateNode(sibling.children[0], sibling, 0); - prelude += "else if(" + sibling.attributes[""] + "){" + generateElseIf.prelude + variable + "=" + generateElseIf.node + ";}"; // Remove the `else-if` clause so that it isn't generated + prelude += "else if(" + sibling.attributes[""] + "){" + generateClause(variable, sibling, staticNodes) + "}"; // Remove the `else-if` clause so that it isn't generated // individually by the parent. siblings.splice(i, 1); } else if (sibling.type === "else") { // Generate the `else` clause. - var generateElse = generateNode(sibling.children[0], sibling, 0); - prelude += "else{" + generateElse.prelude + variable + "=" + generateElse.node + ";}"; // Skip generating the empty `else` clause. + prelude += "else{" + generateClause(variable, sibling, staticNodes) + "}"; // Skip generating the empty `else` clause. emptyElseClause = false; // Remove the `else` clause so that it isn't generated // individually by the parent. @@ -599,23 +639,31 @@ if (emptyElseClause) { - prelude += "else{" + variable + "={type:" + types.text + ",name:\"text\",data:{\"\":\"\",children:[]}};}"; + prelude += "else{" + variable + "=m[" + generateStatic + "];}"; + staticNodes.push({ + prelude: "", + node: "{type:" + types.text + ",name:\"text\",data:{\"\":\"\",children:[]}}", + isStatic: true + }); + setGenerateStatic(generateStatic + 1); } return { prelude: prelude, - node: variable + node: variable, + isStatic: false }; } /** - * Generates view function code and prelude code for a `for` element. + * Generates code for a node from a `for` element. * * @param {Object} element - * @returns {Object} View function code and prelude code + * @param {Array} staticNodes + * @returns {Object} Prelude code, view function code, and static status */ - function generateNodeFor(element) { + function generateNodeFor(element, staticNodes) { var variable = "m" + generateVariable; var dataLocals = element.attributes[""].split(","); var dataArray = element.attributes["of"]; @@ -624,8 +672,18 @@ var dataValue; var prelude; setGenerateVariable(generateVariable + 1); - var generateChild = generateNode(element.children[0], element, 0); - var body = "" + generateChild.prelude + variable + ".push(" + generateChild.node + ");"; + var generateChild = generateNode(element.children[0], element, 0, staticNodes); + var body; + + if (generateChild.isStatic) { + // If the body is static, then use a static node in place of it. + body = variable + ".push(m[" + generateStatic + "]);"; + staticNodes.push(generateChild); + setGenerateStatic(generateStatic + 1); + } else { + // If the body is dynamic, then use the dynamic node in the loop body. + body = "" + generateChild.prelude + variable + ".push(" + generateChild.node + ");"; + } if (dataArray === undefined) { // Generate a `for` loop over an object. The first local is the key and @@ -651,27 +709,30 @@ return { prelude: "var " + variable + "=[];" + prelude, - node: "{type:" + types.element + ",name:\"span\",data:{children:" + variable + "}}" + node: "{type:" + types.element + ",name:\"span\",data:{children:" + variable + "}}", + isStatic: false }; } /** - * Generates view function code for a Moon node from an element. + * Generates code for a node from an element. * * @param {Object} element * @param {Object} parent * @param {number} index - * @returns {Object} View function code and prelude code + * @param {Array} staticNodes + * @returns {Object} Prelude code, view function code, and static status */ - function generateNode(element, parent, index) { + function generateNode(element, parent, index, staticNodes) { var name = element.type; - var type; // Generate the correct type number for the given name. + var type; + var isStatic = true; // Generate the correct type number for the given name. if (name === "if") { - return generateNodeIf(element, parent, index); + return generateNodeIf(element, parent, index, staticNodes); } else if (name === "for") { - return generateNodeFor(element); + return generateNodeFor(element, staticNodes); } else if (name === "text") { type = types.text; } else if (name[0] === name[0].toLowerCase()) { @@ -686,20 +747,51 @@ var separator = ""; for (var attribute in attributes) { - data += separator + "\"" + attribute + "\":" + attributes[attribute]; + var attributeValue = attributes[attribute]; // Mark the current node as dynamic if any attributes are dynamic. Events + // are always treated as static. + + if (attribute[0] !== "@" && attributeValue[0] !== "\"" && attributeValue[0] !== "'") { + isStatic = false; + } + + data += separator + "\"" + attribute + "\":" + attributeValue; separator = ","; } if (attributes.children === undefined) { // Generate children if they are not in the element data. var children = element.children; + var generateChildren = []; data += separator + "children:["; separator = ""; for (var i = 0; i < children.length; i++) { - var generateChild = generateNode(children[i], element, i); - prelude += generateChild.prelude; - data += separator + generateChild.node; + var generateChild = generateNode(children[i], element, i, staticNodes); // Mark the current node as dynamic if any child is dynamic. + + if (!generateChild.isStatic) { + isStatic = false; + } + + generateChildren.push(generateChild); + } + + for (var _i = 0; _i < generateChildren.length; _i++) { + var _generateChild = generateChildren[_i]; + + if (isStatic || !_generateChild.isStatic) { + // If the whole current node is static or the current node and + // child node are dynamic, then append the child as a part of the + // node as usual. + prelude += _generateChild.prelude; + data += separator + _generateChild.node; + } else { + // If the whole current node is dynamic and the child node is + // static, then use a static node in place of the static child. + data += separator + ("m[" + generateStatic + "]"); + staticNodes.push(_generateChild); + setGenerateStatic(generateStatic + 1); + } + separator = ","; } @@ -708,7 +800,8 @@ return { prelude: prelude, - node: "{type:" + type + ",name:\"" + name + "\",data:" + data + "}}" + node: "{type:" + type + ",name:\"" + name + "\",data:" + data + "}}", + isStatic: isStatic }; } /** @@ -725,15 +818,36 @@ */ function generate(element) { - // Reset generator variable. - setGenerateVariable(0); // Generate the root node and get the prelude and node code. + // Store static nodes. + var staticNodes = []; // Reset generator variable. + + setGenerateVariable(0); // Hold a reference to the next static node. + + var staticRoot = generateStatic; // Generate the root node and get the prelude and node code. - var _generateNode = generateNode(element, null, 0), + var _generateNode = generateNode(element, null, 0, staticNodes), prelude = _generateNode.prelude, - node = _generateNode.node; // Convert the code into a usable function body. + node = _generateNode.node, + isStatic = _generateNode.isStatic; + + if (isStatic) { + // Account for a static root node. + setGenerateStatic(generateStatic + 1); + return "if(m[" + staticRoot + "]===undefined){" + prelude + "m[" + staticRoot + "]=" + node + ";}return m[" + staticRoot + "];"; + } else if (staticNodes.length === 0) { + return prelude + "return " + node + ";"; + } else { + // Generate static nodes only once at the start. + var staticCode = "if(m[" + staticRoot + "]===undefined){"; + for (var i = 0; i < staticNodes.length; i++) { + var staticNode = staticNodes[i]; + staticCode += staticNode.prelude + "m[" + (staticRoot + i) + "]=" + staticNode.node + ";"; + } - return prelude + "return " + node + ";"; + staticCode += "}"; + return "" + staticCode + prelude + "return " + node + ";"; + } } function compile(input) { @@ -754,6 +868,11 @@ */ var components = {}; + /** + * Global static component views + */ + + var m = []; /** * Set old view to a new object. * @@ -1233,19 +1352,25 @@ } if (typeof view === "string") { - view = new Function("data", compile(view)); + view = new Function("m", "data", compile(view)); } // If a `root` option is given, start the root renderer, or else just return // the component. var root = typeof options.root === "string" ? document.querySelector(options.root) : options.root; - delete options.root; + delete options.root; // Create a wrapper view function that maps data to the compiled view + // function. The compiled view function takes `m`, which holds static nodes. + // The data is also processed so that `options` acts as a default. + + var viewComponent = function viewComponent(data) { + return view(m, defaultObject(data, options)); + }; if (root === undefined) { - components[name] = function (data) { - return view(defaultObject(data, options)); - }; + // Store it as a component if no `root` is given. + components[name] = viewComponent; } else { + // Mount to the `root` element and begin execution if it is given. setViewOld({ type: types.element, name: root.tagName.toLowerCase(), @@ -1254,7 +1379,7 @@ }, node: root }); - setViewCurrent(view); + setViewCurrent(viewComponent); execute(options); } } diff --git a/packages/moon/dist/moon.min.js b/packages/moon/dist/moon.min.js index 38c213d6..7fb63adb 100644 --- a/packages/moon/dist/moon.min.js +++ b/packages/moon/dist/moon.min.js @@ -4,4 +4,4 @@ * Released under the MIT License * https://kbrsh.github.io/moon */ -!function(e,n){"undefined"==typeof module?e.Moon=n():module.exports=n()}(this,function(){"use strict";var c={element:0,text:1,component:2};var v,A=/^\s+$/,N=/<([\w\d-_]+)([^>]*?)(\/?)>/g,k=/\s*([\w\d-_:@]*)(?:=(?:("[^"]*"|'[^']*')|{([^{}]*)}))?/g,n=/"[^"]*"|'[^']*'|\d+[a-zA-Z$_]\w*|\.[a-zA-Z$_]\w*|[a-zA-Z$_]\w*:|([a-zA-Z$_]\w*)/g,t=["NaN","false","in","null","this","true","typeof","undefined","window"];function E(e){return e.replace(n,function(e,n){return void 0===n||"$"===n[0]||-1!==t.indexOf(n)?e:"data."+n})}function r(e){e=e.trim();for(var n=[],t=0;t",t+2),i=e.slice(t+2,o);0,n.push({type:"tagClose",value:i}),t=o+1;continue}if("!"===a&&"-"===e[t+2]&&"-"===e[t+3]){var d=e.indexOf("--\x3e",t+4);0,t=d+3;continue}N.lastIndex=t;var l=N.exec(e);0;for(var u=l[0],p=l[1],f=l[2],s=l[3],v={},c=void 0;null!==(c=k.exec(f));){var h=c[0],m=c[1],g=c[2],w=c[3];0===h.length?k.lastIndex+=1:(v[m]=void 0===w?void 0===g?'""':g:E(w),"@"===m[0]&&(v[m]="function($event){"+v[m]+"}"))}n.push({type:"tagOpen",value:p,attributes:v,closed:"/"===s}),t+=u.length}else if("{"===r){var y="";for(t+=1;t]*?)(\/?)>/g,k=/\s*([\w\d-_:@]*)(?:=(?:("[^"]*"|'[^']*')|{([^{}]*)}))?/g,t=/"[^"]*"|'[^']*'|\d+[a-zA-Z$_]\w*|\.[a-zA-Z$_]\w*|[a-zA-Z$_]\w*:|([a-zA-Z$_]\w*)/g,n=["NaN","false","in","null","this","true","typeof","undefined","window"];function E(e){return e.replace(t,function(e,t){return void 0===t||"$"===t[0]||-1!==n.indexOf(t)?e:"data."+t})}function r(e){e=e.trim();for(var t=[],n=0;n",n+2),i=e.slice(n+2,o);0,t.push({type:"tagClose",value:i}),n=o+1;continue}if("!"===a&&"-"===e[n+2]&&"-"===e[n+3]){var u=e.indexOf("--\x3e",n+4);0,n=u+3;continue}N.lastIndex=n;var d=N.exec(e);0;for(var l=d[0],p=d[1],s=d[2],f=d[3],c={},v=void 0;null!==(v=k.exec(s));){var h=v[0],m=v[1],g=v[2],w=v[3];0===h.length?k.lastIndex+=1:(c[m]=void 0===w?void 0===g?'""':g:E(w),"@"===m[0]&&(c[m]="function($event){"+c[m]+"}"))}t.push({type:"tagOpen",value:p,attributes:c,closed:"/"===f}),n+=l.length}else if("{"===r){var y="";for(n+=1;n view(m, defaultObject(data, options)); + if (root === undefined) { - components[name] = (data) => view(defaultObject(data, options)); + // Store it as a component if no `root` is given. + components[name] = viewComponent; } else { + // Mount to the `root` element and begin execution if it is given. setViewOld({ type: types.element, name: root.tagName.toLowerCase(), @@ -69,7 +76,7 @@ export default function Moon(options) { }, node: root }); - setViewCurrent(view); + setViewCurrent(viewComponent); execute(options); } } diff --git a/packages/moon/src/util/globals.js b/packages/moon/src/util/globals.js index a0357d5a..c0d5c7c3 100644 --- a/packages/moon/src/util/globals.js +++ b/packages/moon/src/util/globals.js @@ -13,6 +13,11 @@ export let viewOld, viewNew, viewCurrent; */ export const components = {}; +/** + * Global static component views + */ +export const m = []; + /** * Set old view to a new object. * diff --git a/packages/moon/src/util/util.js b/packages/moon/src/util/util.js index e1371fbf..fbaee3e9 100644 --- a/packages/moon/src/util/util.js +++ b/packages/moon/src/util/util.js @@ -37,13 +37,15 @@ export function defaultValue(value, fallback) { export function defaultObject(obj, fallback) { let full = {}; - for (let key in fallback) { - full[key] = fallback[key]; - } - for (let key in obj) { full[key] = obj[key]; } + for (let key in fallback) { + if (!(key in obj)) { + full[key] = fallback[key]; + } + } + return full; }