diff --git a/packages/moon/dist/moon.js b/packages/moon/dist/moon.js
index 5071bb9f..c958fd03 100644
--- a/packages/moon/dist/moon.js
+++ b/packages/moon/dist/moon.js
@@ -22,16 +22,6 @@
component: 2,
fragment: 3
};
- /**
- * Checks if a given character is a quote.
- *
- * @param {string} char
- * @returns {boolean} True if the character is a quote
- */
-
- function isQuote(_char) {
- return _char === "\"" || _char === "'";
- }
/**
* Logs an error message to the console.
* @param {string} message
@@ -74,6 +64,11 @@
return full;
}
+ /**
+ * Capture whitespace-only text.
+ */
+
+ var whitespaceRE = /^\s+$/;
/**
* Capture the tag name, attribute text, and closing slash from an opening tag.
*/
@@ -98,12 +93,23 @@
*/
var globals = ["NaN", "event", "false", "in", "null", "this", "true", "typeof", "undefined", "window"];
+ /**
+ * Checks if a given character is a quote.
+ *
+ * @param {string} char
+ * @returns {boolean} True if the character is a quote
+ */
+
+ function isQuote(_char) {
+ return _char === "\"" || _char === "'";
+ }
/**
* Scope an expression to use variables within the `data` object.
*
* @param {string} expression
*/
+
function scopeExpression(expression) {
return expression.replace(expressionRE, function (match, name) {
return name === undefined || globals.indexOf(name) !== -1 ? match : "data." + name;
@@ -191,9 +197,9 @@
var tokens = [];
for (var i = 0; i < input.length;) {
- var _char = input[i];
+ var _char2 = input[i];
- if (_char === "<") {
+ if (_char2 === "<") {
var charNext = input[i + 1];
if ("development" === "development" && charNext === undefined) {
@@ -286,18 +292,18 @@
closed: closeSlash === "/"
});
i += typeMatch.length;
- } else if (_char === "{") {
+ } else if (_char2 === "{") {
// If a sequence of characters begins with "{", process it as an
// expression token.
var expression = ""; // Consume the input until the end of the expression.
for (i += 1; i < input.length; i++) {
- var _char2 = input[i];
+ var _char3 = input[i];
- if (_char2 === "}") {
+ if (_char3 === "}") {
break;
} else {
- expression += _char2;
+ expression += _char3;
}
} // Append the expression as a `` element with the appropriate
// text content attribute.
@@ -317,25 +323,27 @@
var text = ""; // Consume the input until the start of a new tag or expression.
for (; i < input.length; i++) {
- var _char3 = input[i];
+ var _char4 = input[i];
- if (_char3 === "<" || _char3 === "{") {
+ if (_char4 === "<" || _char4 === "{") {
break;
} else {
- text += _char3;
+ text += _char4;
}
} // Append the text as a `` element with the appropriate text
- // content attribute.
+ // content attribute if it isn't only whitespace.
- tokens.push({
- type: "tagOpen",
- value: "text",
- attributes: {
- "": "\"" + text + "\""
- },
- closed: true
- });
+ if (!whitespaceRE.test(text)) {
+ tokens.push({
+ type: "tagOpen",
+ value: "text",
+ attributes: {
+ "": "\"" + text + "\""
+ },
+ closed: true
+ });
+ }
}
}
@@ -531,23 +539,92 @@
}
/**
- * Generator
+ * Global variable number
+ */
+ var generateVariable;
+ /**
+ * Set variable number to a new number.
*
- * The generator is responsible for generating a function that creates a view.
- * A view could be represented as a normal set of recursive function calls, but
- * it uses lightweight objects to represent them instead. This allows the
- * executor to execute the function over multiple frames with its own
- * representation of the stack.
+ * @param {number} newGenerateVariable
+ */
+
+ function setGenerateVariable(newGenerateVariable) {
+ generateVariable = newGenerateVariable;
+ }
+
+ /**
+ * Generates view function code and prelude code for an `if` element.
*
* @param {Object} element
- * @returns {string} View function code
+ * @param {Object} parent
+ * @param {number} index
+ * @returns {Object} View function code and prelude code
*/
- function generate(element) {
+ function generateNodeIf(element, parent, index) {
+ 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.
+
+ if (parent !== null) {
+ var siblings = parent.children;
+
+ for (var i = index + 1; i < siblings.length; i++) {
+ var sibling = siblings[i];
+
+ 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
+ // 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.
+
+ emptyElseClause = false; // Remove the `else` clause so that it isn't generated
+ // individually by the parent.
+
+ siblings.splice(i, 1);
+ } else {
+ break;
+ }
+ }
+ } // Generate an empty `else` clause represented by an empty text node.
+
+
+ if (emptyElseClause) {
+ prelude += "else{" + variable + "={type:" + types.text + ",name:\"text\",data:{children:[]}};}";
+ }
+
+ return {
+ prelude: prelude,
+ node: variable
+ };
+ }
+
+ /**
+ * Generates view function code for a Moon node from an element.
+ *
+ * @param {Object} element
+ * @param {Object} parent
+ * @param {number} index
+ * @returns {Object} View function code and prelude code
+ */
+
+ function generateNode(element, parent, index) {
var name = element.type;
- var type;
+ var type; // Generate the correct type number for the given name.
- if (name === "text") {
+ if (name === "if") {
+ return generateNodeIf(element, parent, index);
+ } else if (name === "text") {
type = types.text;
} else if (name === "fragment") {
type = types.fragment;
@@ -558,6 +635,7 @@
}
var attributes = element.attributes;
+ var prelude = "";
var data = "{";
var separator = "";
@@ -573,14 +651,43 @@
separator = "";
for (var i = 0; i < children.length; i++) {
- data += separator + generate(children[i]);
+ var childNode = generateNode(children[i], element, i);
+ prelude += childNode.prelude;
+ data += separator + childNode.node;
separator = ",";
}
data += "]";
}
- return "{type:" + type + ",name:\"" + name + "\",data:" + data + "}}";
+ return {
+ prelude: prelude,
+ node: "{type:" + type + ",name:\"" + name + "\",data:" + data + "}}"
+ };
+ }
+ /**
+ * Generator
+ *
+ * The generator is responsible for generating a function that creates a view.
+ * A view could be represented as a normal set of recursive function calls, but
+ * it uses lightweight objects to represent them instead. This allows the
+ * executor to execute the function over multiple frames with its own
+ * representation of the stack.
+ *
+ * @param {Object} element
+ * @returns {string} View function code
+ */
+
+ function generate(element) {
+ // Reset generator variable.
+ setGenerateVariable(0); // Generate the root node and get the prelude and node code.
+
+ var _generateNode = generateNode(element, null, 0),
+ prelude = _generateNode.prelude,
+ node = _generateNode.node; // Convert the code into a usable function body.
+
+
+ return prelude + "return " + node + ";";
}
function compile(input) {
@@ -603,6 +710,7 @@
var components = {};
/**
* Set old view to a new object.
+ *
* @param {Object} viewOld
*/
@@ -611,6 +719,7 @@
}
/**
* Set new view to a new object.
+ *
* @param {Object} viewOld
*/
@@ -619,6 +728,7 @@
}
/**
* Set current view to a new function.
+ *
* @param {Function} viewCurrentNew
*/
@@ -1082,7 +1192,7 @@
}
if (typeof view === "string") {
- view = new Function("data", "return " + compile(view));
+ view = new Function("data", compile(view));
} // If a `root` option is given, start the root renderer, or else just return
// the component.
diff --git a/packages/moon/dist/moon.min.js b/packages/moon/dist/moon.min.js
index e1526eb9..16918a55 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 v={element:0,text:1,component:2,fragment:3};var N=/<([\w\d-_]+)([^>]*?)(\/?)>/g,A=/\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","event","false","in","null","this","true","typeof","undefined","window"];function E(e){return e.replace(n,function(e,n){return void 0===n||-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 u=N.exec(e);0;for(var l=u[0],p=u[1],f=u[2],s=u[3],c={},v=void 0;null!==(v=A.exec(f));){var h=v[0],m=v[1],g=v[2],w=v[3];0===h.length?A.lastIndex+=1:(c[m]=void 0===w?g:E(w),"@"===m[0]&&(c[m]="function(event){"+c[m]+"}"))}n.push({type:"tagOpen",value:p,attributes:c,closed:"/"===s}),t+=l.length}else if("{"===r){var y="";for(t+=1;t]*?)(\/?)>/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","event","false","in","null","this","true","typeof","undefined","window"];function E(e){return e.replace(n,function(e,n){return void 0===n||-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}A.lastIndex=t;var l=A.exec(e);0;for(var u=l[0],p=l[1],f=l[2],s=l[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?g:E(w),"@"===m[0]&&(c[m]="function(event){"+c[m]+"}"))}n.push({type:"tagOpen",value:p,attributes:c,closed:"/"===s}),t+=u.length}else if("{"===r){var y="";for(t+=1;t` element with the appropriate text
- // content attribute.
- tokens.push({
- type: "tagOpen",
- value: "text",
- attributes: {
- "": `"${text}"`
- },
- closed: true
- });
+ // content attribute if it isn't only whitespace.
+ if (!whitespaceRE.test(text)) {
+ tokens.push({
+ type: "tagOpen",
+ value: "text",
+ attributes: {
+ "": `"${text}"`
+ },
+ closed: true
+ });
+ }
}
}
diff --git a/packages/moon/src/index.js b/packages/moon/src/index.js
index f04d44cf..bbdad1b8 100644
--- a/packages/moon/src/index.js
+++ b/packages/moon/src/index.js
@@ -46,7 +46,7 @@ export default function Moon(options) {
}
if (typeof view === "string") {
- view = new Function("data", "return " + compile(view));
+ view = new Function("data", compile(view));
}
// If a `root` option is given, start the root renderer, or else just return
diff --git a/packages/moon/src/util/globals.js b/packages/moon/src/util/globals.js
index dd282201..a0357d5a 100644
--- a/packages/moon/src/util/globals.js
+++ b/packages/moon/src/util/globals.js
@@ -15,6 +15,7 @@ export const components = {};
/**
* Set old view to a new object.
+ *
* @param {Object} viewOld
*/
export function setViewOld(viewOldNew) {
@@ -23,6 +24,7 @@ export function setViewOld(viewOldNew) {
/**
* Set new view to a new object.
+ *
* @param {Object} viewOld
*/
export function setViewNew(viewNewNew) {
@@ -31,6 +33,7 @@ export function setViewNew(viewNewNew) {
/**
* Set current view to a new function.
+ *
* @param {Function} viewCurrentNew
*/
export function setViewCurrent(viewCurrentNew) {
diff --git a/packages/moon/src/util/util.js b/packages/moon/src/util/util.js
index e5dab1e7..83423218 100644
--- a/packages/moon/src/util/util.js
+++ b/packages/moon/src/util/util.js
@@ -8,16 +8,6 @@ export const types = {
fragment: 3
};
-/**
- * Checks if a given character is a quote.
- *
- * @param {string} char
- * @returns {boolean} True if the character is a quote
- */
-export function isQuote(char) {
- return char === "\"" || char === "'";
-}
-
/**
* Logs an error message to the console.
* @param {string} message