Framework-free JSX-like syntax in pure JS using XHTM and native DOM creation.
ეს სკრიპტი არ უნდა იყოს პირდაპირ დაინსტალირებული. ეს ბიბლიოთეკაა, სხვა სკრიპტებისთვის უნდა ჩართეთ მეტა-დირექტივაში // @require https://update.greasyfork.org/scripts/591770/1909428/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 BOOLEAN_PROPS = new Set([ "allowFullscreen", "async", "autofocus", "autoplay", "checked", "controls", "default", "defer", "disabled", "formNoValidate", "hidden", "inert", "loop", "multiple", "muted", "noValidate", "open", "readOnly", "required", "reversed", "selected" ]); var SVG_TAGS = new Map([ ["animate", "animate"], ["animatemotion", "animateMotion"], ["animatetransform", "animateTransform"], ["circle", "circle"], ["clippath", "clipPath"], ["defs", "defs"], ["desc", "desc"], ["ellipse", "ellipse"], ["feblend", "feBlend"], ["fecolormatrix", "feColorMatrix"], ["fecomponenttransfer", "feComponentTransfer"], ["fecomposite", "feComposite"], ["feconvolvematrix", "feConvolveMatrix"], ["fediffuselighting", "feDiffuseLighting"], ["fedisplacementmap", "feDisplacementMap"], ["fedistantlight", "feDistantLight"], ["fedropshadow", "feDropShadow"], ["filter", "filter"], ["feflood", "feFlood"], ["fefunca", "feFuncA"], ["fefuncb", "feFuncB"], ["fefuncg", "feFuncG"], ["fefuncr", "feFuncR"], ["fegaussianblur", "feGaussianBlur"], ["feimage", "feImage"], ["femerge", "feMerge"], ["femergenode", "feMergeNode"], ["femorphology", "feMorphology"], ["feoffset", "feOffset"], ["fepointlight", "fePointLight"], ["fespecularlighting", "feSpecularLighting"], ["fespotlight", "feSpotLight"], ["fetile", "feTile"], ["feturbulence", "feTurbulence"], ["foreignobject", "foreignObject"], ["g", "g"], ["glyph", "glyph"], ["glyphref", "glyphRef"], ["hatch", "hatch"], ["hatchpath", "hatchpath"], ["hkern", "hkern"], ["image", "image"], ["line", "line"], ["lineargradient", "linearGradient"], ["marker", "marker"], ["mask", "mask"], ["metadata", "metadata"], ["mesh", "mesh"], ["meshgradient", "meshgradient"], ["meshpatch", "meshpatch"], ["meshrow", "meshrow"], ["missing-glyph", "missing-glyph"], ["mpath", "mpath"], ["path", "path"], ["pattern", "pattern"], ["polygon", "polygon"], ["polyline", "polyline"], ["radialgradient", "radialGradient"], ["rect", "rect"], ["set", "set"], ["solidcolor", "solidcolor"], ["stop", "stop"], ["svg", "svg"], ["symbol", "symbol"], ["text", "text"], ["textpath", "textPath"], ["tspan", "tspan"], ["use", "use"], ["view", "view"], ["vkern", "vkern"] ]); 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 (isNode(child)) parent.appendChild(child); else parent.appendChild(document.createTextNode(String(child))); } } function isNode(value) { return value !== null && typeof value === "object" && "nodeType" in value && typeof value.nodeType === "number" && "nodeName" in value && typeof value.nodeName === "string"; } function setDataset(el, dataset) { if ("dataset" in el && el.dataset) { Object.assign(el.dataset, dataset); return; } for (const key in dataset) { const value = dataset[key]; const attribute = `data-${key.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`; if (value == null) el.removeAttribute(attribute); else el.setAttribute(attribute, String(value)); } } 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 svgTag = typeof tag === "string" ? SVG_TAGS.get(tag.toLowerCase()) : void 0; const isSvg = svgTag !== void 0; const el = isSvg ? document.createElementNS(SVG_NS, svgTag) : document.createElement(tag); if (props) for (const key in props) { const val = props[key]; if (key === "ref") { if (typeof val === "function") val(el); else if (val && typeof val === "object") val.current = el; continue; } if (key === "children") continue; if (key.charCodeAt(0) === 46) { el[key.slice(1)] = val; continue; } if (val === void 0 || val === null) continue; if (key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110) { const eventName = key.slice(2).toLowerCase(); if (typeof val === "function" || typeof val === "object" && val !== null) 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") { setDataset(el, val); continue; } if (typeof val === "boolean") { if (!isSvg && BOOLEAN_PROPS.has(key)) el[key] = val; if (val) el.setAttribute(attrName, ""); else el.removeAttribute(attrName); continue; } el.setAttribute(attrName, String(val)); } appendChildren(el, children); return el; } var boundHtm = xhtm_default.bind(h); function html(statics, ...args) { const result = boundHtm(statics, ...args); if (Array.isArray(result)) { const fragment = document.createDocumentFragment(); appendChildren(fragment, result); return fragment; } if (!isNode(result)) return document.createTextNode(String(result ?? "")); return result; } (typeof globalThis !== "undefined" ? globalThis : window).html = html; })();