-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjsx.js
105 lines (98 loc) · 3.23 KB
/
jsx.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
const jsxState = { attrs: [], domRoots: [] };
function formatAttr(attr) {
if (!Array.isArray(attr)) return [attr.replace(/^\$/, "").replace(/_/, "-"), true];
if (attr[1] === false) return [];
const [k, v] = attr;
const keyMap = { htmlFor: "for", className: "class" };
const normalizedKey = k in keyMap ? keyMap[k] : k.replace(/_/, "-")
return [normalizedKey, v];
}
function formatTag(rawAttrs) {
const [tagName, ...attrs] = rawAttrs;
const normalizedTagName = tagName.replace(/_/, "-");
if (normalizedTagName.startsWith("$")) {
if (attrs.length > 0)
throw new Error("JSX closing tag cannot have attributes");
return { type: "closing", tagName: normalizedTagName.replace(/^\$/, "") };
} else if (attrs.at(-1) === "$") {
attrs.pop();
return { type: "self-close", tagName: normalizedTagName, attrs: attrs.map(formatAttr) };
} else {
return { type: "opening", tagName: normalizedTagName, attrs: attrs.map(formatAttr) };
}
}
function appendChild(stack, element) {
if (stack.length > 0) stack[0].appendChild(element);
else jsxState.domRoots.push(element);
}
function createJSX(stack) {
function jsx(strs, ...args) {
const textNode = args.reduce(
(acc, cur, idx) => acc + String(cur) + strs[idx + 1],
strs[0],
);
const element = document.createTextNode(textNode);
appendChild(stack, element);
return createJSX(stack);
}
jsxState.attrs = [];
jsx.stack = () => stack;
jsx.toDOM = () => {
if (stack.length)
throw new Error(`Unclosed tags: ${stack.map(({tagName}) => tagName.toLowerCase()).join(", ")}`);
return jsxState.domRoots;
}
return new Proxy(jsx, jsxTrap);
}
const jsxTrap = {
has() { return true; },
get(target, prop) {
if (prop in target || typeof prop === "symbol") return target[prop];
const {type, tagName, attrs} = formatTag(jsxState.attrs);
const stack = target.stack();
if (type === "self-close") {
const element = document.createElement(tagName);
attrs.forEach(([k, v]) => k && element.setAttribute(k, v));
appendChild(stack, element);
} else if (type === "closing") {
if (stack[0].tagName.toLowerCase() !== tagName)
throw new Error(`Tag name mismatch: ${stack[0].tagName.toLowerCase()} vs. ${tagName}`);
const element = stack.shift();
appendChild(stack, element);
} else {
const element = document.createElement(tagName);
attrs.forEach(([k, v]) => k && element.setAttribute(k, v));
stack.unshift(element);
}
return createJSX(stack);
},
};
globalThis.universalNS = new Proxy({}, {
has(target, prop) {
return !(prop in globalThis);
},
get(target, prop) {
if (typeof prop === "symbol") return target[prop];
jsxState.attrs.push(prop);
return [prop];
},
set(target, prop, value) {
if (typeof prop === "symbol") target[prop] = value;
else if (prop in globalThis) globalThis[prop] = value;
else {
if (Array.isArray(value)) {
jsxState.attrs.pop();
jsxState.attrs.push([prop, value[0]]);
} else {
jsxState.attrs.push([prop, value]);
}
}
return true;
}
});
globalThis.jsx = createJSX([]);
globalThis.resetJSX = () => {
globalThis.jsx = createJSX([]);
jsxState.attrs = [];
jsxState.domRoots = [];
}