easy-jsx-html-engine
Version:
Dead simple HTML engine using JSX syntax.
97 lines • 3.36 kB
JavaScript
import clsx from "clsx";
import { escapeHTML, isPromise, normalizeChildren } from "./util";
import { isVoidElem } from "./intrinsics";
class Element {
name;
attrs;
constructor(name, attrs) {
this.name = name;
this.attrs = attrs;
}
toHTML() {
const { children, ...attrs } = this.attrs;
const attrEntries = Object.entries(attrs);
return `<${this.name}${attrEntries.length
? " " +
attrEntries
.filter(([_, value]) => value || value === 0)
.map(([key, value]) => value === true
? key
: `${key}="${typeof value === "string" ? value.replace(/"/g, '\\"') : value}"`)
.join(" ")
: ""}${isVoidElem(this.name) ? "/>" : ">" + children + `</${this.name}>`}`;
}
}
export function childrenToString(children) {
return children
.filter(Boolean)
.map((child) => child?.toHTML
? child.toHTML()
: typeof child === "string"
? escapeHTML(child)
: child.toString())
.join("");
}
export function createElement(name, attrs = {}, ...children) {
if (typeof name === "function") {
return name({
...attrs,
children: !children?.length
? undefined
: children.length === 1
? children[0]
: children,
});
}
// remove children from attrs to avoid extra work if it was erroneously passed in as an attribute
const { children: _, ...attrsWithoutChildren } = attrs;
const normalizedChildren = normalizeChildren(children);
const normalizedAttrs = normalizeAttributes(attrsWithoutChildren);
if (isPromise(normalizedChildren) || isPromise(normalizedAttrs)) {
return Promise.all([normalizedAttrs, normalizedChildren]).then(([attrs, children]) => createElement(name, attrs, children));
}
const { class: __, ...other } = attrsWithoutChildren;
const escapedAttrs = Object.fromEntries(Object.entries(other).map(([key, value]) => [
key,
typeof value === "string" ? escapeHTML(value) : value,
]));
if ("class" in attrs) {
escapedAttrs.class = clsx(attrs.class);
}
return new Element(name, {
...escapedAttrs,
children: childrenToString(normalizedChildren),
});
}
function normalizeAttributes(attrs) {
const entries = Object.entries(attrs);
if (entries.some(([, value]) => isPromise(value))) {
return Promise.all(entries.map(([key, value]) => {
if (isPromise(value)) {
return value.then((value) => [key, value]);
}
return [key, value];
})).then((entries) => Object.fromEntries(entries));
}
return attrs;
}
class NoEscape {
str;
constructor(str) {
this.str = str;
}
toHTML() {
return this.str;
}
}
export function dangerouslyPreventEscaping(str) {
return new NoEscape(str);
}
export function Fragment(props) {
const children = normalizeChildren(props.children);
if (isPromise(children)) {
return children.then((children) => createElement(Fragment, {}, children));
}
return dangerouslyPreventEscaping(childrenToString(children));
}
//# sourceMappingURL=create-element.js.map