I'm building a framework like this that can turn SASS-like code into CSS, a template engine like Mustache, a DOM manipulator like React, and a router to add a single page app, and I'm having a performance problem.
I need some help to get it running quickly and maybe some code refactoring if possible.
var Dust = (function() {
var $Sass = {regex: {url: /@import\s+(?:url\()?\s*(?:['"])?([^'"\)\s]+)(?:['"])?\s*(?:\))?;/g,selector: /([^\s\;\{\}][^\;\{\}]*)\{/g,end: /\}/g,line: /([^\;\{\}]*)\;/g,comment: /\/\*[\s\S]*?\*\//g,attribute: /([^\:]+):([^\;]*);/,alt: /(\/\*[\s\S]*?\*\/)|([^\s\;\{\}][^\;\{\}]*(?=\{))|(\})|([^\;\{\}]+\;(?!\s*\*\/))/gim},},
$Preset = {devider: "__devide__",quotes: "(?=(?:[^'\"`]|[\"'`][^'\"`]*[\"'`])*$)",regex: {imports: /{{\s*#import\s+['|"|`](.*?)['|"|`]\s*}}/g,spacers: /\s*(<\/?[\w\s="/.':;#-\/\?]+>)\s*/gi,escape: /@{{/gi,comment: /{{!--(.*?)--}}/gms,array: /(.*)->(.*)/g,start: "{{",end: "}}",},},
$Maker = {text: "text",svgs: {svg: true,animate: true,animateMotion: true,"mask-box-width": true,"mask-border-width": true,"shape-image-threshold": true,}},
$Router = {
join: function() {
var separator = "/";
return separator + arrayFilter(Array.from(arguments).join(separator).split(separator), function(e) {return e.length;}).join(separator);
},
map: function(tree, parent, routes) {
var guard = function() {return true;},fn = function() {},tree = tree || [],routes = routes || [],parent = parent || {path: "",guard: function() {return true;},};
for (var object of tree) {
var _ = {};
_.view = object.view;
_.path = $Router.join(parent.path, object.path);
_.guard = (Verify.fn(object.guard) && object.guard) || (object.guard == "current" && parent.guard) || guard;
_.name = object.name;
_.fn = object.fn || fn;
routes.push(_);
$Router.map(object.scope, object, routes);
}
return routes;
},
path: function(path) {
return new RegExp("^" + path.replace(/\//g, "\\/").replace(/{\w+:\w+}/g, "(.+)") + "$");
},
match: function(router) {
var url = (router.hash ? location.hash.substring(1) : location.pathname) || "/";
if (!router.hash && !url.startsWith("/")) url = "/" + url;
var potentialMatches = router.routes.map(function(route) {return {route: route,result: url.match($Router.path(route.path)),};});
var match = potentialMatches.find(function(potentialMatch) {return potentialMatch.result !== null; });
if (!match) { match = {route: router.routes.find(function(p) {return p.path === "/404";}), result: [url],};}
return match;
},
params: function(match) {
var values = arrayMap(arraySlice(match.result, 1), function(e) {return e.split("?")[0];});
var keys = arrayMap(arrayFrom(match.route.path.matchAll(/{(\w+):/g)), function(result) {return result[1];});
var types = arrayMap(arrayFrom(match.route.path.matchAll(/:(\w+)}/g)), function(result) {return result[1];});
return objectFromEntries(arrayMap(keys, function(key, i) {
var val = void 0;
switch (types[i].toLowerCase()) {
case "str":
val = String(values[i]);
break;
case "int":
val = parseInt(values[i]);
break;
case "num":
val = Number(values[i]);
break;
case "real":
val = parseFloat(values[i]);
break;
case "bool":
val = Boolean(values[i]);
break;
case "any":
default:
val = values[i];
break;
}
return [key, val];
}));
}
},
$HELPERS = {},$Navigate;
var _toArray = function(array) {
var _array = [];
for (var object of array) {
_array.push(object);
}
return _array
},
arrayFrom = function(array, callback, args) {
if ("next" in array) array = _toArray(array);
if (Verify.type(array) === "set") array = _toArray(array.values());
if (Verify.obj(array) && Verify.nbr(array.length)) {
for (var len = array.length, _array = [], i = 0; i < len; i++) _array.push(undefined);
array = _array;
}
for (var len = array.length, _array = [], i = 0; i < len; i++) {
_array.push(Verify.fn(callback) ? callback.call(args, array[i], i, array) : array[i]);
}
return _array;
},
arrayMap = function(array, callback, args) {
for (var len = array.length, _array = [], i = 0; i < len; i++) {
_array[i] = callback.call(args, array[i], i, array);
}
return _array;
},
arrayFilter = function(array, callback, args) {
for (var len = array.length, _array = [], i = 0; i < len; i++) {
if (callback.call(args, array[i], i, array))
_array.push(array[i]);
}
return _array;
},
arrayFind = function(array, callback, args) {
for (var len = array.length, i = 0; i < len; i++) {
if (callback.call(args, array[i], i, array))
return array[i];
}
return null;
},
arraySort = function(array, callback, args) {
for (var len = array.length - 1, i = 0; i < len; i++) {
for (var _len = array.length, _i = i + 1; _i < _len; _i++) {
if (callback.call(args, array[i], array[_i], i, array) > 0) {
var temp = array[i];
array[i] = array[_i];
array[_i] = temp;
}
}
}
return array;
},
arrayEach = function(array, callback, args) {
for (var len = array.length, i = 0; i < len; i++) {
callback.call(args, array[i], i, array);
}
},
arrayReduce = function(array, callback, start, args) {
for (var len = array.length, i = 0; i < len; i++) {
start = callback.call(args, start, array[i], i, array);
}
return start;
},
arraySlice = function(array, start, end) {
for (var len = end || array.length, _array = [], i = start || 0; i < len; i++) {
_array.push(array[i]);
}
return _array;
},
arrayConcat = function() {
for (var len = arguments.length, array = [], i = 0; i < len; i++) {
for (var _array = arguments[i], _len = _array.length, _i = 0; _i < _len; _i++) {
array.push(_array[_i]);
}
}
return array;
};
var objectProperty = function(object, property, descriptor) {
Object.prototype.__defineGetter__ && ('get' in descriptor) && Object.prototype.__defineGetter__.call(object, property, descriptor.get);
Object.prototype.__defineSetter__ && ('set' in descriptor) && Object.prototype.__defineSetter__.call(object, property, descriptor.set);
('value' in descriptor) && (object[property] = descriptor.value);
return object;
},
objectKeys = function(object) {
var key, array = [];
for (key in object) Object.prototype.hasOwnProperty.call(object, key) && array.push(key);
return array;
},
objectValues = function(object) {
var key, array = [];
for (key in object) Object.prototype.hasOwnProperty.call(object, key) && array.push(object[key]);
return array;
},
objectEntries = function(object) {
var key, array = [];
for (key in object) Object.prototype.hasOwnProperty.call(object, key) && array.push([key, object[key]]);
return array;
},
objectFromEntries = function(array) {
for (var len = array.length, object = {}, i = 0; i < len; i++) {
Verify.arr(array[i]) && (array[i].length == 2) && (object[array[i][0]] = array[i][1]);
}
return object;
}
var execArrayFns = function(self, name) {
const current = self[name];
for (let fn of current) fn();
}
var Verify = (function() {
function Verify() {}
Verify.type = function(element) {return Object.prototype.toString.call(element).slice(8, -1).toLowerCase();}
Verify.null = function(element) {return this.type(element) === "null";}
Verify.undefined = function(element) {return this.type(element) === "undefined";}
Verify.str = function(element) {return this.type(element) === "string";}
Verify.nbr = function(element) {return this.type(element) === "number";}
Verify.obj = function(element) {return this.type(element) === "object";}
Verify.arr = function(element) {return this.type(element) === "array";}
Verify.fn = function(element) {return this.type(element) === "function";}
Verify.absent = function(element) {return (this.null(element) || this.undefined(element) ||(this.nbr(element) && isNaN(element)) ||(this.str(element) && element === "") ||(this.arr(element) && element.length === 0) ||(this.obj(element) && objectKeys(element).length === 0));}
Verify.present = function(element) {return !this.absent(element);}
return Verify;
})();
var Fiber = (function() {
function Fiber(props) {
this.type = props.type && props.type.toLowerCase();
this.props = props.props;
}
Fiber.prototype.isText = function() {
return this.type === $Maker.text;
}
Fiber.prototype.isSvg = function() {
return this.type in $Maker.svgs;
}
return Fiber;
})();
var Instance = (function() {
function Instance(props) {
this.dom = props.dom;
this.fiber = props.fiber;
this.children = props.children;
}
return Instance;
})();
var Import = (function() {
function Import(url) {
this.url = url;
}
Import.prototype.string = function() {
return `@import url("${this.url}");`;
}
return Import;
})();
var Font = (function() {
function Font(props) {
this.name = props.name;
this.properties = props.properties;
}
Font.prototype.string = function() {
return `@face-font {${arrayMap(this.properties, function (e) {
return e.string();
}).join("")}}`;
}
return Font;
})();
var Variable = (function() {
function Variable(props) {
this.name = props.name;
this.value = props.value;
}
return Variable;
})();
var Mixin = (function() {
function Mixin(props) {
this.name = props.name;
this.properties = props.properties;
}
return Mixin;
})();
var Block = (function() {
function Block(props) {
this.selector = props.selector;
this.properties = props.properties;
this.index = props.index;
}
Block.prototype.string = function() {
return `${this.selector}{${arrayMap(this.properties, function (e) {
return e.string();
}).join("")}}`;
}
return Block;
})();
var Property = (function() {
function Property(props) {
this.name = props.name;
this.value = props.value;
}
Property.prototype.string = function() {
return `${this.name}:${this.value};`;
}
return Property;
})();
var Frame = (function() {
function Frame(props) {
this.name = props.name;
this.properties = props.properties;
}
Frame.prototype.string = function() {
var self = this;
return arrayMap(["-webkit-", ""], function(frame) {
return `@${frame}keyframes ${self.name}{${arrayMap(self.properties, function (e) {
return e.string();
}).join("")}}`;
}).join("");
}
return Frame;
})();
var Media = (function() {
function Media(props) {
this.condition = props.condition;
this.properties = props.properties;
}
Media.prototype.string = function() {
return `@media ${this.condition}{${arrayMap(this.properties, function (e) {
return e.string();
}).join("")}}`
}
return Media;
})();
var Sass = (function() {
var prefixList = function(property) {
return Sass.PREFIX_NAMES[property] || [];
}
function Sass(template) {
this.entry = template.replace(/(\n|\s)+/g, " ");
this.variables = [];
this.imports = [];
this.frames = [];
this.mixins = [];
this.medias = [];
this.errors = [];
this.fonts = [];
this.tree = [];
this.count = 0;
this._exec();
}
Sass.prototype._exec = function() {
var self = this;
this._extract();
this.tree = this._parse(this.tree);
arrayEach(this.fonts, function(font) {
var props = [];
for (var prop in font.properties)
if (prop !== "@index")
props.push(new Property({
name: prop,
value: font.properties[prop]
}));
font.properties = props;
});
arrayEach(this.medias, function(block) {
block.properties = self._parse(block.properties);
});
arrayEach(this.frames, function(block) {
block.properties = self._parse(block.properties);
});
}
Sass.prototype._object = function() {
var sheet = {},
match = null,
entry = this.entry.replace($Sass.regex.url, function(_, s) {
return `@import: "${encodeURIComponent(s)}";`;
})
.replace($Sass.regex.comment, '')
.replace(/(@include)/g, "$1:");
while ((match = $Sass.regex.alt.exec(entry)) != null) {
if (Verify.present(match[ /*selector*/ 2])) {
var name = match[ /*selector*/ 2].trim(),
id = String(this.count++),
_sheet = this._object(entry);
_sheet["@index"] = id;
sheet[name] = _sheet;
} else if (Verify.present(match[ /*end*/ 3])) {
return sheet;
} else if (Verify.present(match[ /*attr*/ 4])) {
var line = match[ /*attr*/ 4].trim(),
attr = $Sass.regex.attribute.exec(line);
if (attr) {
var name = attr[1].trim(),
value = attr[2].trim();
sheet[name] = value;
name === "@import" && this.count++;
}
}
}
return sheet;
}
Sass.prototype._extract = function() {
var self = this,
tree = this._object();
arrayEach(objectKeys(tree), function(key) {
var found = false;
if (key[0] === "$") {
self.variables.push(new Variable({
name: key.substring(1),
value: tree[key],
}));
found = true;
} else if (key.slice(1, 7) === "import") {
self.imports.push(new Import(decodeURIComponent(tree[key].replace(/["'`]/g, ""))));
found = true;
} else if (key.slice(1, 6) === "mixin") {
self.mixins.push(new Mixin({
name: key.substring(6).trim(),
properties: Object.assign({}, tree[key])
}));
found = true;
} else if (key.slice(1, 6) === "media") {
self.medias.push(new Media({
condition: key[6] === "." ? Sass.BREAK_POINTS[key.substring(7).trim()] : key.substring(6).trim(),
properties: Object.assign({}, self._variable(self._mixin(tree[key])))
}));
found = true;
} else if (key.slice(1, 6) === "frame") {
self.frames.push(new Frame({
name: key.substring(6).trim(),
properties: Object.assign({}, self._variable(self._mixin(tree[key])))
}));
found = true;
} else if (key.slice(1, 5) === "font") {
var font = key.substring(6).trim();
tree[key]["font-family"] = '"' + font + '"';
self.fonts.push(new Font({
name: font,
properties: Object.assign({}, self._variable(self._mixin(tree[key])))
}));
found = true;
}
if (found)
delete tree[key];
});
this.tree = this._variable(this._mixin(tree));
}
Sass.prototype._property = function(tree, parent, _tree) {
var self = this,
properties = [];
arrayEach(objectKeys(tree), function(key) {
if (Verify.obj(tree[key])) {
var _ = [],
selector = arrayMap(key.split(","), function(e) {
return e.trim();
});
for (var i = 0; i < parent.length; i++) {
for (var j = 0; j < selector.length; j++) {
var text = parent[i] + (selector[j][0] === "&" ? selector[j].substring(1) : " " + selector[j]);
_.push(text);
}
}
_tree.push({
selector: _,
properties: self._property(tree[key], _, _tree)
});
} else {
arrayEach(prefixList(key), function(prop) {
properties.push(new Property({
name: prop + key,
value: tree[key],
}));
});
properties.push(new Property({
name: key,
value: tree[key],
}));
}
});
return properties;
}
Sass.prototype._clean = function(tree) {
tree = arrayFilter(tree, function(e) {
return e.selector[0] !== "@index";
});
tree = arraySort(tree, function(a, b) {
var apos = parseInt(arrayFind(a.properties, function(e) {
return e.name === "@index";
}).value);
var bpos = parseInt(arrayFind(b.properties, function(e) {
return e.name === "@index";
}).value);
return apos - bpos;
});
tree = arrayMap(tree, function(e) {
var position, props = [];
arrayEach(e.properties, function(e, i) {
if (e.name === "@index") position = e.value;
else props.push(e);
});
return (new Block({
properties: props,
selector: e.selector.join(),
index: parseInt(position),
}));
});
tree = arrayFilter(tree, function(e) {
return e.properties.length > 0;
});
return tree;
}
Sass.prototype._variable = function(tree) {
var remove, error, self = this;
arrayEach(objectKeys(tree), function(key) {
if (Verify.obj(tree[key])) {
tree[key] = self._variable(tree[key]);
} else {
tree[key] = tree[key].replace(/\$([A-za-z0-9_-]+)/g, function(_, s) {
var _var = arrayFind(self.variables, function(e) {
return e.name === s;
});
if (!_var)(remove = true) && (error = `variables does not include "${s}"`) && !self.errors.includes(error) && self.errors.push(error) && console.error(error);
else return _var.value;
});
remove && delete tree[key] && (remove = null);
}
});
return tree;
}
Sass.prototype._mixin = function(tree) {
var error, self = this,
_tree = {};
arrayEach(objectKeys(tree), function(key) {
if (Verify.obj(tree[key])) {
_tree[key] = self._mixin(tree[key]);
} else {
if (key === "@include") {
var names = arrayMap(tree[key].split(","), function(e) {
return e.trim();
});
arrayEach(names, function(name) {
var object = arrayFind(self.mixins, function(e) {
return e.name === name;
});
if (object && object.properties) {
_tree = Object.assign({}, _tree, self._mixin(object.properties));
} else(error = `@mixins does not include "${name}"`) && !self.errors.includes(error) && self.errors.push(error) && console.error(error);
});
} else {
_tree[key] = tree[key];
}
}
});
return _tree;
}
Sass.prototype._parse = function(tree) {
var self = this,
_tree = [];
arrayEach(objectKeys(tree), function(key) {
if (Verify.obj(tree[key])) {
var selector = arrayMap(key.split(","), function(e) {
return e.trim();
});
_tree.push({
selector: selector,
properties: self._property(tree[key], selector, _tree),
});
}
});
return this._clean(_tree);
}
Sass.prototype.string = function() {
var self = this,
stylesheet = arrayMap(this.imports, function(e) {
return e.string();
});
arrayEach(this.fonts, function(font) {
stylesheet.push(self._parse({
"@font-face": font.properties
})[0].string());
});
arrayEach(this.tree, function(block) {
stylesheet.push(block.string());
});
arrayEach(this.medias, function(frame) {
stylesheet.push(frame.string());
});
arrayEach(this.frames, function(block) {
stylesheet.push(block.string());
});
return stylesheet.join("");
}
Sass.BREAK_POINTS = {
sm: "(min-width:640px)",
md: "(min-width:768px)",
lg: "(min-width:1024px)",
xl: "(min-width:1280px)",
}
Sass.PREFIX_NAMES = {
"align-content": ["-webkit-", "-ms-", "-moz-"],
"align-items": ["-webkit-", "-ms-", "-moz-"],
"user-drag": ["-webkit-", "-khtml-"],
"wrap-flow": ["-webkit-"],
"wrap-through": ["-webkit-"],
"zoom": ["-ms-"]
}
return Sass;
})();
var Preset = (function() {
var modeType, memory = {},
_regex = [
[$Preset.regex.comment, ""],
[$Preset.regex.escape, "\\{\\{"],
[$Preset.regex.spacers, function(_, t) {
return t.trim();
}]
],
systemFns = {
each(obj, func) {
if (obj === null) {
return obj;
}
var index = -1;
if (Array.isArray(obj)) {
var length = obj.length,
count = 1;
while (++index < length) {
if (func(index, obj[index], {index: index, round: count, odd: index % 2 > 0, even: index % 2 === 0, first: index === 0, last: count === length,}) === false) {
break;
}
count++;
}
}
var key = Object.keys(obj),
length = key.length,
count = 1;
while (++index < length) {
if (func(key[index], obj[key[index]], {index: index,round: count,odd: index % 2 > 0,even: index % 2 === 0,first: index === 0,last: count === length,}) === false) {
break;
}
count++;
}
},
range(from, to, inc, func) {
to = inc ? (from < to ? to + 1 : to - 1) : to;
for (var i = from; from < to ? i < to : i > to; from < to ? i++ : i--) {
func(i, {index: i,round: i + 1,odd: i % 2 > 0,even: i % 2 === 0,first: i === from,last: i === from < to ? i - 1 : i + 1});
}
},
length(obj) {
if (obj === null) return obj;
if (Array.isArray(obj)) return obj.length;
Object.keys(obj).length
}
},
mode = {
if (args) {
modeType = "if";
return "if(" + args.join(" ") + "){";
},
elif(args) {return "} else if(" + args.join(" ") + "){";},
else() {return (modeType === "for" ? "});" : "") + "}else{";},
unless(args) {return "if(!(" + args.join(" ") + ")){";},
case (args) {return "break;case " + args.join(" ") + ":";},
default () {return "break;default:";},
},
loop = {
while (args) {return "while(" + args.join(" ") + "){";},
untill(args) {return "while(!(" + args.join(" ") + ")){";},
switch (args) {return "switch(" + args.join(" ") + "){";},
range(args) {return "$RANGE(" + args[2] + "," + args[4] + "," + args.includes("through") + ", function(" + args[0] + ", $LOOP){";},
each(args, key, value) {return "$EACH(" + args[0] + ",function(" + key + "," + value + ", $LOOP){";},
forelse(args, key, value) {
modeType = "for";
return "if($LENGTH(" + args[0] + ")){$EACH(" + args[0] + ",function(" + key + "," + value + ", $LOOP){";
},
},
rule = {
var (args) {return "var " + args.join(" ") + ";";},
alert(args) {return "alert(" + args.join(" ") + ");";},
console(args, type) {
type = type || "log";
return "console." + type + "(" + args.join(" ") + ");";
}
};
var endAction = function(type) { return (type == 2 ? "break;" : "") + "}" + (type == 1 ? ");" : "");},
regexCreate = function() {
var flag = arguments[0],
args = arrayConcat([], arraySlice(arrayFrom(arguments), 1));
return new RegExp(args.join(""), flag);
},
regexReplace = function() {
var line = arguments[0],
regexArray = arrayConcat([], arraySlice(arrayFrom(arguments), 1));
for (var i = 0, len = regexArray.length; i < len; i++) {
var regex = regexArray[i][0],
replace = regexArray[i][1];
line = line.replace(regex, replace);
}
return line;
}
function Preset(entry, data, async) {
this.entry = regexReplace.apply(null, arrayConcat([this._cleanHtml(entry)], _regex));
this.data = data || {};
this.async = async || false;
this._count = 0;
}
Preset.prototype._globalProps = function(line) {
return regexReplace(line, [regexCreate("g", "(@loop)", $Preset.quotes), "$LOOP"], [regexCreate("g", "(@self)", $Preset.quotes), "$SELF"], [regexCreate("g", "(@)", $Preset.quotes), "$HELPERS."], [regexCreate("g", "([.]+[\\w\\d-_]+)", $Preset.quotes), function(obj) {
return "['" + obj.substring(1) + "']";
}]);
}
Preset.prototype._lastCharAtIndex = function(string, index) {
if (string[index + 2] === $Preset.regex.end[0]) index = index + 1;
else return index;
return this._lastCharAtIndex(string, index);
}
Preset.prototype._cleanHtml = function(string) {
return regexReplace(string.trim(), [/</g, "<"], [/>/g, ">"], [/"/g, '"'], [/'/g, "'"], [/&/g, "&"], [/\n/g, ""], [/\s+/g, " "]);
}
Preset.prototype._arraySyntax = function(array) {
var match = null,
key = "_",
value = array.slice(2).join(" ");
if (match = $Preset.regex.array.exec(value)) {
key = match[1];
value = match[2];
}
return {
key,
value
};
}
Preset.prototype._logicSyntax = function(array) {
var self = this;
return arrayMap(arrayFilter(array, function(line) {
return line && line.length > 0;
}), function(line) {
line = self._globalProps(line);
if (line[0] !== "#") return "$TXT+=\"" + $Preset.devider + "\";$JSX.push(" + line + ");";
var args = arrayMap(line
.substring(1)
.split(" "),
function(arg) {
return arg.trim();
}),
act = args.shift(),
{
key,
value
} = self._arraySyntax(args);
switch (act) {
case "set":
return rule.var(args);
case "alert":
return rule.alert(args);
case "log":
case "info":
case "warn":
case "error":
case "debug":
return rule.console(args, act);
case "if":
return mode.if(args);
case "elif":
return mode.elif(args);
case "else":
return mode.else();
case "unless":
return mode.unless(args);
case "case":
return mode.case(args);
case "default":
return mode.default();
case "while":
return loop.while(args);
case "until":
return loop.untill(args);
case "switch":
return loop.switch(args);
case "range":
return loop.range(args);
case "each":
return loop.each(args, key, value);
case "forelse":
return loop.forelse(args, key, value);
case "/each":
case "/range":
return endAction(1);
case "/switch":
return endAction(2);
case "/if":
case "/while":
case "/unless":
case "/until":
case "/forelse":
return endAction();
case "code":
return args.join(" ");
}
});
}
Preset.prototype._bindToObject = function(object) {
for (var fn in object) {
if (Verify.fn(object[fn]))
object[fn] = object[fn].bind(this.data);
else this._bindToObject(object[fn]);
}
}
Preset.prototype._switchType = function(templateArray, isHtml) {
if (isHtml) {
var parts = templateArray.shift();
return parts.reduce(function(acc, part, i) {
return acc + part + (templateArray[i] || "");
}, "");
}
return (new(Function.prototype.bind.apply(Jsx, arrayConcat([null], templateArray)))).exec();
}
Preset.prototype._switchAsync = function(type) {
if (this.async) {
var self = this;
return new Promise(async function(resolve) {
self.entry = await self._insertHtml(self.entry);
resolve(self._switchType(self._compile(self.entry), type));
});
}
return this._switchType(this._compile(this.entry), type);
}
Preset.prototype._parse = function(template) {
var data = {
txt: [],
jsx: [],
},
state = $Preset.regex.start,
code,
index = -1;
while (template.length) {
index = template.indexOf(state);
if (index > -1) {
if (state === $Preset.regex.start) {
code = template.substring(0, index);
data.txt.push(code);
template = template.substring(index);
state = $Preset.regex.end;
} else {
index = this._lastCharAtIndex(template, index);
code = template.substring(state.length, index).trim();
data.jsx.push(code);
template = template.substring(index + state.length);
state = $Preset.regex.start;
}
} else {
template.trim().length && data[state == $Preset.regex.start ? "txt" : "jsx"].push(template);
template = "";
}
}
return data;
}
Preset.prototype._tokenize = function(string) {
var code = "",
data = this._parse(string);
data.jsx = this._logicSyntax(data.jsx);
for (var i = 0; i < data.txt.length; i++) {
var prejsx = data.jsx[i - 1],
curjsx = data.jsx[i],
curtxt = data.txt[i];
code += "$TXT+=`" + curtxt + "`;";
if (curjsx && prejsx && curjsx.startsWith("break;") && prejsx.startsWith("switch"))
code += curjsx.substring(6);
else
code += curjsx;
}
return code;
}
Preset.prototype._compile = function() {
this._bindToObject($HELPERS);
var str = this._tokenize(this.entry),
res = new Function(
"",
regexReplace("return function($SELF, $HELPERS) {" +
"var $TXT = '', $JSX = [], $EACH = function " + systemFns.each.toString() +
", $RANGE = function " + systemFns.range.toString() + ", $LENGTH = function " +
systemFns.length.toString() + "; with($SELF || {}) { try {" + str +
" } catch(e) { console.error(e); }}return [$TXT.split('" + $Preset.devider + "'), ...$JSX];}", [/\s+/g, " "])
)()(this.data, $HELPERS);
return res;
}
Preset.prototype._importHtml = async function(path) {
var newpath = "/views/" + path.replaceAll(".", "/") + ".dust.html";
if (!memory[path]) {
var r = await fetch(newpath);
memory[path] = r.status === 200 ? regexReplace.apply(null, arrayConcat([this._cleanHtml(await r.text())], _regex)) : "";
}
return memory[path];
}
Preset.prototype._insertHtml = async function(template) {
var matches = [],
self = this;
template = regexReplace(template, [$Preset.regex.imports, function(_, path) {
var hold = "___import__placement__code__" + (self._count++) + "___"
matches.push({
path,
hold
});
return hold;
}]);
for (var match of matches) {
var _template = await this._importHtml(match.path);
template = template.replace(match.hold, _template);
template = await this._insertHtml(template);
}
return template;
}
Preset.prototype.exec = function() {
return this._switchAsync();
}
Preset.prototype.string = function() {
return this._switchAsync(true);
}
return Preset;
})();
var Jsx = (function() {
function Jsx(entry) {
this.entry = entry;
this.props = arraySlice(arrayFrom(arguments), 1);
}
Jsx.prototype._props = function(line) {
var attrs = line.replace(/<(\/?)(\w+)\s*|\s*(\/?)>/g, "").split(new RegExp("\\s+" + $Preset.quotes));
attrs = arrayReduce(attrs, function(acc, attr, i) {
if (!attr.length) return acc;
return acc.push(attr.replace(/[`'"]/g, "")) && acc;
}, []);
for (var len = attrs.length, _props = {}, i = 0; i < len; i++) {
var [name, value = ""] = attrs[i].split("=");
_props[name] = value;
}
return _props;
}
Jsx.prototype._replace = function(attrs, props) {
var self = this,
_props = {};
for (var [name, value] of objectEntries(attrs)) {
if (name.includes("__code__joiner__")) {
var [_name, _value = ""] = name.replace(/__code__joiner__/g, function(_, s) {
return props[self._count++];
}).trim().split("=");
_name.length && (_props[_name] = _value.replace(/["'`](.*?)["'`]/g, "$1"));
} else if (value === "__code__joiner__") {
var j = this._count++;
_props[name] = Verify.str(props[j]) ? props[j].trim() : props[j];
} else {
_props[name] = value.replace(/__code__joiner__/g, function() {
return props[self._count++]
}).trim();
}
}
return _props;
}
Jsx.prototype._tokenize = function(template, props) {
var match,tokens = [],lastIndex = 0,regex = /<(\/?)([a-zA-Z0-9-]+)\s*.*?\s*(\/?)>/g;
while ((match = regex.exec(template))) {
var tagStart = match.index,tagEnd = regex.lastIndex,text = template.slice(lastIndex, tagStart),type = match[1].length ? "closing" : "opening",unique = match[1].length ? -1 : 1,single = match[3] ? true : false,name = match[2];
if (text.trim().length > 0) {tokens.push({type: 'texting',unique: 0,content: text}); }
tokens.push({type: type,unique: unique,tag: name.toLowerCase(),attributes: this._props(match[0], props),content: match[0] });
if (single) {tokens.push({type: "closing",tag: name.toLowerCase(),attributes: {},});}
lastIndex = tagEnd;
}
var remainingText = template.slice(lastIndex);
if (remainingText.trim().length > 0) {tokens.push({type: 'texting',unique: 0,content: remainingText}); }
return arrayConcat([{ type: "opening", tag: "stack", attributes: {},}], tokens, [{ type: "closing", tag: "stack", attributes: {},}]);
}
Jsx.prototype._parse = function(tokens, props) {
var self = this,
stack = [],
result = [];
for (let i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.type === "opening") {
var node = new Fiber({
type: token.tag,
props: Object.assign(this._replace(token.attributes, props), {children: [] })
});
if (stack.length === 0) {result.push(node);
} else { stack[stack.length - 1].props.children.push(node);}
stack.push(node);
} else if (token.type === "closing") {
var node = stack.pop();
if (node.type !== token.tag) {throw new Error(`Unexpected closing tag: ${token.tag}`);}
if (stack.length === 0) {return result[0].props.children;}
} else if (token.type === "texting") {
var node = new Fiber({
type: "text",
props: Object.assign({
textContent: token.content.replace(/__code__joiner__/g, function() {return props[self._count++];}).trim()
}, {children: []});
});
if (stack.length === 0) {result.push(node);
} else {stack[stack.length - 1].props.children.push(node);}
}
}
throw new Error("Unclosed tags");
}
Jsx.prototype.exec = function() {
this._count = 0;
var template = this.entry.join("__code__joiner__"),
tokens = this._tokenize(template),
tree = this._parse(tokens, this.props);
delete this._count;
return tree;
}
return Jsx;
})();
var Maker = (function() {
function Maker(container, tree) {
this.container = container;
this.instance = null;
this.tree = tree;
this.refs = {};
}
Maker.prototype._flat = function() {
var self = this;
arrayReduce(array, function(flat, toFlatten) {return arrayConcat(flat, Verify.arr(toFlatten) ? self._flat(toFlatten) : toFlatten);}, []);
}
Maker.prototype._create = function(fiber) {
var {type,props} = fiber, isTextElement = type === $Maker.text, dom = isTextElement ? document.createTextNode('') : type in $Maker.svgs ?
document.createElementNS("http://www.w3.org/2000/svg", type) : document.createElement(type), children = this._children({dom}, fiber);
this._props(dom, {}, props);
return new Instance({dom,fiber,children,});
}
Maker.prototype._reconcile = function(parentDom, oldNode, fiber) {
var node, self = this;
if (oldNode == null) {
if (Verify.arr(fiber)) {
node = arrayMap(fiber, function(e) { return self._create(e);});
arrayEach(node, function(e) {parentDom.appendChild(e.dom);});
} else node = this._create(fiber);
if (Verify.arr(node.dom)) {
var doms = this._flat(node.dom);
arrayEach(doms, function(e) {parentDom.appendChild(e);});
} else if (node.dom) {parentDom.appendChild(node.dom);}
return node;
} else if (fiber == null) {
if (Verify.arr(oldNode)) {
arrayEach(oldNode, function(e) {parentDom.removeChild(e.dom);});
} else if (oldNode.dom) {parentDom.removeChild(oldNode.dom);}
return null;
} else if (Verify.arr(oldNode) && Verify.arr(fiber)) {
return this._children({dom: parentDom,children: oldNode}, {props: { children: fiber}});
} else if ((oldNode.fiber || {}).type !== fiber.type) {
var node = this._create(fiber);
if (oldNode.dom || oldNode[0].dom) {
parentDom.replaceChild(node.dom, oldNode.dom || oldNode[0].dom);
}
return node;
} else if (typeof fiber.type === 'string') {
oldNode.children = this._children(oldNode, fiber);
this._props(oldNode.dom, oldNode.fiber.props, fiber.props);
oldNode.fiber = fiber;
return oldNode;
}
}
Maker.prototype._children = function(oldNode, fiber) {
var oldChildNodes = arrayFilter(oldNode.children || [], function(node) {return node != null;}),
childElements = arrayFilter(fiber.props.children || [], function(node) {return node != null;}),
length = Math.max(oldChildNodes.length, childElements.length),children = [];
for (var i = 0; i < length; i++) {
var childNode = this._reconcile(oldNode.dom, oldChildNodes[i], childElements[i]);
children.push(childNode);
}
return children;
}
Maker.prototype._props = function(node, oldProps, newProps) {
for (var prop in oldProps) {
if (!(prop in newProps)) {
if (prop === "route") {
node.onclick = undefined;
if (node.tagName === "A") node.removeAttribute("href");
continue;
}
var _ = node && node.tagName && node.tagName.toLowerCase(),
is_p = prop in node || prop.startsWith("$."),
is_s = _ in $Maker.svgs;
if (is_p && !is_s) node[prop.startsWith("$.") ? prop.substring(2) : prop] = undefined;
else node.removeAttribute(prop);
}
}
for (var event in node._listener) {
if (!(event in newProps)) {
var ev = event.split(":"),
eventName = ev[0].substring(1);
node.removeEventListener(eventName, node._listener[event]);
delete node._listener[event];
}
}
for (var prop in newProps) {
if (prop === "route") {
var link = newProps[prop];
node.onclick = function(e) {
e.preventDefault();
$Navigate(link);
}
if (node.tagName === "A") node.href = link;
continue;
}
if (prop === "children") continue;
if (prop === "style") {
if (Verify.obj(newProps[prop])) this._style(node, newProps[prop]);
else node.style = newProps[prop];
continue;
}
var _ = node && node.tagName && node.tagName.toLowerCase(),
is_p = prop in node || prop.startsWith("$."),
is_o = prop.split(".").length > 1,
is_e = prop.startsWith("@"),
is_s = _ in $Maker.svgs,
is_r = prop === "ref";
if (is_e) {
this._event(node, prop, newProps[prop]);
continue;
}
if (is_p && !is_s) {
node[prop.startsWith("$.") ? prop.substring(2) : prop] = newProps[prop];
continue;
}
if (is_o) {
var _ = arrayReduce(prop.split("."), function(a, e) {
return e.length ? a + `["${e}"]` : a;
}, "");
eval(`node${_} = newProps[prop]`);
continue;
}
if (is_r) {
if (this.refs[newProps[prop]])
Verify.arr(this.refs[newProps[prop]]) ?
this.refs[newProps[prop]].push(node) :
(this.refs[newProps[prop]] = [this.refs[newProps[prop]], node]);
else this.refs[newProps[prop]] = node;
continue;
}
node.setAttribute(prop, newProps[prop]);
}
}
Maker.prototype._style = function(node, props) {
for (var key in props) {
var prop = $HELPERS.kebabCase(key),
cur = props[key];
if (prop.startsWith("--")) {
document.documentElement.style.setProperty(prop, cur);
} else if (Verify.present(cur)) {
Verify.nbr(cur) && !(prop in $Maker.css) && (cur += "px");
node.style[prop] = cur;
}
}
}
Maker.prototype._event = function(node, name, callback) {
var self = this,
isSameFunction = false,
ev = name.split(":"),
eventName = ev[0].substring(1);
var fn = function(e) {
self._break(ev, e);
callback(e);
}
node._listener = node._listener || {};
if (node._listener[name] && node._listener[name].toString() === fn.toString()) {
isSameFunction = true;
return;
}
if (!isSameFunction) {
node._listener[name] = fn;
node.addEventListener(eventName, fn);
}
}
Maker.prototype._break = function(types, event) {
types.forEach(function(type) {
switch (type) {
case "prevent":
event.preventDefault();
case "propagation":
event.stopPropagation();
case "immediate":
event.stopImmediatePropagation();
}
});
}
Maker.prototype.exec = function(tree) {
this.refs = {};
this.tree = tree || this.tree;
this.instance = this._reconcile(this.container, this.instance, this.tree);
}
return Maker;
})();
var Class = (function() {
var memory = [],
defineSrc = function(self, object, fn, vr, ext) {
Object.defineProperty(object, "src", {
get() {
return this._src;
},
set(value) {
self._load = true;
if (!value) return null;
var _self = this;
value = value.replaceAll(".", "/");
this._src = "/views/" + value + ".dust." + ext;
self._run = false;
(async function() {
if (!memory[value]) {
var code = await fetch(_self._src);
memory[value] = await code.text();
}
self[vr] = memory[value];
await self[fn]();
self._load = false;
})();
},
});
},
stateUpdate = function(object, callback) {
const handler = {
get(target, property, receiver) {
const desc = Object.getOwnPropertyDescriptor(target, property);
if (desc && !desc.writable && !desc.configurable) {
return Reflect.get(target, property, receiver);
}
try {
return new Proxy(target[property], handler);
} catch (err) {
return Reflect.get(target, property, receiver);
}
},
defineProperty(target, property, descriptor) {
callback(property, descriptor.value);
return Reflect.defineProperty(target, property, descriptor);
},
deleteProperty(target, property) {
callback();
return Reflect.deleteProperty(target, property);
},
};
return new Proxy(object, handler);
}
function Class(props) {
this._run = true;
this.state = this._set(props.state);
this.style = document.querySelector(props.style);
this.container = document.querySelector(props.container);
if (this.container) {
var dust_code = document.getElementById(this.container.getAttribute("target"));
this.code = dust_code ? dust_code.textContent : "";
defineSrc(this, this.container, "_code", "code", "html");
if (dust_code) {
dust_code.remove();
this.container.removeAttribute("target");
}
if (this.container.hasAttribute("src")) {
this.container.src = this.container.getAttribute("src");
this.container.removeAttribute("src");
}
}
if (this.style) {
var dust_sass = document.getElementById(this.style.getAttribute("target"));
this.sass = dust_sass ? dust_sass.textContent : "";
defineSrc(this, this.style, "_sass", "sass", "css");
if (dust_sass) {
dust_sass.remove();
this.style.removeAttribute("target");
}
if (this.style.hasAttribute("src")) {
this.style.src = this.style.getAttribute("src");
this.style.removeAttribute("src");
}
}
if (this._run) {
this._sass();
this._code();
}
var self = this;
Class.Helper("state", function(state) {
return self.setState(state);
});
execArrayFns(Class, "START_FNS");
}
Class.prototype.setState = function(state) {
this.state = this._set(Object.assign({}, this.state, state));
this._sass();
this._code();
}
Class.prototype._set = function(state) {
var self = this,
_state = {};
arrayEach(objectKeys(state), function(key) {
if (Verify.fn(state[key])) _state[key] = state[key].bind(self);
else _state[key] = state[key];
});
return stateUpdate(_state, function() {
setTimeout(function() {
self._sass();
self._code();
}, 0);
});
}
Class.prototype._sass = function() {
if (this.style) {
const sass = new Preset(this.sass, this.state).string();
const code = new Sass(sass).string();
this.style.textContent = code;
}
}
Class.prototype._code = async function() {
const tree = await new Preset(this.code, this.state, true).exec();
if (tree.length) {
if (!this.maker) this.maker = new Maker(this.container);
this.maker.exec(tree);
execArrayFns(Class, "UPDATE_FNS");
}
}
Class.Helper = function(name, fn) {
if (arrayConcat(["loop", "self"], objectKeys($HELPERS)).includes(name)) throw new Error("reserved helper name (" + name + ")");
$HELPERS[name] = fn;
}
Class.init = function(fn) {document.addEventListener("DOMContentLoaded", fn);}
Class.start = function(fn) {this.START_FNS.push(fn);}
Class.update = function(fn) {this.UPDATE_FNS.push(fn);}
objectProperty(Class.prototype, "refs", {
get() {return this.maker && this.maker.refs || {};}
});
Class.START_FNS = [];
Class.UPDATE_FNS = [];
return Class;
})();
var Router = (function() {
var Load = { in: function() {
document.body.insertAdjacentHTML("afterbegin", "<div class=\"__dust__loader__\" " +
"style=\"position:fixed;inset:0;background:#fff;display:flex;align-items:center;" +
"justify-content:center;font-size:24px;font-weight:600;z-index:9999;\">Loading...</div>");
document.body.style.overflow = "hidden";
},
out: function() {
document.body.style.overflow = "";
arrayFrom(document.querySelectorAll(".__dust__loader__"), function(load) {
load.remove();
});
}
};
function Router(props) {
var self = this,
container = props.container,
style = props.style,
routes = props.routes || [],
state = props.state || {},
match = arrayFind(routes, function(p) {return p.path === "/404";}) || {name: "404",path: "/404",view: "404",};
this.routes = $Router.map(arrayConcat([match], arrayFilter(routes, function(e) {return e.path != "/404"; })));
this.hash = props.hash || false;
Class.Helper("navigate", function(path) { return self.navigate(path);});
Class.Helper("route", function(name, object) { return self.route(name, object);});
Class.Helper("match", function(name) { return self.match.route.name === name;});
Class.Helper("params", function() { return self.params();});
Class.Helper("queries", function() { return self.queries();});
this.dust = new Class({container: container,style: style,state: state});
if (this.hash && !location.hash) location.hash = "#/";
window[this.hash ? "onhashchange" : "onpopstate"] = function() {
self.navigate(self.hash ? location.hash.substring(1) : history.state.path);
};
this.navigate(this.hash ? location.hash.substring(1) : location.pathname);
$Navigate = this.navigate.bind(this);
execArrayFns(Router, "LOAD_FNS");
}
Router.prototype.navigate = function(path) {
if (!history.state || history.state.path !== path) {
history.pushState({path: path,}, document.title,(this.hash ? "#" : "") + path);
}
this.execute();
}
Router.prototype.route = function(name, object) {
var _path = null,route = arrayFind(this.routes, function(r) {return r.name === name;});
if (route) {
var path = route.path,
queries = [],
regex = /{(\w+):\w+}/g;
if (!Verify.obj(object)) {
_path = regex.test(path) ? path.replace(regex, object) : path + (object ? ("?" + object) : "");
} else {
_path = path.replace(regex, function(_, s) {
var value = object[s] || "";
delete object[s];
return value;
});
for (var key in object) queries.push(key + "=" + object[key]);
_path = _path + (queries.length ? "?" + queries.join("&") : "");
}
}
return _path;
}
Router.prototype.params = function() {return $Router.params(this.match);}
Router.prototype.queries = function() {
var params = new URLSearchParams(this.hash ? location.hash.split("?")[1] || "" : location.search),
obj = {};
arrayEach(arrayFrom(new Set(params.keys())), function(key) {
obj[key] = params.getAll(key).length > 1 ? params.getAll(key) : params.get(key);
});
return obj;
}
Router.prototype.execute = async function() {
Load.in();
this.match = $Router.match(this);
var self = this,
route = this.match.route,
fnCall = function() {
self.dust._load ? requestAnimationFrame(fnCall) :
route.fn.call(self) && execArrayFns(Router, "NEXT_FNS");
}
guard = route.guard.bind(this);
if (Verify.fn(guard) && Verify.present(await guard())) {
execArrayFns(Router, "PREV_FNS");
this.dust.container.src = route.view;
fnCall()
}
Load.out();
}
Router.prototype.setState = function(state) {this.dust.setState(state);}
Router.load = function(fn) {this.LOAD_FNS.push(fn);}
Router.prev = function(fn) {this.PREV_FNS.push(fn);}
Router.next = function(fn) {this.NEXT_FNS.push(fn);}
objectProperty(Router.prototype, "refs", {
get() { return this.dust && this.dust.refs || {};}
});
objectProperty(Router.prototype, "state", {
get() {return this.dust && this.dust.state || {}; }
});
Router.Helper = Class.Helper;
Router.LOAD_FNS = [];
Router.PREV_FNS = [];
Router.NEXT_FNS = [];
return Router;
})();
Class.Sass = Sass;
Class.Preset = Preset;
Class.Jsx = Jsx;
Class.Maker = Maker;
Class.Router = Router;
return Class;
})();