@solidjs/html 2.0.0-experimental.9 → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -5
- package/dist/html.cjs +7 -574
- package/dist/html.js +8 -575
- package/package.json +22 -13
- package/types/index.d.ts +3 -0
- package/types/tagged-jsx.d.ts +12558 -0
- package/types-cjs/index.d.cts +3 -0
- package/types-cjs/package.json +3 -0
- package/types-cjs/tagged-jsx.d.cts +12558 -0
package/README.md
CHANGED
|
@@ -2,26 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
This sub module provides a Tagged Template Literal `html` method for Solid. This is useful to use Solid in non-compiled environments. This method can be used as replacement for JSX.
|
|
4
4
|
|
|
5
|
-
`html` uses `${}` to escape into JavaScript expressions. Components are closed with
|
|
5
|
+
`html` uses `${}` to escape into JavaScript expressions. Components are closed with `<//>`.
|
|
6
|
+
|
|
7
|
+
Since Solid 2.0, `html` is backed by [`@dom-expressions/tagged-jsx`](https://www.npmjs.com/package/@dom-expressions/tagged-jsx), an AST-based tagged-template runtime. Templates are parsed at runtime (no `new Function` / `eval`, so it is CSP-safe) and reactive bindings are installed against the resulting DOM.
|
|
8
|
+
|
|
9
|
+
For editor support, the [Tagged JSX Tools VS Code extension](https://marketplace.visualstudio.com/items?itemName=DanielRKling.tagged-jsx-vscode) provides syntax highlighting, formatting, conversion commands, and TypeScript diagnostics for JSX inside tagged template literals.
|
|
6
10
|
|
|
7
11
|
```js
|
|
8
12
|
// create an element with a title attribute
|
|
9
13
|
html`<button title="My button">Click Me</button>`
|
|
10
14
|
|
|
11
|
-
// create a component with a title prop
|
|
15
|
+
// create a component with a title prop (inline expression hole)
|
|
12
16
|
html`<${Button} title="My button">Click me<//>`
|
|
13
17
|
|
|
14
18
|
// create an element with dynamic attribute and spread
|
|
15
19
|
html`<div title=${() => selectedClass()} ...${props} />`
|
|
16
20
|
```
|
|
17
21
|
|
|
18
|
-
Using `html` is slightly less efficient than JSX
|
|
22
|
+
Using `html` is slightly less efficient than JSX, requires a larger runtime that isn't treeshakeable, and cannot leverage expression analysis, so it requires manual wrapping of expressions and has a few other caveats (see below).
|
|
19
23
|
|
|
20
24
|
## Example
|
|
21
25
|
|
|
22
26
|
```js
|
|
23
|
-
import { render } from "
|
|
24
|
-
import html from "
|
|
27
|
+
import { render } from "@solidjs/web";
|
|
28
|
+
import html from "@solidjs/html";
|
|
25
29
|
import { createSignal } from "solid-js";
|
|
26
30
|
|
|
27
31
|
function Button(props) {
|
|
@@ -38,6 +42,40 @@ function Counter() {
|
|
|
38
42
|
render(Counter, document.getElementById("app"));
|
|
39
43
|
```
|
|
40
44
|
|
|
45
|
+
## Component registry
|
|
46
|
+
|
|
47
|
+
Inline expression holes (`<${Component} />`) work without any setup, but the Tagged JSX runtime also supports a named component registry. `html.define({ ... })` returns a new tag with the supplied components merged into the registry; capitalized tag names in the template are then looked up by name. The original `html` tag is unchanged.
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
import html from "@solidjs/html";
|
|
51
|
+
import { For, Show } from "solid-js";
|
|
52
|
+
|
|
53
|
+
const tpl = html.define({ For, Show });
|
|
54
|
+
|
|
55
|
+
function List(props) {
|
|
56
|
+
return tpl`
|
|
57
|
+
<Show when=${() => props.items.length > 0} fallback=${tpl`<p>No items</p>`}>
|
|
58
|
+
<ul>
|
|
59
|
+
<For each=${() => props.items}>
|
|
60
|
+
${item => tpl`<li>${item.name}</li>`}
|
|
61
|
+
</For>
|
|
62
|
+
</ul>
|
|
63
|
+
</Show>
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
An unregistered capitalized tag name throws at template-construction time, which gives tooling (codemods, syntax highlighters) something concrete to key off.
|
|
69
|
+
|
|
70
|
+
## Return shape
|
|
71
|
+
|
|
72
|
+
A `html\`...\`` expression returns a single node when the template resolves to one root, and an array of nodes when it resolves to many. Consumers that need to spread or iterate the result should normalize:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
const result = html`<span/><span/>`;
|
|
76
|
+
const nodes = Array.isArray(result) ? result : [result];
|
|
77
|
+
```
|
|
78
|
+
|
|
41
79
|
## Differences from JSX
|
|
42
80
|
|
|
43
81
|
There are a few differences from Solid's JSX that are important to note.
|
package/dist/html.cjs
CHANGED
|
@@ -2,586 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
var web = require('@solidjs/web');
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
const lookup = {
|
|
8
|
-
area: true,
|
|
9
|
-
base: true,
|
|
10
|
-
br: true,
|
|
11
|
-
col: true,
|
|
12
|
-
embed: true,
|
|
13
|
-
hr: true,
|
|
14
|
-
img: true,
|
|
15
|
-
input: true,
|
|
16
|
-
keygen: true,
|
|
17
|
-
link: true,
|
|
18
|
-
menuitem: true,
|
|
19
|
-
meta: true,
|
|
20
|
-
param: true,
|
|
21
|
-
source: true,
|
|
22
|
-
track: true,
|
|
23
|
-
wbr: true
|
|
24
|
-
};
|
|
25
|
-
function parseTag(tag) {
|
|
26
|
-
const res = {
|
|
27
|
-
type: 'tag',
|
|
28
|
-
name: '',
|
|
29
|
-
voidElement: false,
|
|
30
|
-
attrs: [],
|
|
31
|
-
children: []
|
|
32
|
-
};
|
|
33
|
-
const tagMatch = tag.match(/<\/?([^\s]+?)[/\s>]/);
|
|
34
|
-
if (tagMatch) {
|
|
35
|
-
res.name = tagMatch[1];
|
|
36
|
-
if (lookup[tagMatch[1].toLowerCase()] || tag.charAt(tag.length - 2) === '/') {
|
|
37
|
-
res.voidElement = true;
|
|
38
|
-
}
|
|
39
|
-
if (res.name.startsWith('!--')) {
|
|
40
|
-
const endIndex = tag.indexOf('-->');
|
|
41
|
-
return {
|
|
42
|
-
type: 'comment',
|
|
43
|
-
comment: endIndex !== -1 ? tag.slice(4, endIndex) : ''
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const reg = new RegExp(attrRE);
|
|
48
|
-
for (const match of tag.matchAll(reg)) {
|
|
49
|
-
if ((match[1] || match[2]).startsWith('use:')) {
|
|
50
|
-
res.attrs.push({
|
|
51
|
-
type: 'directive',
|
|
52
|
-
name: match[1] || match[2],
|
|
53
|
-
value: match[4] || match[5] || ''
|
|
54
|
-
});
|
|
55
|
-
} else {
|
|
56
|
-
res.attrs.push({
|
|
57
|
-
type: 'attr',
|
|
58
|
-
name: match[1] || match[2],
|
|
59
|
-
value: match[4] || match[5] || ''
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return res;
|
|
64
|
-
}
|
|
65
|
-
function pushTextNode(list, html, start) {
|
|
66
|
-
const end = html.indexOf('<', start);
|
|
67
|
-
const content = html.slice(start, end === -1 ? undefined : end);
|
|
68
|
-
if (!/^\s*$/.test(content)) {
|
|
69
|
-
list.push({
|
|
70
|
-
type: 'text',
|
|
71
|
-
content: content
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function pushCommentNode(list, tag) {
|
|
76
|
-
const content = tag.replace('<!--', '').replace('-->', '');
|
|
77
|
-
if (!/^\s*$/.test(content)) {
|
|
78
|
-
list.push({
|
|
79
|
-
type: 'comment',
|
|
80
|
-
content: content
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function parse(html) {
|
|
85
|
-
const result = [];
|
|
86
|
-
let current = undefined;
|
|
87
|
-
let level = -1;
|
|
88
|
-
const arr = [];
|
|
89
|
-
const byTag = {};
|
|
90
|
-
html.replace(tagRE, (tag, index) => {
|
|
91
|
-
const isOpen = tag.charAt(1) !== '/';
|
|
92
|
-
const isComment = tag.slice(0, 4) === '<!--';
|
|
93
|
-
const start = index + tag.length;
|
|
94
|
-
const nextChar = html.charAt(start);
|
|
95
|
-
let parent = undefined;
|
|
96
|
-
if (isOpen && !isComment) {
|
|
97
|
-
level++;
|
|
98
|
-
current = parseTag(tag);
|
|
99
|
-
if (!current.voidElement && nextChar && nextChar !== '<') {
|
|
100
|
-
pushTextNode(current.children, html, start);
|
|
101
|
-
}
|
|
102
|
-
byTag[current.tagName] = current;
|
|
103
|
-
if (level === 0) {
|
|
104
|
-
result.push(current);
|
|
105
|
-
}
|
|
106
|
-
parent = arr[level - 1];
|
|
107
|
-
if (parent) {
|
|
108
|
-
parent.children.push(current);
|
|
109
|
-
}
|
|
110
|
-
arr[level] = current;
|
|
111
|
-
}
|
|
112
|
-
if (isComment) {
|
|
113
|
-
if (level < 0) {
|
|
114
|
-
pushCommentNode(result, tag);
|
|
115
|
-
} else {
|
|
116
|
-
pushCommentNode(arr[level].children, tag);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (isComment || !isOpen || current.voidElement) {
|
|
120
|
-
if (!isComment) {
|
|
121
|
-
level--;
|
|
122
|
-
}
|
|
123
|
-
if (nextChar !== '<' && nextChar) {
|
|
124
|
-
parent = level === -1 ? result : arr[level].children;
|
|
125
|
-
pushTextNode(parent, html, start);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
return result;
|
|
130
|
-
}
|
|
131
|
-
function attrString(attrs) {
|
|
132
|
-
const buff = [];
|
|
133
|
-
for (const attr of attrs) {
|
|
134
|
-
buff.push(attr.name + '="' + attr.value.replace(/"/g, '"') + '"');
|
|
135
|
-
}
|
|
136
|
-
if (!buff.length) {
|
|
137
|
-
return '';
|
|
138
|
-
}
|
|
139
|
-
return ' ' + buff.join(' ');
|
|
140
|
-
}
|
|
141
|
-
function stringifier(buff, doc) {
|
|
142
|
-
switch (doc.type) {
|
|
143
|
-
case 'text':
|
|
144
|
-
return buff + doc.content;
|
|
145
|
-
case 'tag':
|
|
146
|
-
buff += '<' + doc.name + (doc.attrs ? attrString(doc.attrs) : '') + (doc.voidElement ? '/>' : '>');
|
|
147
|
-
if (doc.voidElement) {
|
|
148
|
-
return buff;
|
|
149
|
-
}
|
|
150
|
-
return buff + doc.children.reduce(stringifier, '') + '</' + doc.name + '>';
|
|
151
|
-
case 'comment':
|
|
152
|
-
return buff += '<!--' + doc.content + '-->';
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function stringify(doc) {
|
|
156
|
-
return doc.reduce(function (token, rootEl) {
|
|
157
|
-
return token + stringifier('', rootEl);
|
|
158
|
-
}, '');
|
|
159
|
-
}
|
|
160
|
-
const cache = new Map();
|
|
161
|
-
const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
|
|
162
|
-
const spaces = " \\f\\n\\r\\t";
|
|
163
|
-
const almostEverything = "[^" + spaces + "\\/>\"'=]+";
|
|
164
|
-
const attrName = "[ " + spaces + "]+(?:use:<!--#-->|" + almostEverything + ")";
|
|
165
|
-
const tagName = "<([A-Za-z$#]+[A-Za-z0-9:_-]*)((?:";
|
|
166
|
-
const attrPartials = "(?:\\s*=\\s*(?:'[^']*?'|\"[^\"]*?\"|\\([^)]*?\\)|<[^>]*?>|" + almostEverything + "))?)";
|
|
167
|
-
const attrSeeker = new RegExp(tagName + attrName + attrPartials + "+)([ " + spaces + "]*/?>)", "g");
|
|
168
|
-
const findAttributes = new RegExp("(" + attrName + "\\s*=\\s*)(<!--#-->|['\"(]([\\w\\s]*<!--#-->[\\w\\s]*)*['\")])", "gi");
|
|
169
|
-
const selfClosing = new RegExp(tagName + attrName + attrPartials + "*)([ " + spaces + "]*/>)", "g");
|
|
170
|
-
const marker = "<!--#-->";
|
|
171
|
-
const reservedNameSpaces = new Set(["class", "on", "style", "use", "prop"]);
|
|
172
|
-
function attrReplacer($0, $1, $2, $3) {
|
|
173
|
-
return "<" + $1 + $2.replace(findAttributes, replaceAttributes) + $3;
|
|
174
|
-
}
|
|
175
|
-
function replaceAttributes($0, $1, $2) {
|
|
176
|
-
return $1.replace(/<!--#-->/g, "###") + ($2[0] === '"' || $2[0] === "'" ? $2.replace(/<!--#-->/g, "###") : '"###"');
|
|
177
|
-
}
|
|
178
|
-
function fullClosing($0, $1, $2) {
|
|
179
|
-
return VOID_ELEMENTS.test($1) ? $0 : "<" + $1 + $2 + "></" + $1 + ">";
|
|
180
|
-
}
|
|
181
|
-
function parseDirective(name, value, tag, options) {
|
|
182
|
-
if (name === "use:###" && value === "###") {
|
|
183
|
-
const count = options.counter++;
|
|
184
|
-
options.exprs.push(`typeof exprs[${count}] === "function" ? r.use(exprs[${count}], ${tag}, exprs[${options.counter++}]) : (()=>{throw new Error("use:### must be a function")})()`);
|
|
185
|
-
} else {
|
|
186
|
-
throw new Error(`Not support syntax ${name} must be use:{function}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
function createHTML(r, {
|
|
190
|
-
delegateEvents = true,
|
|
191
|
-
functionBuilder = (...args) => new Function(...args)
|
|
192
|
-
} = {}) {
|
|
193
|
-
let uuid = 1;
|
|
194
|
-
r.wrapProps = props => {
|
|
195
|
-
const d = Object.getOwnPropertyDescriptors(props);
|
|
196
|
-
for (const k in d) {
|
|
197
|
-
if (typeof d[k].value === "function" && !d[k].value.length) r.dynamicProperty(props, k);
|
|
198
|
-
}
|
|
199
|
-
return props;
|
|
200
|
-
};
|
|
201
|
-
r.resolveFn = fn => typeof fn === "function" ? fn() : fn;
|
|
202
|
-
function createTemplate(statics, opt) {
|
|
203
|
-
let i = 0,
|
|
204
|
-
markup = "";
|
|
205
|
-
for (; i < statics.length - 1; i++) {
|
|
206
|
-
markup = markup + statics[i] + "<!--#-->";
|
|
207
|
-
}
|
|
208
|
-
markup = markup + statics[i];
|
|
209
|
-
const replaceList = [[selfClosing, fullClosing], [/<(<!--#-->)/g, "<###"], [/\.\.\.(<!--#-->)/g, "###"], [attrSeeker, attrReplacer], [/>\n+\s*/g, ">"], [/\n+\s*</g, "<"], [/\s+</g, " <"], [/>\s+/g, "> "]];
|
|
210
|
-
markup = replaceList.reduce((acc, x) => {
|
|
211
|
-
return acc.replace(x[0], x[1]);
|
|
212
|
-
}, markup);
|
|
213
|
-
const pars = parse(markup);
|
|
214
|
-
const [html, code] = parseTemplate(pars, opt.funcBuilder),
|
|
215
|
-
templates = [];
|
|
216
|
-
for (let i = 0; i < html.length; i++) {
|
|
217
|
-
templates.push(document.createElement("template"));
|
|
218
|
-
templates[i].innerHTML = html[i];
|
|
219
|
-
const nomarkers = templates[i].content.querySelectorAll("script,style");
|
|
220
|
-
for (let j = 0; j < nomarkers.length; j++) {
|
|
221
|
-
const d = nomarkers[j].firstChild?.data || "";
|
|
222
|
-
if (d.indexOf(marker) > -1) {
|
|
223
|
-
const parts = d.split(marker).reduce((memo, p, i) => {
|
|
224
|
-
i && memo.push("");
|
|
225
|
-
memo.push(p);
|
|
226
|
-
return memo;
|
|
227
|
-
}, []);
|
|
228
|
-
nomarkers[i].firstChild.replaceWith(...parts);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
templates[0].create = code;
|
|
233
|
-
cache.set(statics, templates);
|
|
234
|
-
return templates;
|
|
235
|
-
}
|
|
236
|
-
function parseKeyValue(node, tag, name, value, isSVG, options) {
|
|
237
|
-
let expr, parts, namespace;
|
|
238
|
-
if (value === "###") {
|
|
239
|
-
expr = `_$v`;
|
|
240
|
-
options.counter++;
|
|
241
|
-
} else {
|
|
242
|
-
const chunks = value.split("###");
|
|
243
|
-
options.counter = chunks.length - 1 + options.counter;
|
|
244
|
-
expr = chunks.map((v, i) => i ? ` + _$v[${i - 1}] + "${v}"` : `"${v}"`).join("");
|
|
245
|
-
}
|
|
246
|
-
if ((parts = name.split(":")) && parts[1] && reservedNameSpaces.has(parts[0])) {
|
|
247
|
-
name = parts[1];
|
|
248
|
-
namespace = parts[0];
|
|
249
|
-
}
|
|
250
|
-
const isChildProp = r.ChildProperties.has(name);
|
|
251
|
-
const isProp = r.Properties.has(name);
|
|
252
|
-
if (name === "style") {
|
|
253
|
-
options.exprs.push(`r.style(${tag},${expr},_$p)`);
|
|
254
|
-
} else if (name === "class") {
|
|
255
|
-
options.exprs.push(`r.className(${tag},${expr},${isSVG},_$p)`);
|
|
256
|
-
} else if (isChildProp || !isSVG && isProp || namespace === "prop") {
|
|
257
|
-
options.exprs.push(`${tag}.${name} = ${expr}`);
|
|
258
|
-
} else {
|
|
259
|
-
const ns = isSVG && name.indexOf(":") > -1 && r.SVGNamespace[name.split(":")[0]];
|
|
260
|
-
if (ns) options.exprs.push(`r.setAttributeNS(${tag},"${ns}","${name}",${expr})`);else options.exprs.push(`r.setAttribute(${tag},"${name}",${expr})`);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
function parseAttribute(node, tag, name, value, isSVG, options) {
|
|
264
|
-
if (name.slice(0, 2) === "on") {
|
|
265
|
-
if (!name.includes(":")) {
|
|
266
|
-
const lc = name.slice(2).toLowerCase();
|
|
267
|
-
const delegate = delegateEvents && r.DelegatedEvents.has(lc);
|
|
268
|
-
options.exprs.push(`r.addEventListener(${tag},"${lc}",exprs[${options.counter++}],${delegate})`);
|
|
269
|
-
delegate && options.delegatedEvents.add(lc);
|
|
270
|
-
} else {
|
|
271
|
-
options.exprs.push(`${tag}.addEventListener("${name.slice(3)}",exprs[${options.counter++}])`);
|
|
272
|
-
}
|
|
273
|
-
} else if (name === "ref") {
|
|
274
|
-
options.exprs.push(`exprs[${options.counter++}](${tag})`);
|
|
275
|
-
} else {
|
|
276
|
-
const childOptions = Object.assign({}, options, {
|
|
277
|
-
exprs: []
|
|
278
|
-
}),
|
|
279
|
-
count = options.counter;
|
|
280
|
-
parseKeyValue(node, tag, name, value, isSVG, childOptions);
|
|
281
|
-
options.decl.push(`_fn${count} = (_$v, _$p) => {\n${childOptions.exprs.join(";\n")};\n}`);
|
|
282
|
-
if (value === "###") {
|
|
283
|
-
options.exprs.push(`typeof exprs[${count}] === "function" ? r.effect(() => exprs[${count}](), _fn${count}) : _fn${count}(exprs[${count}])`);
|
|
284
|
-
} else {
|
|
285
|
-
let check = "";
|
|
286
|
-
let list = "";
|
|
287
|
-
let reactiveList = "";
|
|
288
|
-
for (let i = count; i < childOptions.counter; i++) {
|
|
289
|
-
if (i !== count) {
|
|
290
|
-
check += " || ";
|
|
291
|
-
list += ",";
|
|
292
|
-
reactiveList += ",";
|
|
293
|
-
}
|
|
294
|
-
check += `typeof exprs[${i}] === "function"`;
|
|
295
|
-
list += `exprs[${i}]`;
|
|
296
|
-
reactiveList += `r.resolveFn(exprs[${i}])`;
|
|
297
|
-
}
|
|
298
|
-
options.exprs.push(check + ` ? r.effect(() => [${reactiveList}], _fn${count}) : _fn${count}([${list}])`);
|
|
299
|
-
}
|
|
300
|
-
options.counter = childOptions.counter;
|
|
301
|
-
options.wrap = false;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
function processChildren(node, options) {
|
|
305
|
-
const childOptions = Object.assign({}, options, {
|
|
306
|
-
first: true,
|
|
307
|
-
multi: false,
|
|
308
|
-
parent: options.path
|
|
309
|
-
});
|
|
310
|
-
if (node.children.length > 1) {
|
|
311
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
312
|
-
const child = node.children[i];
|
|
313
|
-
if (child.type === "comment" && child.content === "#" || child.type === "tag" && child.name === "###") {
|
|
314
|
-
childOptions.multi = true;
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
let i = 0;
|
|
320
|
-
while (i < node.children.length) {
|
|
321
|
-
const child = node.children[i];
|
|
322
|
-
if (child.name === "###") {
|
|
323
|
-
if (childOptions.multi) {
|
|
324
|
-
node.children[i] = {
|
|
325
|
-
type: "comment",
|
|
326
|
-
content: "#"
|
|
327
|
-
};
|
|
328
|
-
i++;
|
|
329
|
-
} else node.children.splice(i, 1);
|
|
330
|
-
processComponent(child, childOptions);
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
parseNode(child, childOptions);
|
|
334
|
-
if (!childOptions.multi && child.type === "comment" && child.content === "#") node.children.splice(i, 1);else i++;
|
|
335
|
-
}
|
|
336
|
-
options.counter = childOptions.counter;
|
|
337
|
-
options.templateId = childOptions.templateId;
|
|
338
|
-
options.hasCustomElement = options.hasCustomElement || childOptions.hasCustomElement;
|
|
339
|
-
options.isImportNode = options.isImportNode || childOptions.isImportNode;
|
|
340
|
-
}
|
|
341
|
-
function processComponentProps(propGroups) {
|
|
342
|
-
let result = [];
|
|
343
|
-
for (const props of propGroups) {
|
|
344
|
-
if (Array.isArray(props)) {
|
|
345
|
-
if (!props.length) continue;
|
|
346
|
-
result.push(`r.wrapProps({${props.join(",") || ""}})`);
|
|
347
|
-
} else result.push(props);
|
|
348
|
-
}
|
|
349
|
-
return result.length > 1 ? `r.mergeProps(${result.join(",")})` : result[0];
|
|
350
|
-
}
|
|
351
|
-
function processComponent(node, options) {
|
|
352
|
-
let props = [];
|
|
353
|
-
const keys = Object.keys(node.attrs),
|
|
354
|
-
propGroups = [props],
|
|
355
|
-
componentIdentifier = options.counter++;
|
|
356
|
-
for (let i = 0; i < keys.length; i++) {
|
|
357
|
-
const {
|
|
358
|
-
type,
|
|
359
|
-
name,
|
|
360
|
-
value
|
|
361
|
-
} = node.attrs[i];
|
|
362
|
-
if (type === "attr") {
|
|
363
|
-
if (name === "###") {
|
|
364
|
-
propGroups.push(`exprs[${options.counter++}]`);
|
|
365
|
-
propGroups.push(props = []);
|
|
366
|
-
} else if (value === "###") {
|
|
367
|
-
props.push(`"${name}": exprs[${options.counter++}]`);
|
|
368
|
-
} else props.push(`"${name}": "${value}"`);
|
|
369
|
-
} else if (type === 'directive') {
|
|
370
|
-
const tag = `_$el${uuid++}`;
|
|
371
|
-
const topDecl = !options.decl.length;
|
|
372
|
-
options.decl.push(topDecl ? "" : `${tag} = ${options.path}.${options.first ? "firstChild" : "nextSibling"}`);
|
|
373
|
-
parseDirective(name, value, tag, options);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
if (node.children.length === 1 && node.children[0].type === "comment" && node.children[0].content === "#") {
|
|
377
|
-
props.push(`children: () => exprs[${options.counter++}]`);
|
|
378
|
-
} else if (node.children.length) {
|
|
379
|
-
const children = {
|
|
380
|
-
type: "fragment",
|
|
381
|
-
children: node.children
|
|
382
|
-
},
|
|
383
|
-
childOptions = Object.assign({}, options, {
|
|
384
|
-
first: true,
|
|
385
|
-
decl: [],
|
|
386
|
-
exprs: [],
|
|
387
|
-
parent: false
|
|
388
|
-
});
|
|
389
|
-
parseNode(children, childOptions);
|
|
390
|
-
props.push(`children: () => { ${childOptions.exprs.join(";\n")}}`);
|
|
391
|
-
options.templateId = childOptions.templateId;
|
|
392
|
-
options.counter = childOptions.counter;
|
|
393
|
-
}
|
|
394
|
-
let tag;
|
|
395
|
-
if (options.multi) {
|
|
396
|
-
tag = `_$el${uuid++}`;
|
|
397
|
-
options.decl.push(`${tag} = ${options.path}.${options.first ? "firstChild" : "nextSibling"}`);
|
|
398
|
-
}
|
|
399
|
-
if (options.parent) options.exprs.push(`r.insert(${options.parent}, r.createComponent(exprs[${componentIdentifier}],${processComponentProps(propGroups)})${tag ? `, ${tag}` : ""})`);else options.exprs.push(`${options.fragment ? "" : "return "}r.createComponent(exprs[${componentIdentifier}],${processComponentProps(propGroups)})`);
|
|
400
|
-
options.path = tag;
|
|
401
|
-
options.first = false;
|
|
402
|
-
}
|
|
403
|
-
function parseNode(node, options) {
|
|
404
|
-
if (node.type === "fragment") {
|
|
405
|
-
const parts = [];
|
|
406
|
-
node.children.forEach(child => {
|
|
407
|
-
if (child.type === "tag") {
|
|
408
|
-
if (child.name === "###") {
|
|
409
|
-
const childOptions = Object.assign({}, options, {
|
|
410
|
-
first: true,
|
|
411
|
-
fragment: true,
|
|
412
|
-
decl: [],
|
|
413
|
-
exprs: []
|
|
414
|
-
});
|
|
415
|
-
processComponent(child, childOptions);
|
|
416
|
-
parts.push(childOptions.exprs[0]);
|
|
417
|
-
options.counter = childOptions.counter;
|
|
418
|
-
options.templateId = childOptions.templateId;
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
options.templateId++;
|
|
422
|
-
const id = uuid;
|
|
423
|
-
const childOptions = Object.assign({}, options, {
|
|
424
|
-
first: true,
|
|
425
|
-
decl: [],
|
|
426
|
-
exprs: []
|
|
427
|
-
});
|
|
428
|
-
options.templateNodes.push([child]);
|
|
429
|
-
parseNode(child, childOptions);
|
|
430
|
-
parts.push(`function() { ${childOptions.decl.join(",\n") + ";\n" + childOptions.exprs.join(";\n") + `;\nreturn _$el${id};\n`}}()`);
|
|
431
|
-
options.counter = childOptions.counter;
|
|
432
|
-
options.templateId = childOptions.templateId;
|
|
433
|
-
} else if (child.type === "text") {
|
|
434
|
-
parts.push(`"${child.content}"`);
|
|
435
|
-
} else if (child.type === "comment") {
|
|
436
|
-
if (child.content === "#") parts.push(`exprs[${options.counter++}]`);else if (child.content) {
|
|
437
|
-
for (let i = 0; i < child.content.split("###").length - 1; i++) {
|
|
438
|
-
parts.push(`exprs[${options.counter++}]`);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
options.exprs.push(`return [${parts.join(", \n")}]`);
|
|
444
|
-
} else if (node.type === "tag") {
|
|
445
|
-
const tag = `_$el${uuid++}`;
|
|
446
|
-
const topDecl = !options.decl.length;
|
|
447
|
-
const templateId = options.templateId;
|
|
448
|
-
options.decl.push(topDecl ? "" : `${tag} = ${options.path}.${options.first ? "firstChild" : "nextSibling"}`);
|
|
449
|
-
const isSVG = r.SVGElements.has(node.name);
|
|
450
|
-
options.hasCustomElement = node.name.includes("-") || node.attrs.some(e => e.name === "is");
|
|
451
|
-
options.isImportNode = (node.name === "img" || node.name === "iframe") && node.attrs.some(e => e.name === "loading" && e.value === "lazy");
|
|
452
|
-
if (node.attrs.some(e => e.name === "###")) {
|
|
453
|
-
const spreadArgs = [];
|
|
454
|
-
let current = "";
|
|
455
|
-
const newAttrs = [];
|
|
456
|
-
for (let i = 0; i < node.attrs.length; i++) {
|
|
457
|
-
const {
|
|
458
|
-
type,
|
|
459
|
-
name,
|
|
460
|
-
value
|
|
461
|
-
} = node.attrs[i];
|
|
462
|
-
if (type === "attr") {
|
|
463
|
-
if (value.includes("###")) {
|
|
464
|
-
let count = options.counter++;
|
|
465
|
-
current += `${name}: ${name !== "ref" ? `typeof exprs[${count}] === "function" ? exprs[${count}]() : ` : ""}exprs[${count}],`;
|
|
466
|
-
} else if (name === "###") {
|
|
467
|
-
if (current.length) {
|
|
468
|
-
spreadArgs.push(`()=>({${current}})`);
|
|
469
|
-
current = "";
|
|
470
|
-
}
|
|
471
|
-
spreadArgs.push(`exprs[${options.counter++}]`);
|
|
472
|
-
} else {
|
|
473
|
-
newAttrs.push(node.attrs[i]);
|
|
474
|
-
}
|
|
475
|
-
} else if (type === "directive") {
|
|
476
|
-
parseDirective(name, value, tag, options);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
node.attrs = newAttrs;
|
|
480
|
-
if (current.length) {
|
|
481
|
-
spreadArgs.push(`()=>({${current}})`);
|
|
482
|
-
}
|
|
483
|
-
options.exprs.push(`r.spread(${tag},${spreadArgs.length === 1 ? `typeof ${spreadArgs[0]} === "function" ? r.mergeProps(${spreadArgs[0]}) : ${spreadArgs[0]}` : `r.mergeProps(${spreadArgs.join(",")})`},${isSVG},${!!node.children.length})`);
|
|
484
|
-
} else {
|
|
485
|
-
for (let i = 0; i < node.attrs.length; i++) {
|
|
486
|
-
const {
|
|
487
|
-
type,
|
|
488
|
-
name,
|
|
489
|
-
value
|
|
490
|
-
} = node.attrs[i];
|
|
491
|
-
if (type === "directive") {
|
|
492
|
-
parseDirective(name, value, tag, options);
|
|
493
|
-
node.attrs.splice(i, 1);
|
|
494
|
-
i--;
|
|
495
|
-
} else if (type === "attr") {
|
|
496
|
-
if (value.includes("###")) {
|
|
497
|
-
node.attrs.splice(i, 1);
|
|
498
|
-
i--;
|
|
499
|
-
parseAttribute(node, tag, name, value, isSVG, options);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
options.path = tag;
|
|
505
|
-
options.first = false;
|
|
506
|
-
processChildren(node, options);
|
|
507
|
-
if (topDecl) {
|
|
508
|
-
options.decl[0] = options.hasCustomElement || options.isImportNode ? `const ${tag} = r.untrack(() => document.importNode(tmpls[${templateId}].content.firstChild, true))` : `const ${tag} = tmpls[${templateId}].content.firstChild.cloneNode(true)`;
|
|
509
|
-
}
|
|
510
|
-
} else if (node.type === "text") {
|
|
511
|
-
const tag = `_$el${uuid++}`;
|
|
512
|
-
options.decl.push(`${tag} = ${options.path}.${options.first ? "firstChild" : "nextSibling"}`);
|
|
513
|
-
options.path = tag;
|
|
514
|
-
options.first = false;
|
|
515
|
-
} else if (node.type === "comment") {
|
|
516
|
-
const tag = `_$el${uuid++}`;
|
|
517
|
-
options.decl.push(`${tag} = ${options.path}.${options.first ? "firstChild" : "nextSibling"}`);
|
|
518
|
-
if (node.content === "#") {
|
|
519
|
-
if (options.multi) {
|
|
520
|
-
options.exprs.push(`r.insert(${options.parent}, exprs[${options.counter++}], ${tag})`);
|
|
521
|
-
} else options.exprs.push(`r.insert(${options.parent}, exprs[${options.counter++}])`);
|
|
522
|
-
}
|
|
523
|
-
options.path = tag;
|
|
524
|
-
options.first = false;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
function parseTemplate(nodes, funcBuilder) {
|
|
528
|
-
const options = {
|
|
529
|
-
path: "",
|
|
530
|
-
decl: [],
|
|
531
|
-
exprs: [],
|
|
532
|
-
delegatedEvents: new Set(),
|
|
533
|
-
counter: 0,
|
|
534
|
-
first: true,
|
|
535
|
-
multi: false,
|
|
536
|
-
templateId: 0,
|
|
537
|
-
templateNodes: []
|
|
538
|
-
},
|
|
539
|
-
id = uuid,
|
|
540
|
-
origNodes = nodes;
|
|
541
|
-
let toplevel;
|
|
542
|
-
if (nodes.length > 1) {
|
|
543
|
-
nodes = [{
|
|
544
|
-
type: "fragment",
|
|
545
|
-
children: nodes
|
|
546
|
-
}];
|
|
547
|
-
}
|
|
548
|
-
if (nodes[0].name === "###") {
|
|
549
|
-
toplevel = true;
|
|
550
|
-
processComponent(nodes[0], options);
|
|
551
|
-
} else parseNode(nodes[0], options);
|
|
552
|
-
r.delegateEvents(Array.from(options.delegatedEvents));
|
|
553
|
-
const templateNodes = [origNodes].concat(options.templateNodes);
|
|
554
|
-
return [templateNodes.map(t => stringify(t)), funcBuilder("tmpls", "exprs", "r", options.decl.join(",\n") + ";\n" + options.exprs.join(";\n") + (toplevel ? "" : `;\nreturn _$el${id};\n`))];
|
|
555
|
-
}
|
|
556
|
-
function html(statics, ...args) {
|
|
557
|
-
const templates = cache.get(statics) || createTemplate(statics, {
|
|
558
|
-
funcBuilder: functionBuilder
|
|
559
|
-
});
|
|
560
|
-
return templates[0].create(templates, args, r);
|
|
561
|
-
}
|
|
562
|
-
return html;
|
|
563
|
-
}
|
|
5
|
+
const e=e=>t(e)||e>=48&&e<=58||e===46||e===45,t=e=>e>=65&&e<=90||e>=97&&e<=122||e===95||e===36,n=e=>e>=9&&e<=13||e===32,r=(r,i)=>{let a=[],o=0,s=``,c=0;for(let l=0;l<r.length;l++){let u=r[l],d=u.length;for(c=0;c<d;)switch(o){case 0:{s=``;let e=u.indexOf(`<`,c);e===-1?(c<d&&a.push({type:6,value:u.slice(c)}),c=d):(e>c&&a.push({type:6,value:u.slice(c,e)}),u[e+1]===`!`&&u[e+2]===`-`&&u[e+3]===`-`?(o=3,c=e+4):(a.push({type:0}),o=1,c=e+1));break}case 1:{let r=u.charCodeAt(c);if(n(r))c++;else if(r===62)i.has(s)&&a[a.length-2]?.type!==2?o=2:(o=0,s=``),a.push({type:1}),c++;else if(r===61)a.push({type:4}),c++;else if(r===47){let e=u.charCodeAt(c+1),t=u.slice(c+2).search(/\S/),n=e===47&&a[a.length-1]?.type===0&&t!==-1&&u[c+2+t]===`>`;e===47&&!n?o=4:e===42?o=5:(a.push({type:2}),c++);}else if(r===34||r===39){let e=u[c],t=u.indexOf(e,c+1);if(t===-1)throw Error(`Unterminated string at ${l}:${c}`);a.push({type:5,value:u.slice(c+1,t),quote:e}),c=t+1;}else if(t(r)){let t=c;for(;c<d&&e(u.charCodeAt(c));)c++;let n=u.slice(t,c);s===``&&(s=n),a.push({type:3,value:n});}else if(r===46&&u[c+1]===`.`&&u[c+2]===`.`)a.push({type:8}),c+=3;else throw Error(`Unexpected Character: ${u[c]} at ${l}:${c}`);break}case 2:{let e=RegExp(`<\\s*/\\s*${s}\\s*>`,`g`);e.lastIndex=c;let t=e.exec(u);if(t){let e=t.index;e>c&&a.push({type:6,value:u.slice(c,e)}),o=0,c=e,s=``;}else a.push({type:6,value:u.slice(c)}),c=d;break}case 3:case 4:case 5:{let e=o===4?`
|
|
6
|
+
`:o===5?`*/`:`-->`,t=u.indexOf(e,c);t===-1?c=d:(o=o===3?0:1,c=t+e.length);break}}l<r.length-1&&(o===0||o===1||o===2)&&a.push({type:7,value:l});}return a},i=e=>{let t=e.charCodeAt(0);return t>=65&&t<=90},a=(e,t)=>{let n={type:0,children:[]},r=[n],a=0,o=e.length;for(;a<o;){let n=e[a],s=r[r.length-1];switch(n.type){case 6:{let t=n.value;if(t.trim()===``){let t=e[a-1]?.type,n=e[a+1]?.type;if(t===1||n===0){a++;continue}}s.children.push({type:3,value:t}),a++;continue}case 7:s.children.push({type:4,value:n.value}),a++;continue;case 0:{let n=e[++a];if(n.type===2){let n=e[++a],i=e[++a],o=r[r.length-1];if(r.length>1&&i.type===1&&(n?.type===3&&o.name===n.value||(n?.type===7||n.type===2)&&typeof o.name==`number`)){let e=r.pop();e?.type===1&&t.has(e.name)&&(e.children=[]),a++;continue}throw Error(`Mismatched closing tag for <${o.name}>`)}if(n.type===3||n.type===7){let t=n.value,c={type:typeof t==`number`||i(t)?2:1,name:t,props:[],children:[]};for(s.children.push(c),a++;a<o;){let t=e[a];if(t.type===1||t.type===2)break;if(t.type===8){let t=e[a+1];if(t?.type===7)c.props.push({type:3,value:t.value}),a+=2;else throw Error(`Spread operator in <${c.name}> must be followed by an expression`)}else if(t.type===3){let n=t.value;if(e[a+1]?.type===4){a+=2;let t=e[a];if(t.type===7)c.props.push({name:n,type:2,value:t.value}),a++;else if(t.type===5){let e=t.quote;c.props.push({name:n,value:t.value,quote:e,type:1}),a++;}else throw Error(`Attribute value for "${n}" in <${c.name}> must be an expression or a string`)}else c.props.push({type:0,name:n,value:true}),a++;}else throw Error(`Invalid attribute in <${c.name}>`)}let l=e[a];l.type===2?a+=2:l.type===1&&(a++,r.push(c));continue}}default:throw Error(`Unexpected token: ${JSON.stringify(n)} after <${r[r.length-1].name}>`)}}if(r.length>1)throw Error(`Unclosed tag for <${r[r.length-1].name}>`);return n},o=e=>e.length===1?e[0]:e;function s(e){let t=new WeakMap,n=new Set(e.RawTextElements);n.delete(`template`);let i=document.createTreeWalker(document,129),s=t=>e.SVGElements.has(t)?document.createElementNS(`http://www.w3.org/2000/svg`,t):e.MathMLElements.has(t)?document.createElementNS(`http://www.w3.org/1998/Math/MathML`,t):document.createElement(t),c=e=>{let t=(t,...n)=>m(l(t),n,e);return t.components=e,t.jsx=t,t.define=t=>c({...e,...t}),t},l=i=>{let o=t.get(i);return o||(o=a(r(i,n),e.VoidElements),u(o,false),t.set(i,o)),o},u=(e,t)=>{if(e.type===1){if(!t){let n=document.createElement(`template`);n.content.appendChild(f(e)),e.template=n,t=true;}e.children.forEach(e=>u(e,t));}else e.type===2||e.type===0?e.children.forEach(e=>u(e,false)):e.type===3&&!t&&(d.innerHTML=e.value,e.value=d.content.textContent??``);},d=document.createElement(`template`),f=e=>{switch(e.type){case 3:return d.innerHTML=e.value,document.createTextNode(d.content.textContent??``);case 4:return document.createComment(`+`);case 2:return document.createComment(e.name);case 1:let t=false,n=s(e.name),r=e.name===`a`?`href`:e.name===`form`?`action`:void 0;return e.props=e.props.filter(i=>i.type===1?i.name.startsWith(`prop:`)?true:(n.setAttribute(i.name,i.value),!t&&i.name===r&&(e.claim=true),t):i.type===0?(n.setAttribute(i.name,``),!t&&i.name===r&&(e.claim=true),t):i.type===3?(t=true,t):true),(e.name===`template`?n.content:n).append(...e.children.map(f)),n}},p=(t,n,r)=>{switch(t.type){case 3:return t.value;case 4:return n[t.value];case 2:let i=typeof t.name==`string`?r[t.name]:n[t.name];if(i&&typeof i==`function`)return e.createComponent(i,h(t,n,r));throw Error(`Component "${t.name}" not found in registry`);case 1:let a=m(t,n,r),o=h(t,n,r);return e.spread(a,o,true),t.claim&&e.claimElement(a),a}},m=(t,n,r)=>{if(t.type!==1||!t.template)return o(t.children.map(e=>p(e,n,r)));let a=t.template.content.firstChild.cloneNode(true);i.currentNode=a;let s=(t,i)=>{for(let a of t)if(a.type===1||a.type===4||a.type===2){let t=i.nextNode();if(a.type===4||a.type===2)e.insert(t.parentNode,p(a,n,r),t),i.currentNode=t;else {if(a.props.length){let i=h(a,n,r);e.spread(t,i,true);}a.claim&&e.claimElement(t),s(a.children,a.name===`template`?document.createTreeWalker(t.content,129):i);}}};return s(t.children,t.name===`template`?document.createTreeWalker(a.content,129):i),a},h=(t,n,r,i={})=>{for(let r of t.props)switch(r.type){case 0:i[r.name]=true;break;case 1:i[r.name]=r.value;break;case 2:g(i,r.name,n[r.value]);break;case 3:let t=n[r.value];if(!t||typeof t!=`object`)throw Error(`Can only spread objects`);i=e.mergeProps(i,t);break}return t.type===2&&t.children.length&&Object.defineProperty(i,`children`,{get(){return m(t,n,r)}}),i},g=(e,t,n)=>{typeof n==`function`&&n.length===0&&t!==`ref`&&!t.startsWith(`on`)?Object.defineProperty(e,t,{get(){return n()},enumerable:true}):e[t]=n;};return c({})}
|
|
564
7
|
|
|
565
|
-
const html =
|
|
566
|
-
effect: web.effect,
|
|
567
|
-
style: web.style,
|
|
8
|
+
const html = s({
|
|
568
9
|
insert: web.insert,
|
|
569
|
-
untrack: web.untrack,
|
|
570
10
|
spread: web.spread,
|
|
571
11
|
createComponent: web.createComponent,
|
|
572
|
-
delegateEvents: web.delegateEvents,
|
|
573
|
-
className: web.className,
|
|
574
12
|
mergeProps: web.mergeProps,
|
|
575
|
-
|
|
576
|
-
setAttribute: web.setAttribute,
|
|
577
|
-
setAttributeNS: web.setAttributeNS,
|
|
578
|
-
addEventListener: web.addEventListener,
|
|
579
|
-
getPropAlias: web.getPropAlias,
|
|
580
|
-
Properties: web.Properties,
|
|
581
|
-
ChildProperties: web.ChildProperties,
|
|
582
|
-
DelegatedEvents: web.DelegatedEvents,
|
|
13
|
+
claimElement: web.claimElement,
|
|
583
14
|
SVGElements: web.SVGElements,
|
|
584
|
-
|
|
15
|
+
MathMLElements: web.MathMLElements,
|
|
16
|
+
VoidElements: web.VoidElements,
|
|
17
|
+
RawTextElements: web.RawTextElements
|
|
585
18
|
});
|
|
586
19
|
|
|
587
20
|
module.exports = html;
|