feat.js
Version:
The Feat.JS Framework; Easy, powerful and fast.
243 lines (186 loc) • 10.3 kB
JavaScript
module.exports = (html, cmo, options, blb) => {
//go thru each space
let content = html;
let components = cmo;
let bottomScript = `<script> `;
//Config
let config = {
enableCustomAttributes: true, //Disabling this wil disable custom attributes, but make your website blazin' fast
// Custom attributes are the ones like feat:onclick, feat:increment, etc. We suggest keeping it on for a better expierience.
// However, if you want to disable it, just set this to false. (do this if you don't use custom attributes)
// With the SBAdmin 2 theme we tested the parser on the index html file (This test was done with 5 iterations and no logic)
// Enabled: 127ms -> Disabled: 20ms
// Anyway default is true, so you don't need to change this, but if you want that juicy performance, just set it to false.
}
//get amount of elements in components
if (html?.length > 0) {
let cpy = Object.keys(components)
for (let m = 0; m < cpy.length; m++) {
let key = cpy[m];
let obj = components[key];
//replace self-closing tags with open and close tags
if (obj.selfClosing) {
content = content.replace(new RegExp(`<${obj.name}/>`, 'g'), `<${obj.name}></${obj.name}>`);
content = content.replace(new RegExp(`<${obj.name} />`, 'g'), `<${obj.name}></${obj.name}>`);
}
//find all tags with obj.name
let tags = content.match(new RegExp(`<${obj.tag}[^>]*>`, 'g'));
let sp = content.split("<");
for (let i = 0; i < sp.length; i++) {
let tag = sp[i];
let tagName = tag.split(">")[0];
let tagAttrs = {};
let spl = tag.split(" ");
let abt = spl.join(" ").split("\"")
let joinContent = () => {
content = sp.join("<");
}
let setAttribute = (attr, val) => {
tagAttrs[attr] = val;
//reparse tags
let attrString = "";
for (let attr in tagAttrs) {
attrString += ` ${attr}="${tagAttrs[attr]}"`;
}
//make sure to include the tag contents
let tagContent = tag.split(">");
//remove everything before index 1 from tagContent
tagContent.splice(0, tagContent.length - 1);
tag = `${tagName.split(" ")[0]}${attrString}>${tagContent[0]}`;
sp[i] = tag;
}
let deleteAttribute = (attr) => {
//remove attr from tagAttrs without delete
delete tagAttrs[attr];
}
let parseAttributes = () => {
for (let o = 0; o < abt.length; o++) {
let attr = abt[o];
if (o % 2 === 0) {
//remove equals sign
let split = attr.split(" ");
latest = split[split.length - 1].replace("=", "").trim();
} else {
//remove quotes
tagAttrs[latest] = attr;
}
}
let keys = Object.keys(tagAttrs);
for (let q = 0; q < keys.length; q++) {
let attr = keys[q];
let val = tagAttrs[attr];
//check if attr stats with feat:
if (attr.startsWith("feat:")) {
//split the attribute
let split = attr.split("feat:")[1];
switch (split) {
case "onclick":
deleteAttribute("feat:onclick");
setAttribute("onclick", `Feat.emit('${val}', this)`);
break;
case "increment":
deleteAttribute("feat:increment");
setAttribute("onclick", `if (!Feat.getState('${val}')) { Feat.setState('${val}', 0) } Feat.setState('${val}', Feat.getState('${val}') + 1)`);
break;
case "decrement":
deleteAttribute("feat:decrement");
setAttribute("onclick", `if (!Feat.getState('${val}')) { Feat.setState('${val}', 0) } Feat.setState('${val}', Feat.getState('${val}') - 1)`);
break;
case "toggle":
deleteAttribute("feat:toggle");
setAttribute("onclick", `Feat.setState('${val}', !Feat.getState('${val}'))`);
break;
case "refresh":
deleteAttribute("feat:refresh");
setAttribute("onclick", `Feat.setState('${val}', Feat.getState('${val}'))`);
break;
case "reset":
deleteAttribute("feat:reset");
setAttribute("onclick", `Feat.setState('${val}', Feat.defaultStates['${val}'])`);
break;
case "bind":
deleteAttribute("feat:bind");
if (tagAttrs["id"]) {
bottomScript += `Feat.useState('${tagAttrs[id]}', '${val}');`;
} else {
// generate 16 char long id without numbers
let id = Math.random().toString(36).substr(2, 16).replace(/[^a-z]+/g, '');
//set id
setAttribute("id", id);
//use state
bottomScript += `Feat.useState('#${id}', '${val}');`;
}
break;
}
}
}
//reconstruct tag
joinContent();
}
if (config.enableCustomAttributes) parseAttributes();
if (components[tagName.split(" ")[0].toLowerCase()]) {
let comp = components[tagName.split(" ")[0].toLowerCase()];
//get html attributes from tagName
if (!config.enableCustomAttributes) parseAttributes();
//remove first el
spl.shift();
let start = i;
let end = i;
let contentt = [];
let depth = 0;
let isset = false;
for (let x = start; x < sp.length; x++) {
let t = sp[x];
let tagName = t.split(" ")[0];
if (tagName.toLowerCase().startsWith(comp.name.toLowerCase())) {
depth++;
}
if (tagName.toLowerCase().startsWith(`/${comp.name.toLowerCase()}>`)) {
depth--;
}
let improved = t;
// remove \r and \n
improved = improved.replace(/\r/g, "");
improved = improved.replace(/\n/g, "");
improved = improved.trim();
contentt.push("<" + improved);
if (depth === 0) {
end = x;
break;
}
}
//remove first and last el
contentt[0] = contentt[0].split(">")
//set contentt[0] to last element contentt[0]
contentt[0] = contentt[0][contentt[0].length - 1];
contentt[0].trim();
contentt[0] = "<a>" + contentt[0] + "</a>";
contentt.pop();
function registerComponent(data, src) {
components[data.name.toLowerCase()] = data;
}
//console.log(contentt);
let returnVal = comp.code(tagAttrs, contentt.join("\n"), options, blb, registerComponent);
if (returnVal) {
//remove first char from returnVal
returnVal = returnVal.trim();
returnVal = returnVal.substring(1);
//remove start to end from sp
sp.splice(start, end - start + 1, returnVal);
joinContent();
} else {
//remove start to end from sp
sp.splice(start, end - start + 1, "Feat></Feat>");
joinContent();
}
}
}
}
return {
content: content + bottomScript + "</script>",
components: components
};
} else {
return `<p>Welcome to FeatJS, Please add html to your file to get started</p>`;
}
}