diff --git a/dist/moon.js b/dist/moon.js index 95ce09e0..a7957769 100644 --- a/dist/moon.js +++ b/dist/moon.js @@ -43,7 +43,7 @@ } }; - var whitespaceRE = /\s+/; + var whitespaceRE = /^\s+$/; var parseAttributes = function (index, input, length, dependencies, attributes) { while (index < length) { @@ -118,7 +118,6 @@ var parseOpeningTag = function (index, input, length, stack, dependencies) { var element = { - index: stack[0].nextIndex++, type: "", attributes: [], children: [] @@ -129,6 +128,11 @@ if (char === ">") { var attributes = element.attributes; + + if (element.type[0] !== "#") { + element.index = stack[0].nextIndex++; + } + stack.push(element); for (var i = 0; i < attributes.length;) { @@ -136,7 +140,6 @@ if (attribute.key[0] === "#") { element = { - index: stack[0].nextIndex++, type: attribute.key, attributes: [{ key: "", @@ -157,6 +160,10 @@ index += 1; break; } else if (char === "/" && input[index + 1] === ">") { + if (element.type[0] !== "#") { + element.index = stack[0].nextIndex++; + } + stack[stack.length - 1].children.push(element); index += 2; @@ -240,7 +247,6 @@ if (!whitespaceRE.test(content)) { stack[stack.length - 1].children.push({ - index: stack[0].nextIndex++, type: "#text", attributes: [{ key: "", @@ -270,7 +276,6 @@ } stack[stack.length - 1].children.push({ - index: stack[0].nextIndex++, type: "#text", attributes: [{ key: "", @@ -324,7 +329,7 @@ var result = ""; for (var i = 0; i < arr.length; i++) { - result += fn(arr[i], i); + result += fn(arr[i]); } return result; @@ -354,71 +359,111 @@ var insertBefore = function (element, reference, parent) { return ("m.ib(" + (getElement(element)) + "," + (getElement(reference)) + "," + (getElement(parent)) + ");"); }; - var generateCreate = function (element, index, parent, root, insert) { - var createCode, mountCode = "", mountElement = element.index; + var generateMount = function (element, parent, insert) { return insert === undefined ? appendChild(element, parent) : insertBefore(element, insert, parent); }; + var generateCreate = function (element, parent, root, insert) { switch (element.type) { - case "#if": + case "#if": { var siblings = parent.children; - var ifReference = root.nextIndex++; - var ifCreates = ""; - var ifBranches = ""; - - for (var i = index; i < siblings.length;) { - var sibling = siblings[i]; - var keyword = (void 0); - - if (sibling.type === "#if") { - keyword = "if(" + (attributeValue(sibling.attributes[0])) + ")"; - } else if (sibling.type === "#elseif") { - keyword = "else if(" + (attributeValue(sibling.attributes[0])) + ")"; - } else if (sibling.type === "#else") { - keyword = "else"; - } else { - break; - } + var nextSiblingIndex = siblings.indexOf(element) + 1; + var nextSibling = siblings[nextSiblingIndex]; - ifCreates += setElement(sibling.index, ("function(){" + (mapReduce(sibling.children, function (child, index) { return generateCreate(child, index, parent, root, ifReference); })) + "};")); - ifBranches += keyword + "{" + (getElement(sibling.index)) + "();}"; - siblings.splice(i, 1); + element.ifReference = root.nextIndex++; + element.ifState = root.nextIndex++; + element.ifCreate = generateCreate(element.children[0], parent, root, element.ifReference); + + if (nextSibling !== undefined && nextSibling.type === "#else") { + nextSibling.ifState = element.ifState; + nextSibling.ifReference = element.ifReference; + } else { + siblings.splice(nextSiblingIndex, 0, { + type: "#else", + attributes: [], + children: [{ + type: "#comment", + attributes: [], + children: [] + }], + ifState: element.ifState, + ifReference: element.ifReference + }); } - createCode = setElement(ifReference, createComment()); - mountCode = ifCreates + ifBranches; - mountElement = ifReference; - break; - case "#text": - createCode = setElement(mountElement, createTextNode(attributeValue(element.attributes[0]))); - break; - default: - createCode = setElement(mountElement, createElement(element.type)) + mapReduce(element.attributes, function (attribute) { return attribute.key[0] === "@" ? addEventListener(mountElement, attribute) : setAttribute(mountElement, attribute); }) + mapReduce(element.children, function (child, index) { return generateCreate(child, index, element, root); }); + return setElement(element.ifReference, createComment()) + generateMount(element.ifReference, parent.index, insert); + } + case "#else": { + element.ifCreate = generateCreate(element.children[0], parent, root, element.ifReference); + return ""; + } + case "#comment": { + element.commentElement = root.nextIndex++; + return setElement(element.commentElement, createComment()); + } + case "#text": { + var textAttribute = element.attributes[0]; + element.textElement = root.nextIndex++; + return setElement(element.textElement, createTextNode(textAttribute.dynamic ? "\"\"" : attributeValue(textAttribute))) + generateMount(element.textElement, parent.index, insert); + } + default: { + return setElement(element.index, createElement(element.type)) + mapReduce(element.attributes, function (attribute) { + if (attribute.key[0] === "@") { + return addEventListener(element.index, attribute); + } else if (attribute.dynamic) { + return ""; + } else { + return setAttribute(element.index, attribute); + } + }) + mapReduce(element.children, function (child) { return generateCreate(child, element, root); }) + generateMount(element.index, parent.index, insert); + } } + }; - return createCode + (insert === undefined ? appendChild(mountElement, parent.index) : insertBefore(mountElement, insert, parent.index)) + mountCode; + var generateDestroy = function (element, parent, root) { + switch (element.type) { + case "#if": { + return removeChild(element.ifReference, parent.index) + generateDestroy(element.children[0], parent, root); + } + case "#comment": { + return removeChild(element.commentElement, parent.index); + } + case "#text": { + return removeChild(element.textElement, parent.index); + } + default: { + return removeChild(element.index, parent.index); + } + } }; - var generateUpdate = function (element, index, parent, root) { + var generateUpdate = function (element, parent, root) { switch (element.type) { - case "#text": - var content = element.attributes[0]; - return content.dynamic ? setTextContent(element.index, content.value) : ""; - break; - default: + case "#text": { + var textAttribute = element.attributes[0]; + return textAttribute.dynamic ? setTextContent(element.index, textAttribute.value) : ""; + } + case "#if": { + var ifChild = element.children[0]; + return ("\n if (" + (attributeValue(element.attributes[0])) + ") {\n if (" + (getElement(element.ifState)) + " === 0) {\n " + (generateUpdate(ifChild, parent, root)) + "\n } else {\n " + (generateDestroy(ifChild, parent, root)) + "\n " + (element.ifCreate) + "\n " + (setElement(element.ifState, "0")) + "\n }\n }\n "); + } + case "#else": { + var ifChild$1 = element.children[0]; + return ("\n else {\n if (" + (getElement(element.ifState)) + " === 1) {\n " + (generateUpdate(ifChild$1, parent, root)) + "\n } else {\n " + (generateDestroy(ifChild$1, parent, root)) + "\n " + (element.ifCreate) + "\n " + (setElement(element.ifState, "1")) + "\n }\n }\n "); + } + default: { return mapReduce(element.attributes, function (attribute) { if (attribute.key[0] === "@" || !attribute.dynamic) { return ""; } else { return setAttribute(element.index, attribute); } - }) + mapReduce(element.children, function (child, index) { return generateUpdate(child, index, element, root); }); + }) + mapReduce(element.children, function (child) { return generateUpdate(child, element, root); }); + } } }; - var generateDestroy = function (element, index, parent, root) { return removeChild(element.index, parent.index); }; - var generate = function (tree) { var prelude = mapReduce(tree.dependencies, function (dependency) { return ("var " + dependency + "=this.data." + dependency + ";"); }); - return new Function(("return [function(m){this.m[0]=m;m=this.m;" + prelude + (mapReduce(tree.children, function (child, index) { return generateCreate(child, index, tree, tree); })) + "},function(){var m=this.m;" + prelude + (mapReduce(tree.children, function (child, index) { return generateUpdate(child, index, tree, tree); })) + "},function(){var m=this.m;" + (mapReduce(tree.children, function (child, index) { return generateDestroy(child, index, tree, tree); })) + "m=[m[0]];}];"))(); + return new Function(("return [function(m){this.m[0]=m;m=this.m;" + prelude + (mapReduce(tree.children, function (child) { return generateCreate(child, tree, tree); })) + "},function(){var m=this.m;" + prelude + (mapReduce(tree.children, function (child) { return generateUpdate(child, tree, tree); })) + "},function(){var m=this.m;" + (mapReduce(tree.children, function (child) { return generateDestroy(child, tree, tree); })) + "m=[m[0]];}];"))(); }; var compile = function (input) { @@ -474,7 +519,7 @@ var create = function(root) { this.view[0](root); - this.emit("created"); + this.emit("create"); }; var update = function(key, value) { @@ -497,14 +542,14 @@ setTimeout(function () { instance.view[1](); instance.queued = false; - instance.emit("updated"); + instance.emit("update"); }, 0); } }; var destroy = function() { this.view[2](); - this.emit("destroyed"); + this.emit("destroy"); }; var on = function(type, handler) { @@ -603,6 +648,7 @@ var instance = new instanceComponent(); instance.create(root); + instance.update(); return instance; } diff --git a/dist/moon.min.js b/dist/moon.min.js index 9bd4e8f5..0be1ae0b 100644 --- a/dist/moon.min.js +++ b/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 u=/"[^"]*"|'[^']*'|\d+[a-zA-Z$_]\w*|\.[a-zA-Z$_]\w*|[a-zA-Z$_]\w*:|([a-zA-Z$_]\w*)/g,a=["NaN","event","false","in","m","null","this","true","typeof","undefined"],s=function(e,t){for(var n,i=!1;null!==(n=u.exec(e));){var r=n[1];void 0!==r&&-1===a.indexOf(r)&&"$"!==r[0]&&(i=!0,-1===t.indexOf(r)&&t.push(r))}return i},e={silent:!0},d=/\s+/,v=function(e,t,n,i,r){for(;e"===u)break;if(d.test(u))e+=1;else{for(var a="",o="",c=!1;e"===u||d.test(u)){o=a;break}if("="===u){e+=1;break}a+=u,e+=1}if(0===o.length){var f=void 0;for('"'===(u=t[e])||"'"===u?(f=u,e+=1):"{"===u?(f="}",c=!0,e+=1):f=d;e"!==u;){if("object"==typeof f&&f.test(u)||u===f){e+=1;break}o+=u,e+=1}}r.push({key:a,value:o,expression:c,dynamic:c&&s(o,i)})}}return e},o=function(e,t,n,i,r){for(var u={index:i[0].nextIndex++,type:"",attributes:[],children:[]};e"===a){var o=u.attributes;i.push(u);for(var c=0;c"===t[e+1]){i[i.length-1].children.push(u),e+=2;break}d.test(a)&&(e+=1)||"="===a?e=v(e,t,n,r,u.attributes):(u.type+=a,e+=1)}return e},c=function(e,t,n,i){for(;e"===r){e+=1;break}r}var u=i.pop();return u.type,e},f=function(e,t,n){for(;e"===u){e+=3;break}e+=1}}return e},h=/(?:(?:&(?:amp|gt|lt|nbsp|quot);)|"|\\|\n)/g,l={"&":"&",">":">","<":"<"," ":" ",""":'\\"',"\\":"\\\\",'"':'\\"',"\n":"\\n"},p=function(e,t,n,i){for(var r="";e"===a)break;if(d.test(a))e+=1;else{for(var u="",c="",f=!1;e"===a||d.test(a)){c=u;break}if("="===a){e+=1;break}u+=a,e+=1}if(0===c.length){var o=void 0;for('"'===(a=t[e])||"'"===a?(o=a,e+=1):"{"===a?(o="}",f=!0,e+=1):o=d;e"!==a;){if("object"==typeof o&&o.test(a)||a===o){e+=1;break}c+=a,e+=1}}r.push({key:u,value:c,expression:f,dynamic:f&&s(c,i)})}}return e},c=function(e,t,n,i,r){for(var a={type:"",attributes:[],children:[]};e"===u){var c=a.attributes;"#"!==a.type[0]&&(a.index=i[0].nextIndex++),i.push(a);for(var f=0;f"===t[e+1]){"#"!==a.type[0]&&(a.index=i[0].nextIndex++),i[i.length-1].children.push(a),e+=2;break}d.test(u)&&(e+=1)||"="===u?e=l(e,t,n,r,a.attributes):(a.type+=u,e+=1)}return e},f=function(e,t,n,i){for(;e"===r){e+=1;break}r}var a=i.pop();return a.type,e},o=function(e,t,n){for(;e"===a){e+=3;break}e+=1}}return e},v=/(?:(?:&(?:amp|gt|lt|nbsp|quot);)|"|\\|\n)/g,h={"&":"&",">":">","<":"<"," ":" ",""":'\\"',"\\":"\\\\",'"':'\\"',"\n":"\\n"},m=function(e,t,n,i){for(var r="";e { - let createCode, mountCode = "", mountElement = element.index; +const generateMount = (element, parent, insert) => insert === undefined ? appendChild(element, parent) : insertBefore(element, insert, parent); +export const generateCreate = (element, parent, root, insert) => { switch (element.type) { - case "#if": + case "#if": { const siblings = parent.children; - const ifReference = root.nextIndex++; - let ifCreates = ""; - let ifBranches = ""; + const nextSiblingIndex = siblings.indexOf(element) + 1; + const nextSibling = siblings[nextSiblingIndex]; - for (let i = index; i < siblings.length;) { - const sibling = siblings[i]; - let keyword; + element.ifReference = root.nextIndex++; + element.ifState = root.nextIndex++; + element.ifCreate = generateCreate(element.children[0], parent, root, element.ifReference); - if (sibling.type === "#if") { - keyword = `if(${attributeValue(sibling.attributes[0])})`; - } else if (sibling.type === "#elseif") { - keyword = `else if(${attributeValue(sibling.attributes[0])})`; - } else if (sibling.type === "#else") { - keyword = "else"; - } else { - break; - } - - ifCreates += setElement(sibling.index, `function(){${mapReduce(sibling.children, (child, index) => generateCreate(child, index, parent, root, ifReference))}};`); - ifBranches += `${keyword}{${getElement(sibling.index)}();}`; - siblings.splice(i, 1); + if (nextSibling !== undefined && nextSibling.type === "#else") { + nextSibling.ifState = element.ifState; + nextSibling.ifReference = element.ifReference; + } else { + siblings.splice(nextSiblingIndex, 0, { + type: "#else", + attributes: [], + children: [{ + type: "#comment", + attributes: [], + children: [] + }], + ifState: element.ifState, + ifReference: element.ifReference + }); } - createCode = setElement(ifReference, createComment()); - mountCode = ifCreates + ifBranches; - mountElement = ifReference; - break; - case "#text": - createCode = setElement(mountElement, createTextNode(attributeValue(element.attributes[0]))); - break; - default: - createCode = setElement(mountElement, createElement(element.type)) + mapReduce(element.attributes, (attribute) => attribute.key[0] === "@" ? addEventListener(mountElement, attribute) : setAttribute(mountElement, attribute)) + mapReduce(element.children, (child, index) => generateCreate(child, index, element, root)); + return setElement(element.ifReference, createComment()) + generateMount(element.ifReference, parent.index, insert); + } + case "#else": { + element.ifCreate = generateCreate(element.children[0], parent, root, element.ifReference); + return ""; + } + case "#comment": { + element.commentElement = root.nextIndex++; + return setElement(element.commentElement, createComment()); + } + case "#text": { + const textAttribute = element.attributes[0]; + element.textElement = root.nextIndex++; + return setElement(element.textElement, createTextNode(textAttribute.dynamic ? "\"\"" : attributeValue(textAttribute))) + generateMount(element.textElement, parent.index, insert); + } + default: { + return setElement(element.index, createElement(element.type)) + mapReduce(element.attributes, (attribute) => { + if (attribute.key[0] === "@") { + return addEventListener(element.index, attribute); + } else if (attribute.dynamic) { + return ""; + } else { + return setAttribute(element.index, attribute); + } + }) + mapReduce(element.children, (child) => generateCreate(child, element, root)) + generateMount(element.index, parent.index, insert); + } } - - return createCode + (insert === undefined ? appendChild(mountElement, parent.index) : insertBefore(mountElement, insert, parent.index)) + mountCode; }; diff --git a/src/compiler/generator/destroy.js b/src/compiler/generator/destroy.js index 42350150..cc28efcb 100644 --- a/src/compiler/generator/destroy.js +++ b/src/compiler/generator/destroy.js @@ -1,3 +1,18 @@ import { removeChild } from "./util"; -export const generateDestroy = (element, index, parent, root) => removeChild(element.index, parent.index); +export const generateDestroy = (element, parent, root) => { + switch (element.type) { + case "#if": { + return removeChild(element.ifReference, parent.index) + generateDestroy(element.children[0], parent, root); + } + case "#comment": { + return removeChild(element.commentElement, parent.index); + } + case "#text": { + return removeChild(element.textElement, parent.index); + } + default: { + return removeChild(element.index, parent.index); + } + } +}; diff --git a/src/compiler/generator/generator.js b/src/compiler/generator/generator.js index faaa1d2f..507d22b1 100644 --- a/src/compiler/generator/generator.js +++ b/src/compiler/generator/generator.js @@ -5,5 +5,5 @@ import { mapReduce } from "./util"; export const generate = (tree) => { const prelude = mapReduce(tree.dependencies, (dependency) => `var ${dependency}=this.data.${dependency};`); - return new Function(`return [function(m){this.m[0]=m;m=this.m;${prelude}${mapReduce(tree.children, (child, index) => generateCreate(child, index, tree, tree))}},function(){var m=this.m;${prelude}${mapReduce(tree.children, (child, index) => generateUpdate(child, index, tree, tree))}},function(){var m=this.m;${mapReduce(tree.children, (child, index) => generateDestroy(child, index, tree, tree))}m=[m[0]];}];`)(); + return new Function(`return [function(m){this.m[0]=m;m=this.m;${prelude}${mapReduce(tree.children, (child) => generateCreate(child, tree, tree))}},function(){var m=this.m;${prelude}${mapReduce(tree.children, (child) => generateUpdate(child, tree, tree))}},function(){var m=this.m;${mapReduce(tree.children, (child) => generateDestroy(child, tree, tree))}m=[m[0]];}];`)(); }; diff --git a/src/compiler/generator/update.js b/src/compiler/generator/update.js index 5b69b57e..10f04472 100644 --- a/src/compiler/generator/update.js +++ b/src/compiler/generator/update.js @@ -1,18 +1,48 @@ -import { mapReduce, setAttribute, setTextContent } from "./util"; +import { generateDestroy } from "./destroy"; +import { mapReduce, getElement, setElement, attributeValue, setAttribute, setTextContent } from "./util"; -export const generateUpdate = (element, index, parent, root) => { +export const generateUpdate = (element, parent, root) => { switch (element.type) { - case "#text": - const content = element.attributes[0]; - return content.dynamic ? setTextContent(element.index, content.value) : ""; - break; - default: + case "#text": { + const textAttribute = element.attributes[0]; + return textAttribute.dynamic ? setTextContent(element.index, textAttribute.value) : ""; + } + case "#if": { + const ifChild = element.children[0]; + return ` + if (${attributeValue(element.attributes[0])}) { + if (${getElement(element.ifState)} === 0) { + ${generateUpdate(ifChild, parent, root)} + } else { + ${generateDestroy(ifChild, parent, root)} + ${element.ifCreate} + ${setElement(element.ifState, "0")} + } + } + `; + } + case "#else": { + const ifChild = element.children[0]; + return ` + else { + if (${getElement(element.ifState)} === 1) { + ${generateUpdate(ifChild, parent, root)} + } else { + ${generateDestroy(ifChild, parent, root)} + ${element.ifCreate} + ${setElement(element.ifState, "1")} + } + } + `; + } + default: { return mapReduce(element.attributes, (attribute) => { if (attribute.key[0] === "@" || !attribute.dynamic) { return ""; } else { return setAttribute(element.index, attribute); } - }) + mapReduce(element.children, (child, index) => generateUpdate(child, index, element, root)); + }) + mapReduce(element.children, (child) => generateUpdate(child, element, root)); + } } }; diff --git a/src/compiler/generator/util.js b/src/compiler/generator/util.js index 9da5d454..b31fd867 100644 --- a/src/compiler/generator/util.js +++ b/src/compiler/generator/util.js @@ -2,7 +2,7 @@ export const mapReduce = (arr, fn) => { let result = ""; for (let i = 0; i < arr.length; i++) { - result += fn(arr[i], i); + result += fn(arr[i]); } return result; diff --git a/src/compiler/parser/expression.js b/src/compiler/parser/expression.js index dcdd1fe3..71965e01 100644 --- a/src/compiler/parser/expression.js +++ b/src/compiler/parser/expression.js @@ -15,7 +15,6 @@ export const parseExpression = (index, input, length, stack, dependencies) => { } stack[stack.length - 1].children.push({ - index: stack[0].nextIndex++, type: "#text", attributes: [{ key: "", diff --git a/src/compiler/parser/tag.js b/src/compiler/parser/tag.js index fca05879..cc87e6a5 100644 --- a/src/compiler/parser/tag.js +++ b/src/compiler/parser/tag.js @@ -74,7 +74,6 @@ const parseAttributes = (index, input, length, dependencies, attributes) => { export const parseOpeningTag = (index, input, length, stack, dependencies) => { let element = { - index: stack[0].nextIndex++, type: "", attributes: [], children: [] @@ -85,6 +84,11 @@ export const parseOpeningTag = (index, input, length, stack, dependencies) => { if (char === ">") { const attributes = element.attributes; + + if (element.type[0] !== "#") { + element.index = stack[0].nextIndex++; + } + stack.push(element); for (let i = 0; i < attributes.length;) { @@ -92,7 +96,6 @@ export const parseOpeningTag = (index, input, length, stack, dependencies) => { if (attribute.key[0] === "#") { element = { - index: stack[0].nextIndex++, type: attribute.key, attributes: [{ key: "", @@ -113,6 +116,10 @@ export const parseOpeningTag = (index, input, length, stack, dependencies) => { index += 1; break; } else if (char === "/" && input[index + 1] === ">") { + if (element.type[0] !== "#") { + element.index = stack[0].nextIndex++; + } + stack[stack.length - 1].children.push(element); index += 2; diff --git a/src/compiler/parser/text.js b/src/compiler/parser/text.js index b319e355..d5296226 100644 --- a/src/compiler/parser/text.js +++ b/src/compiler/parser/text.js @@ -27,7 +27,6 @@ export const parseText = (index, input, length, stack) => { if (!whitespaceRE.test(content)) { stack[stack.length - 1].children.push({ - index: stack[0].nextIndex++, type: "#text", attributes: [{ key: "", diff --git a/src/compiler/parser/util.js b/src/compiler/parser/util.js index 3e416a20..d0d761d3 100644 --- a/src/compiler/parser/util.js +++ b/src/compiler/parser/util.js @@ -1,3 +1,3 @@ export { error } from "../../util/util"; -export const whitespaceRE = /\s+/; +export const whitespaceRE = /^\s+$/; diff --git a/src/component/component.js b/src/component/component.js index 39f15246..62efb41d 100644 --- a/src/component/component.js +++ b/src/component/component.js @@ -2,7 +2,7 @@ import { m } from "../util/m"; const create = function(root) { this.view[0](root); - this.emit("created"); + this.emit("create"); }; const update = function(key, value) { @@ -23,14 +23,14 @@ const update = function(key, value) { setTimeout(() => { instance.view[1](); instance.queued = false; - instance.emit("updated"); + instance.emit("update"); }, 0); } }; const destroy = function() { this.view[2](); - this.emit("destroyed"); + this.emit("destroy"); }; const on = function(type, handler) { diff --git a/src/index.js b/src/index.js index 1608f494..7dd826c9 100644 --- a/src/index.js +++ b/src/index.js @@ -32,6 +32,7 @@ export default function Moon(options) { const instance = new instanceComponent(); instance.create(root); + instance.update(); return instance; }