Framework-free JSX-like syntax in pure JS using XHTM and native DOM creation.
Tätä skriptiä ei tulisi asentaa suoraan. Se on kirjasto muita skriptejä varten sisällytettäväksi metadirektiivillä // @require https://update.greasyfork.org/scripts/591770/1907698/Vanilla%20XHTM.js.
// ==UserScript== // @name Vanilla XHTM // @namespace https://greasyfork.org/users/1635096-paesss // @version 1.8.0 // @author Paesss // @description Framework-free JSX-like syntax in pure JS using XHTM and native DOM creation. // @license MIT // @match * // ==/UserScript== /* ======================================================================== * Bundled Library: XHTM ([email protected]) * Copyright (c) 2019 Dmitry Yv. * * MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * ======================================================================== */ (function () { "use strict"; var FIELD = ""; var QUOTES = ""; var CACHE = new WeakMap(); function htm(statics) { let tree = CACHE.get(statics); if (!tree) CACHE.set(statics, (tree = parse(statics))); let nodes = tree.map((node) => build(this, node, arguments)); return nodes.length < 2 ? nodes[0] : nodes; } var build = (h, node, args) => { if (typeof node === "number") return args[node]; if (typeof node !== "object") return node; let props = null; if (node.p) { props = {}; for (let e of node.p) e.length < 2 ? Object.assign(props, args[e[0]]) : (props[part(e[0], args)] = part(e[1], args)); } return h(part(node.t, args), props, ...node.c.map((c) => build(h, c, args))); }; var part = (v, args) => typeof v === "number" ? args[v] : Array.isArray(v) ? v .map( (x) => ((x = typeof x === "number" ? args[x] : x), x == null || x === false ? "" : x), ) .join("") : v; var parse = (statics) => { let prev = 0, current = [null], field = 0, args, name, value, quotes = [], quote = 0, last, level = 0, pre = false; const evaluate = (str, parts = [], raw) => { let i = 0; str = !raw && str === QUOTES ? quotes[quote++].slice(1, -1) : str.replace(/\ue001/g, (m) => quotes[quote++]); if (!str) return str; str.replace(/\ue000/g, (match, idx) => { if (idx) parts.push(str.slice(i, idx)); i = idx + 1; return parts.push(++field); }); if (i < str.length) parts.push(str.slice(i)); return parts.length > 1 ? parts : parts[0]; }; const up = () => { (args = current), (current = args[0]), (last = args[1]); current.push({ t: last, p: args[2], c: args.slice(3), }); if (pre === level--) pre = false; }; let str = statics.join(FIELD); if (str.indexOf("<!") >= 0) str = str.replace(/<!--[^]*?-->/g, "").replace(/<!\[CDATA\[[^]*\]\]>/g, ""); str = str.replace(/('|")[^\1]*?\1/g, (match) => (quotes.push(match), QUOTES)); str.replace(/(?:^|>)((?:[^<]|<[^\w\ue000\/?!>])*)(?:$|<)/g, (match, text, idx, str) => { let tag, close; if (idx) str .slice(prev, idx) .replace(/(\S)\/$/, "$1 /") .split(/\s+/) .map((part, i) => { if (part[0] === "/") { part = part.slice(1); if (EMPTY[part]) return; close = tag || part || 1; } else if (!i) { tag = evaluate(part); if (typeof tag === "string") { tag = tag.toLowerCase(); while (CLOSE[current[1] + tag]) up(); } current = [current, tag, null]; level++; if (!pre && PRE[tag]) pre = level; if (EMPTY[tag]) close = tag; } else if (part) { let props = current[2] || (current[2] = []); if (part.slice(0, 3) === "...") props.push([++field]); else { [name, value] = part.split("="); props.push([evaluate(name), value ? evaluate(value) : true]); } } }); if (close) { if (!current[0]) err(`Wrong close tag \`${close}\``); up(); while (last !== close && CLOSE[last]) up(); } prev = idx + match.length; if (!pre) text = text.replace(/\s*\n\s*/g, "").replace(/\s+/g, " "); if (text) evaluate(((last = 0), text), current, true); }); if (current[0] && CLOSE[current[1]]) up(); if (level) err(`Unclosed \`${current[1]}\`.`); return current.slice(1); }; var err = (msg) => { throw SyntaxError(msg); }; var EMPTY = (htm.empty = {}); var CLOSE = (htm.close = {}); var PRE = (htm.pre = {}); "area base basefont bgsound br col command embed frame hr image img input keygen link meta param source track wbr ! !doctype ? ?xml" .split(" ") .map((v) => (htm.empty[v] = true)); var close = { li: "", dt: "dd", dd: "dt", p: "address article aside blockquote details div dl fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hgroup hr main menu nav ol pre section table", rt: "rp", rp: "rt", optgroup: "", option: "optgroup", caption: "tbody thead tfoot tr colgroup", colgroup: "thead tbody tfoot tr caption", thead: "tbody tfoot caption", tbody: "tfoot caption", tfoot: "caption", tr: "tbody tfoot", td: "th tr", th: "td tr tbody", }; for (let tag in close) for (let closer of [...close[tag].split(" "), tag]) htm.close[tag] = htm.close[tag + closer] = true; "pre textarea".split(" ").map((v) => (htm.pre[v] = true)); var xhtm_default = htm; var SVG_NS = "http://www.w3.org/2000/svg"; var SVG_TAGS = new Set([ "svg", "path", "circle", "rect", "line", "polyline", "polygon", "ellipse", "g", "use", "text", "tspan", "symbol", "defs", "clipPath", "mask", "pattern", "marker", "foreignObject", ]); function appendChildren(parent, children) { for (let i = 0; i < children.length; i++) { const child = children[i]; if (child == null || typeof child === "boolean") continue; if (Array.isArray(child)) appendChildren(parent, child); else if (child instanceof Node) parent.appendChild(child); else parent.appendChild(document.createTextNode(String(child))); } } function h(tag, props, ...children) { if (typeof tag === "function") { const normalizedProps = Object.assign({}, props); normalizedProps.children = children.length === 0 ? void 0 : children.length === 1 ? children[0] : children; return tag(normalizedProps); } if (!tag) { const fragment = document.createDocumentFragment(); appendChildren(fragment, children); return fragment; } const isSvg = typeof tag === "string" && SVG_TAGS.has(tag); const el = isSvg ? document.createElementNS(SVG_NS, tag) : document.createElement(tag); if (props) for (const key in props) { const val = props[key]; if (val === void 0 || val === null) continue; if (key === "ref") { if (typeof val === "function") val(el); else if (val && typeof val === "object") val.current = el; continue; } if (key.charCodeAt(0) === 46) { el[key.slice(1)] = val; continue; } if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110) { const eventName = key.slice(2).toLowerCase(); el.addEventListener(eventName, val); continue; } let attrName = key; if (key === "className") attrName = "class"; else if (key === "htmlFor") attrName = "for"; if (key === "style") { const styledEl = el; if (typeof val === "object" && val !== null) Object.assign(styledEl.style, val); else styledEl.style.cssText = String(val); continue; } if (key === "dataset" && typeof val === "object") { Object.assign(el.dataset, val); continue; } if (typeof val === "boolean") { if (val) el.setAttribute(attrName, ""); else el.removeAttribute(attrName); continue; } if (!isSvg && attrName in el && attrName !== "list" && attrName !== "form") try { el[attrName] = val; } catch (_) { el.setAttribute(attrName, String(val)); } else el.setAttribute(attrName, String(val)); } appendChildren(el, children); return el; } ((global) => { global.html = xhtm_default.bind(h); })(typeof globalThis !== "undefined" ? globalThis : window); })();