From 0cd9a4a82f026ab276360068134ab40de04b28d0 Mon Sep 17 00:00:00 2001 From: Kabir Shah Date: Tue, 30 Apr 2019 18:01:27 -0700 Subject: [PATCH] use hoist optimization in diff phase --- packages/moon/dist/moon.js | 274 ++++++++++++------------- packages/moon/dist/moon.min.js | 2 +- packages/moon/src/executor/executor.js | 265 +++++++++++------------- packages/moon/src/index.js | 13 +- 4 files changed, 259 insertions(+), 295 deletions(-) diff --git a/packages/moon/dist/moon.js b/packages/moon/dist/moon.js index b5476e3a..28b1f4ea 100644 --- a/packages/moon/dist/moon.js +++ b/packages/moon/dist/moon.js @@ -930,56 +930,43 @@ */ function executeCreate(node) { - var nodeType = node.type; - var nodeName = node.name; - var nodeData = {}; - var nodeChildren = []; - var nodeNode; + var element; + var children = []; - if (nodeType === types.text) { - // Get text content using the default data key. - var textContent = node.data[""]; // Create a text node using the text content. - - nodeNode = document.createTextNode(textContent); // Set only the default data key. - - nodeData[""] = textContent; + if (node.type === types.text) { + // Create a text node using the text content from the default key. + element = document.createTextNode(node.data[""]); } else { - var _data = node.data; // Create a DOM element. + var nodeData = node.data; // Create a DOM element. - nodeNode = document.createElement(node.name); // Set data, events, and attributes. + element = document.createElement(node.name); // Set data, events, and attributes. - for (var key in _data) { - var value = _data[key]; + for (var key in nodeData) { + var value = nodeData[key]; if (key[0] === "@") { - nodeData[key] = value; - nodeNode.addEventListener(key.slice(1), value); + element.addEventListener(key.slice(1), value); } else if (key !== "children" && value !== false) { - nodeData[key] = value; - nodeNode.setAttribute(key, value); + element.setAttribute(key, value); } } // Recursively append children. - var children = _data.children; + var nodeDataChildren = nodeData.children; - for (var i = 0; i < children.length; i++) { - var child = executeCreate(children[i]); - nodeChildren.push(child); - nodeNode.appendChild(child.node); + for (var i = 0; i < nodeDataChildren.length; i++) { + var childOld = executeCreate(nodeDataChildren[i]); + element.appendChild(childOld.element); + children.push(childOld); } - } // Set the children of the new old node. - + } // Return an old node with a reference to the immutable node and mutable + // element. This is to help performance and allow static nodes to be reused. - nodeData.children = nodeChildren; // Return a new node with copied properties of the original node. This is to - // prevent bugs from hoisting. When the new nodes are compared to the old - // ones, the old ones are modified, and the new ones are immutable. return { - type: nodeType, - name: nodeName, - data: nodeData, - node: nodeNode + element: element, + node: node, + children: children }; } /** @@ -1006,9 +993,6 @@ } else { parent.data.children[index] = node; } - } else if (parent === null) { - // If there is no parent, set the root new view to the current node. - setViewNew(node); } // Execute the views of the children. @@ -1048,79 +1032,79 @@ function executeDiff(nodesOld, nodesNew, patches) { while (true) { var nodeOld = nodesOld.pop(); - var nodeNew = nodesNew.pop(); - - if (nodeOld === nodeNew) { - // If they have the same reference (hoisted) then skip diffing. - continue; - } else if (nodeOld.name !== nodeNew.name) { - // If they have different names, then replace the old node with the - // new one. - patches.push({ - type: patchTypes.replaceElement, - nodeOld: nodeOld, - nodeNew: nodeNew, - nodeParent: null - }); - } else if (nodeOld.type === types.text) { - // If they both are text, then update the text content. - patches.push({ - type: patchTypes.updateText, - nodeOld: nodeOld, - nodeNew: nodeNew, - parent: null - }); - } else { - // If they both are normal elements, then set attributes and diff the - // children for appends, deletes, or recursive updates. - patches.push({ - type: patchTypes.setAttributes, - nodeOld: nodeOld, - nodeNew: nodeNew, - nodeParent: null - }); - var childrenOld = nodeOld.data.children; - var childrenNew = nodeNew.data.children; - var childrenOldLength = childrenOld.length; - var childrenNewLength = childrenNew.length; - - if (childrenOldLength === childrenNewLength) { - // If the children have the same length then update both as usual. - for (var i = 0; i < childrenOldLength; i++) { - nodesOld.push(childrenOld[i]); - nodesNew.push(childrenNew[i]); - } - } else if (childrenOldLength > childrenNewLength) { - // If there are more old children than new children, update the - // corresponding ones and remove the extra old children. - for (var _i = 0; _i < childrenNewLength; _i++) { - nodesOld.push(childrenOld[_i]); - nodesNew.push(childrenNew[_i]); - } - - for (var _i2 = childrenNewLength; _i2 < childrenOldLength; _i2++) { - patches.push({ - type: patchTypes.removeElement, - nodeOld: childrenOld[_i2], - nodeNew: null, - nodeParent: nodeOld - }); - } + var nodeNew = nodesNew.pop(); // If they have the same reference (hoisted) then skip diffing. + + if (nodeOld.node !== nodeNew) { + if (nodeOld.node.name !== nodeNew.name) { + // If they have different names, then replace the old node with the + // new one. + patches.push({ + type: patchTypes.replaceElement, + nodeOld: nodeOld, + nodeNew: nodeNew, + nodeParent: null + }); + } else if (nodeOld.node.type === types.text) { + // If they both are text, then update the text content. + patches.push({ + type: patchTypes.updateText, + nodeOld: nodeOld, + nodeNew: nodeNew, + nodeParent: null + }); } else { - // If there are more new children than old children, update the - // corresponding ones and append the extra new children. - for (var _i3 = 0; _i3 < childrenOldLength; _i3++) { - nodesOld.push(childrenOld[_i3]); - nodesNew.push(childrenNew[_i3]); - } + // If they both are normal elements, then set attributes and diff + // the children for appends, deletes, or recursive updates. + patches.push({ + type: patchTypes.setAttributes, + nodeOld: nodeOld, + nodeNew: nodeNew, + nodeParent: null + }); + var childrenOld = nodeOld.children; + var childrenNew = nodeNew.data.children; + var childrenOldLength = childrenOld.length; + var childrenNewLength = childrenNew.length; + + if (childrenOldLength === childrenNewLength) { + // If the children have the same length then update both as + // usual. + for (var i = 0; i < childrenOldLength; i++) { + nodesOld.push(childrenOld[i]); + nodesNew.push(childrenNew[i]); + } + } else if (childrenOldLength > childrenNewLength) { + // If there are more old children than new children, update the + // corresponding ones and remove the extra old children. + for (var _i = 0; _i < childrenNewLength; _i++) { + nodesOld.push(childrenOld[_i]); + nodesNew.push(childrenNew[_i]); + } + + for (var _i2 = childrenNewLength; _i2 < childrenOldLength; _i2++) { + patches.push({ + type: patchTypes.removeElement, + nodeOld: childrenOld[_i2], + nodeNew: null, + nodeParent: nodeOld + }); + } + } else { + // If there are more new children than old children, update the + // corresponding ones and append the extra new children. + for (var _i3 = 0; _i3 < childrenOldLength; _i3++) { + nodesOld.push(childrenOld[_i3]); + nodesNew.push(childrenNew[_i3]); + } - for (var _i4 = childrenOldLength; _i4 < childrenNewLength; _i4++) { - patches.push({ - type: patchTypes.appendElement, - nodeOld: null, - nodeNew: childrenNew[_i4], - nodeParent: nodeOld - }); + for (var _i4 = childrenOldLength; _i4 < childrenNewLength; _i4++) { + patches.push({ + type: patchTypes.appendElement, + nodeOld: null, + nodeNew: childrenNew[_i4], + nodeParent: nodeOld + }); + } } } } @@ -1156,9 +1140,9 @@ { // Update text of a node with new text. var nodeOld = patch.nodeOld; - var nodeNewText = patch.nodeNew.data[""]; - nodeOld.data[""] = nodeNewText; - nodeOld.node.textContent = nodeNewText; + var nodeNew = patch.nodeNew; + nodeOld.element.textContent = nodeNew.data[""]; + nodeOld.node = nodeNew; break; } @@ -1166,37 +1150,35 @@ { // Set attributes of a node with new data. var _nodeOld = patch.nodeOld; - var nodeOldData = _nodeOld.data; - var nodeOldNode = _nodeOld.node; - var nodeNewData = patch.nodeNew.data; // Mutate the old node with the new node's data and set attributes - // and events on the DOM node. + var nodeOldElement = _nodeOld.element; + var _nodeNew = patch.nodeNew; + var nodeNewData = _nodeNew.data; // Set attributes on the DOM element. for (var key in nodeNewData) { - var value = nodeNewData[key]; + var value = nodeNewData[key]; // Ignore updating events and children. if (key[0] !== "@" && key !== "children") { - nodeOldData[key] = value; - + // Remove the attribute if the value is false, and update it + // otherwise. if (value === false) { - nodeOldNode.removeAttribute(key); + nodeOldElement.removeAttribute(key); } else { - nodeOldNode.setAttribute(key, value); + nodeOldElement.setAttribute(key, value); } } } + _nodeOld.node = _nodeNew; break; } case patchTypes.appendElement: { - // Append a node. Creates a new old node because the old node must - // be mutable while the new nodes are mutable. - var nodeNew = patch.nodeNew; + // Append a node to the parent. var nodeParent = patch.nodeParent; - var nodeOldNew = executeCreate(nodeNew); - nodeParent.data.children.push(nodeOldNew); - nodeParent.node.appendChild(nodeOldNew.node); + var nodeOldNew = executeCreate(patch.nodeNew); + nodeParent.element.appendChild(nodeOldNew.element); + nodeParent.children.push(nodeOldNew); break; } @@ -1209,9 +1191,7 @@ // efficient than removing at a specific index, especially because // they are equivalent in this case. - _nodeParent.data.children.pop(); - - _nodeParent.node.removeChild(patch.nodeOld.node); + _nodeParent.element.removeChild(_nodeParent.children.pop().element); break; } @@ -1220,21 +1200,18 @@ { // Replaces an old node with a new node. var _nodeOld2 = patch.nodeOld; - var _nodeNew = patch.nodeNew; - - var _nodeOldNew = executeCreate(_nodeNew); + var _nodeOldElement = _nodeOld2.element; + var _nodeNew2 = patch.nodeNew; - var _nodeOldNode = _nodeOld2.node; // Mutate the old node with the copied data from creating the new - // old node, replacing the old DOM node reference. + var _nodeOldNew = executeCreate(_nodeNew2); - _nodeOld2.type = _nodeOldNew.type; - _nodeOld2.name = _nodeOldNew.name; - _nodeOld2.data = _nodeOldNew.data; - _nodeOld2.node = _nodeOldNew.node; // Replace the old node using the reference created before updating - // the old node. + var nodeOldNewElement = _nodeOldNew.element; - _nodeOldNode.parentNode.replaceChild(_nodeOld2.node, _nodeOldNode); + _nodeOldElement.parentNode.replaceChild(nodeOldNewElement, _nodeOldElement); + _nodeOld2.element = nodeOldNewElement; + _nodeOld2.node = _nodeOldNew.node; + _nodeOld2.children = _nodeOldNew.children; break; } } @@ -1270,7 +1247,9 @@ } // Begin executing the view. - executeView([viewCurrent(data)], [null], [0]); + var viewNew = viewCurrent(data); + setViewNew(viewNew); + executeView([viewNew], [null], [0]); } /** * Executor @@ -1372,12 +1351,15 @@ } else { // Mount to the `root` element and begin execution if it is given. setViewOld({ - type: types.element, - name: root.tagName.toLowerCase(), - data: { - children: [] + element: root, + node: { + type: types.element, + name: root.tagName.toLowerCase(), + data: { + children: [] + } }, - node: root + children: [] }); setViewCurrent(viewComponent); execute(options); diff --git a/packages/moon/dist/moon.min.js b/packages/moon/dist/moon.min.js index b5cefd23..447fd305 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,t){"undefined"==typeof module?e.Moon=t():module.exports=t()}(this,function(){"use strict";var y={element:0,text:1,component:2};var b,A=/^\s+$/,N=/<([\w\d-_]+)([^>]*?)(\/?)>/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]*?)(\/?)>/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),o=e.slice(n+2,i);0,t.push({type:"tagClose",value:o}),n=i+1;continue}if("!"===a&&"-"===e[n+2]&&"-"===e[n+3]){var l=e.indexOf("--\x3e",n+4);0,n=l+3;continue}N.lastIndex=n;var u=N.exec(e);0;for(var d=u[0],s=u[1],f=u[2],p=u[3],c={},v=void 0;null!==(v=k.exec(f));){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:s,attributes:c,closed:"/"===p}),n+=d.length}else if("{"===r){var b="";for(n+=1;n childrenNewLength) { - // If there are more old children than new children, update the - // corresponding ones and remove the extra old children. - for (let i = 0; i < childrenNewLength; i++) { - nodesOld.push(childrenOld[i]); - nodesNew.push(childrenNew[i]); - } - - for (let i = childrenNewLength; i < childrenOldLength; i++) { - patches.push({ - type: patchTypes.removeElement, - nodeOld: childrenOld[i], - nodeNew: null, - nodeParent: nodeOld - }); - } + // If they have the same reference (hoisted) then skip diffing. + if (nodeOld.node !== nodeNew) { + if (nodeOld.node.name !== nodeNew.name) { + // If they have different names, then replace the old node with the + // new one. + patches.push({ + type: patchTypes.replaceElement, + nodeOld, + nodeNew, + nodeParent: null + }); + } else if (nodeOld.node.type === types.text) { + // If they both are text, then update the text content. + patches.push({ + type: patchTypes.updateText, + nodeOld, + nodeNew, + nodeParent: null + }); } else { - // If there are more new children than old children, update the - // corresponding ones and append the extra new children. - for (let i = 0; i < childrenOldLength; i++) { - nodesOld.push(childrenOld[i]); - nodesNew.push(childrenNew[i]); - } + // If they both are normal elements, then set attributes and diff + // the children for appends, deletes, or recursive updates. + patches.push({ + type: patchTypes.setAttributes, + nodeOld, + nodeNew, + nodeParent: null + }); + + const childrenOld = nodeOld.children; + const childrenNew = nodeNew.data.children; + + const childrenOldLength = childrenOld.length; + const childrenNewLength = childrenNew.length; + + if (childrenOldLength === childrenNewLength) { + // If the children have the same length then update both as + // usual. + for (let i = 0; i < childrenOldLength; i++) { + nodesOld.push(childrenOld[i]); + nodesNew.push(childrenNew[i]); + } + } else if (childrenOldLength > childrenNewLength) { + // If there are more old children than new children, update the + // corresponding ones and remove the extra old children. + for (let i = 0; i < childrenNewLength; i++) { + nodesOld.push(childrenOld[i]); + nodesNew.push(childrenNew[i]); + } + + for (let i = childrenNewLength; i < childrenOldLength; i++) { + patches.push({ + type: patchTypes.removeElement, + nodeOld: childrenOld[i], + nodeNew: null, + nodeParent: nodeOld + }); + } + } else { + // If there are more new children than old children, update the + // corresponding ones and append the extra new children. + for (let i = 0; i < childrenOldLength; i++) { + nodesOld.push(childrenOld[i]); + nodesNew.push(childrenNew[i]); + } - for (let i = childrenOldLength; i < childrenNewLength; i++) { - patches.push({ - type: patchTypes.appendElement, - nodeOld: null, - nodeNew: childrenNew[i], - nodeParent: nodeOld - }); + for (let i = childrenOldLength; i < childrenNewLength; i++) { + patches.push({ + type: patchTypes.appendElement, + nodeOld: null, + nodeNew: childrenNew[i], + nodeParent: nodeOld + }); + } } } } @@ -267,10 +248,10 @@ function executePatch(patches) { case patchTypes.updateText: { // Update text of a node with new text. const nodeOld = patch.nodeOld; - const nodeNewText = patch.nodeNew.data[""]; + const nodeNew = patch.nodeNew; - nodeOld.data[""] = nodeNewText; - nodeOld.node.textContent = nodeNewText; + nodeOld.element.textContent = nodeNew.data[""]; + nodeOld.node = nodeNew; break; } @@ -278,38 +259,38 @@ function executePatch(patches) { case patchTypes.setAttributes: { // Set attributes of a node with new data. const nodeOld = patch.nodeOld; - const nodeOldData = nodeOld.data; - const nodeOldNode = nodeOld.node; - const nodeNewData = patch.nodeNew.data; + const nodeOldElement = nodeOld.element; + const nodeNew = patch.nodeNew; + const nodeNewData = nodeNew.data; - // Mutate the old node with the new node's data and set attributes - // and events on the DOM node. + // Set attributes on the DOM element. for (let key in nodeNewData) { const value = nodeNewData[key]; + // Ignore updating events and children. if (key[0] !== "@" && key !== "children") { - nodeOldData[key] = value; - + // Remove the attribute if the value is false, and update it + // otherwise. if (value === false) { - nodeOldNode.removeAttribute(key); + nodeOldElement.removeAttribute(key); } else { - nodeOldNode.setAttribute(key, value); + nodeOldElement.setAttribute(key, value); } } } + nodeOld.node = nodeNew; + break; } case patchTypes.appendElement: { - // Append a node. Creates a new old node because the old node must - // be mutable while the new nodes are mutable. - const nodeNew = patch.nodeNew; + // Append a node to the parent. const nodeParent = patch.nodeParent; - const nodeOldNew = executeCreate(nodeNew); + const nodeOldNew = executeCreate(patch.nodeNew); - nodeParent.data.children.push(nodeOldNew); - nodeParent.node.appendChild(nodeOldNew.node); + nodeParent.element.appendChild(nodeOldNew.element); + nodeParent.children.push(nodeOldNew); break; } @@ -323,8 +304,7 @@ function executePatch(patches) { // are extra old children, and popping nodes off of the end is more // efficient than removing at a specific index, especially because // they are equivalent in this case. - nodeParent.data.children.pop(); - nodeParent.node.removeChild(patch.nodeOld.node); + nodeParent.element.removeChild(nodeParent.children.pop().element); break; } @@ -332,20 +312,16 @@ function executePatch(patches) { case patchTypes.replaceElement: { // Replaces an old node with a new node. const nodeOld = patch.nodeOld; + const nodeOldElement = nodeOld.element; const nodeNew = patch.nodeNew; const nodeOldNew = executeCreate(nodeNew); - const nodeOldNode = nodeOld.node; + const nodeOldNewElement = nodeOldNew.element; - // Mutate the old node with the copied data from creating the new - // old node, replacing the old DOM node reference. - nodeOld.type = nodeOldNew.type; - nodeOld.name = nodeOldNew.name; - nodeOld.data = nodeOldNew.data; - nodeOld.node = nodeOldNew.node; + nodeOldElement.parentNode.replaceChild(nodeOldNewElement, nodeOldElement); - // Replace the old node using the reference created before updating - // the old node. - nodeOldNode.parentNode.replaceChild(nodeOld.node, nodeOldNode); + nodeOld.element = nodeOldNewElement; + nodeOld.node = nodeOldNew.node; + nodeOld.children = nodeOldNew.children; break; } @@ -384,7 +360,10 @@ function executeNext() { } // Begin executing the view. - executeView([viewCurrent(data)], [null], [0]); + const viewNew = viewCurrent(data); + + setViewNew(viewNew); + executeView([viewNew], [null], [0]); } /** diff --git a/packages/moon/src/index.js b/packages/moon/src/index.js index f302d165..fc8d1b6f 100644 --- a/packages/moon/src/index.js +++ b/packages/moon/src/index.js @@ -69,12 +69,15 @@ export default function Moon(options) { } else { // Mount to the `root` element and begin execution if it is given. setViewOld({ - type: types.element, - name: root.tagName.toLowerCase(), - data: { - children: [] + element: root, + node: { + type: types.element, + name: root.tagName.toLowerCase(), + data: { + children: [] + } }, - node: root + children: [] }); setViewCurrent(viewComponent); execute(options);