@solidjs/html 2.0.0-rc.2 → 2.0.0-rc.4
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 +1 -1
- package/dist/html.cjs +520 -13
- package/dist/html.js +521 -14
- package/package.json +11 -11
- package/types/index.d.ts +1 -3
- package/types/parse.d.ts +67 -0
- package/types/tagged-jsx.d.ts +4 -12567
- package/types/tokenize.d.ts +43 -0
- package/types/types.d.ts +51 -0
- package/types-cjs/index.d.cts +1 -3
- package/types-cjs/parse.d.cts +67 -0
- package/types-cjs/tagged-jsx.d.cts +4 -12567
- package/types-cjs/tokenize.d.cts +43 -0
- package/types-cjs/types.d.cts +51 -0
- package/LICENSE +0 -21
package/dist/html.js
CHANGED
|
@@ -1,18 +1,525 @@
|
|
|
1
|
-
import { RawTextElements,
|
|
1
|
+
import { RawTextElements, spread, claimElement, createComponent, insert, mergeProps, SVGElements, MathMLElements, VoidElements } from '@solidjs/web';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const OPEN_TAG_TOKEN = 0;
|
|
4
|
+
const CLOSE_TAG_TOKEN = 1;
|
|
5
|
+
const SLASH_TOKEN = 2;
|
|
6
|
+
const IDENTIFIER_TOKEN = 3;
|
|
7
|
+
const EQUALS_TOKEN = 4;
|
|
8
|
+
const STRING_TOKEN = 5;
|
|
9
|
+
const TEXT_TOKEN = 6;
|
|
10
|
+
const EXPRESSION_TOKEN = 7;
|
|
11
|
+
const SPREAD_TOKEN = 8;
|
|
12
|
+
const isIdentifierChar = code => {
|
|
13
|
+
return isIdentifierStart(code) || code >= 48 && code <= 58 ||
|
|
14
|
+
code === 46 ||
|
|
15
|
+
code === 45
|
|
16
|
+
;
|
|
17
|
+
};
|
|
18
|
+
const isIdentifierStart = code => {
|
|
19
|
+
return code >= 65 && code <= 90 ||
|
|
20
|
+
code >= 97 && code <= 122 ||
|
|
21
|
+
code === 95 ||
|
|
22
|
+
code === 36
|
|
23
|
+
;
|
|
24
|
+
};
|
|
25
|
+
const isWhitespace = code => {
|
|
26
|
+
return code >= 9 && code <= 13 || code === 32;
|
|
27
|
+
};
|
|
28
|
+
const STATE_TEXT = 0;
|
|
29
|
+
const STATE_TAG = 1;
|
|
30
|
+
const STATE_RAW_TEXT = 2;
|
|
31
|
+
const STATE_COMMENT = 3;
|
|
32
|
+
const STATE_LINE_COMMENT = 4;
|
|
33
|
+
const STATE_BLOCK_COMMENT = 5;
|
|
34
|
+
const tokenize = (strings, rawTextElements) => {
|
|
35
|
+
const tokens = [];
|
|
36
|
+
let state = STATE_TEXT;
|
|
37
|
+
let lastTagName = "";
|
|
38
|
+
let cursor = 0;
|
|
39
|
+
for (let i = 0; i < strings.length; i++) {
|
|
40
|
+
const str = strings[i];
|
|
41
|
+
const len = str.length;
|
|
42
|
+
cursor = 0;
|
|
43
|
+
while (cursor < len) {
|
|
44
|
+
switch (state) {
|
|
45
|
+
case STATE_TEXT:
|
|
46
|
+
{
|
|
47
|
+
lastTagName = "";
|
|
48
|
+
const nextTag = str.indexOf("<", cursor);
|
|
49
|
+
if (nextTag === -1) {
|
|
50
|
+
if (cursor < len) tokens.push({
|
|
51
|
+
type: TEXT_TOKEN,
|
|
52
|
+
value: str.slice(cursor)
|
|
53
|
+
});
|
|
54
|
+
cursor = len;
|
|
55
|
+
} else {
|
|
56
|
+
if (nextTag > cursor) tokens.push({
|
|
57
|
+
type: TEXT_TOKEN,
|
|
58
|
+
value: str.slice(cursor, nextTag)
|
|
59
|
+
});
|
|
60
|
+
if (str[nextTag + 1] === "!" && str[nextTag + 2] === "-" && str[nextTag + 3] === "-") {
|
|
61
|
+
state = STATE_COMMENT;
|
|
62
|
+
cursor = nextTag + 4;
|
|
63
|
+
} else {
|
|
64
|
+
tokens.push({
|
|
65
|
+
type: OPEN_TAG_TOKEN
|
|
66
|
+
});
|
|
67
|
+
state = STATE_TAG;
|
|
68
|
+
cursor = nextTag + 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case STATE_TAG:
|
|
74
|
+
{
|
|
75
|
+
const code = str.charCodeAt(cursor);
|
|
76
|
+
if (isWhitespace(code)) {
|
|
77
|
+
cursor++;
|
|
78
|
+
} else if (code === 62) {
|
|
79
|
+
if (rawTextElements.has(lastTagName) && tokens[tokens.length - 2]?.type !== SLASH_TOKEN) {
|
|
80
|
+
state = STATE_RAW_TEXT;
|
|
81
|
+
} else {
|
|
82
|
+
state = STATE_TEXT;
|
|
83
|
+
lastTagName = "";
|
|
84
|
+
}
|
|
85
|
+
tokens.push({
|
|
86
|
+
type: CLOSE_TAG_TOKEN
|
|
87
|
+
});
|
|
88
|
+
cursor++;
|
|
89
|
+
} else if (code === 61) {
|
|
90
|
+
tokens.push({
|
|
91
|
+
type: EQUALS_TOKEN
|
|
92
|
+
});
|
|
93
|
+
cursor++;
|
|
94
|
+
} else if (code === 47) {
|
|
95
|
+
const next = str.charCodeAt(cursor + 1);
|
|
96
|
+
const nextNonWhitespace = str.slice(cursor + 2).search(/\S/);
|
|
97
|
+
const isShorthandClosingTag = next === 47 && tokens[tokens.length - 1]?.type === OPEN_TAG_TOKEN && nextNonWhitespace !== -1 && str[cursor + 2 + nextNonWhitespace] === ">";
|
|
98
|
+
if (next === 47 && !isShorthandClosingTag) {
|
|
99
|
+
state = STATE_LINE_COMMENT;
|
|
100
|
+
} else if (next === 42) {
|
|
101
|
+
state = STATE_BLOCK_COMMENT;
|
|
102
|
+
} else {
|
|
103
|
+
tokens.push({
|
|
104
|
+
type: SLASH_TOKEN
|
|
105
|
+
});
|
|
106
|
+
cursor++;
|
|
107
|
+
}
|
|
108
|
+
} else if (code === 34 || code === 39) {
|
|
109
|
+
const char = str[cursor];
|
|
110
|
+
const endQuoteIndex = str.indexOf(char, cursor + 1);
|
|
111
|
+
if (endQuoteIndex === -1) {
|
|
112
|
+
throw new Error(`Unterminated string at ${i}:${cursor}`);
|
|
113
|
+
}
|
|
114
|
+
tokens.push({
|
|
115
|
+
type: STRING_TOKEN,
|
|
116
|
+
value: str.slice(cursor + 1, endQuoteIndex),
|
|
117
|
+
quote: char
|
|
118
|
+
});
|
|
119
|
+
cursor = endQuoteIndex + 1;
|
|
120
|
+
} else if (isIdentifierStart(code)) {
|
|
121
|
+
const start = cursor;
|
|
122
|
+
while (cursor < len && isIdentifierChar(str.charCodeAt(cursor))) cursor++;
|
|
123
|
+
const value = str.slice(start, cursor);
|
|
124
|
+
if (lastTagName === "") {
|
|
125
|
+
lastTagName = value;
|
|
126
|
+
}
|
|
127
|
+
tokens.push({
|
|
128
|
+
type: IDENTIFIER_TOKEN,
|
|
129
|
+
value
|
|
130
|
+
});
|
|
131
|
+
} else if (code === 46 && str[cursor + 1] === "." && str[cursor + 2] === ".") {
|
|
132
|
+
tokens.push({
|
|
133
|
+
type: SPREAD_TOKEN
|
|
134
|
+
});
|
|
135
|
+
cursor += 3;
|
|
136
|
+
} else {
|
|
137
|
+
throw new Error(`Unexpected Character: ${str[cursor]} at ${i}:${cursor}`);
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case STATE_RAW_TEXT:
|
|
142
|
+
{
|
|
143
|
+
const closeTagRegex = new RegExp(`<\\s*/\\s*${lastTagName}\\s*>`, "g");
|
|
144
|
+
closeTagRegex.lastIndex = cursor;
|
|
145
|
+
const match = closeTagRegex.exec(str);
|
|
146
|
+
if (match) {
|
|
147
|
+
const endOfRawIdx = match.index;
|
|
148
|
+
if (endOfRawIdx > cursor) {
|
|
149
|
+
tokens.push({
|
|
150
|
+
type: TEXT_TOKEN,
|
|
151
|
+
value: str.slice(cursor, endOfRawIdx)
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
state = STATE_TEXT;
|
|
155
|
+
cursor = endOfRawIdx;
|
|
156
|
+
lastTagName = "";
|
|
157
|
+
} else {
|
|
158
|
+
tokens.push({
|
|
159
|
+
type: TEXT_TOKEN,
|
|
160
|
+
value: str.slice(cursor)
|
|
161
|
+
});
|
|
162
|
+
cursor = len;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case STATE_COMMENT:
|
|
167
|
+
case STATE_LINE_COMMENT:
|
|
168
|
+
case STATE_BLOCK_COMMENT:
|
|
169
|
+
{
|
|
170
|
+
const commentEnd = state === STATE_LINE_COMMENT ? "\n" : state === STATE_BLOCK_COMMENT ? "*/" : "-->";
|
|
171
|
+
const commentEndIndex = str.indexOf(commentEnd, cursor);
|
|
172
|
+
if (commentEndIndex === -1) {
|
|
173
|
+
cursor = len;
|
|
174
|
+
} else {
|
|
175
|
+
state = state === STATE_COMMENT ? STATE_TEXT : STATE_TAG;
|
|
176
|
+
cursor = commentEndIndex + commentEnd.length;
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (i < strings.length - 1) {
|
|
183
|
+
if (state === STATE_TEXT || state === STATE_TAG || state === STATE_RAW_TEXT) {
|
|
184
|
+
tokens.push({
|
|
185
|
+
type: EXPRESSION_TOKEN,
|
|
186
|
+
value: i
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return tokens;
|
|
192
|
+
};
|
|
5
193
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
194
|
+
const isComponentNode = name => {
|
|
195
|
+
const char = name.charCodeAt(0);
|
|
196
|
+
return char >= 65 && char <= 90
|
|
197
|
+
;
|
|
198
|
+
};
|
|
199
|
+
const ROOT_NODE = 0;
|
|
200
|
+
const ELEMENT_NODE = 1;
|
|
201
|
+
const COMPONENT_NODE = 2;
|
|
202
|
+
const TEXT_NODE = 3;
|
|
203
|
+
const EXPRESSION_NODE = 4;
|
|
204
|
+
const BOOLEAN_PROP = 0;
|
|
205
|
+
const STATIC_PROP = 1;
|
|
206
|
+
const EXPRESSION_PROP = 2;
|
|
207
|
+
const SPREAD_PROP = 3;
|
|
208
|
+
const parse = (tokens, voidElements) => {
|
|
209
|
+
const root = {
|
|
210
|
+
type: ROOT_NODE,
|
|
211
|
+
children: []
|
|
212
|
+
};
|
|
213
|
+
const stack = [root];
|
|
214
|
+
let pos = 0;
|
|
215
|
+
const len = tokens.length;
|
|
216
|
+
while (pos < len) {
|
|
217
|
+
const token = tokens[pos];
|
|
218
|
+
const parent = stack[stack.length - 1];
|
|
219
|
+
switch (token.type) {
|
|
220
|
+
case TEXT_TOKEN:
|
|
221
|
+
{
|
|
222
|
+
const value = token.value;
|
|
223
|
+
if (value.trim() === "") {
|
|
224
|
+
const prevType = tokens[pos - 1]?.type;
|
|
225
|
+
const nextType = tokens[pos + 1]?.type;
|
|
226
|
+
if (prevType === CLOSE_TAG_TOKEN || nextType === OPEN_TAG_TOKEN) {
|
|
227
|
+
pos++;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
parent.children.push({
|
|
232
|
+
type: TEXT_NODE,
|
|
233
|
+
value
|
|
234
|
+
});
|
|
235
|
+
pos++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
case EXPRESSION_TOKEN:
|
|
239
|
+
{
|
|
240
|
+
parent.children.push({
|
|
241
|
+
type: EXPRESSION_NODE,
|
|
242
|
+
value: token.value
|
|
243
|
+
});
|
|
244
|
+
pos++;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
case OPEN_TAG_TOKEN:
|
|
248
|
+
{
|
|
249
|
+
const nextToken = tokens[++pos];
|
|
250
|
+
if (nextToken.type === SLASH_TOKEN) {
|
|
251
|
+
const nameToken = tokens[++pos];
|
|
252
|
+
const closeToken = tokens[++pos];
|
|
253
|
+
const currentParent = stack[stack.length - 1];
|
|
254
|
+
if (stack.length > 1 && closeToken.type === CLOSE_TAG_TOKEN && (nameToken?.type === IDENTIFIER_TOKEN && currentParent.name === nameToken.value || (nameToken?.type === EXPRESSION_TOKEN || nameToken.type === SLASH_TOKEN) && typeof currentParent.name === "number")) {
|
|
255
|
+
const node = stack.pop();
|
|
256
|
+
if (node?.type === ELEMENT_NODE && voidElements.has(node.name)) {
|
|
257
|
+
node.children = [];
|
|
258
|
+
}
|
|
259
|
+
pos++;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
throw new Error(`Mismatched closing tag for <${currentParent.name}>`);
|
|
263
|
+
}
|
|
264
|
+
if (nextToken.type === IDENTIFIER_TOKEN || nextToken.type === EXPRESSION_TOKEN) {
|
|
265
|
+
const tagName = nextToken.value;
|
|
266
|
+
const node = {
|
|
267
|
+
type: typeof tagName === "number" || isComponentNode(tagName) ? COMPONENT_NODE : ELEMENT_NODE,
|
|
268
|
+
name: tagName,
|
|
269
|
+
props: [],
|
|
270
|
+
children: []
|
|
271
|
+
};
|
|
272
|
+
parent.children.push(node);
|
|
273
|
+
pos++;
|
|
274
|
+
while (pos < len) {
|
|
275
|
+
const attrToken = tokens[pos];
|
|
276
|
+
if (attrToken.type === CLOSE_TAG_TOKEN || attrToken.type === SLASH_TOKEN) {
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
if (attrToken.type === SPREAD_TOKEN) {
|
|
280
|
+
const expr = tokens[pos + 1];
|
|
281
|
+
if (expr?.type === EXPRESSION_TOKEN) {
|
|
282
|
+
node.props.push({
|
|
283
|
+
type: SPREAD_PROP,
|
|
284
|
+
value: expr.value
|
|
285
|
+
});
|
|
286
|
+
pos += 2;
|
|
287
|
+
} else {
|
|
288
|
+
throw new Error(`Spread operator in <${node.name}> must be followed by an expression`);
|
|
289
|
+
}
|
|
290
|
+
} else if (attrToken.type === IDENTIFIER_TOKEN) {
|
|
291
|
+
const name = attrToken.value;
|
|
292
|
+
const next = tokens[pos + 1];
|
|
293
|
+
if (next?.type === EQUALS_TOKEN) {
|
|
294
|
+
pos += 2;
|
|
295
|
+
const valToken = tokens[pos];
|
|
296
|
+
if (valToken.type === EXPRESSION_TOKEN) {
|
|
297
|
+
node.props.push({
|
|
298
|
+
name,
|
|
299
|
+
type: EXPRESSION_PROP,
|
|
300
|
+
value: valToken.value
|
|
301
|
+
});
|
|
302
|
+
pos++;
|
|
303
|
+
} else if (valToken.type === STRING_TOKEN) {
|
|
304
|
+
const quote = valToken.quote;
|
|
305
|
+
node.props.push({
|
|
306
|
+
name,
|
|
307
|
+
value: valToken.value,
|
|
308
|
+
quote,
|
|
309
|
+
type: STATIC_PROP
|
|
310
|
+
});
|
|
311
|
+
pos++;
|
|
312
|
+
} else {
|
|
313
|
+
throw new Error(`Attribute value for "${name}" in <${node.name}> must be an expression or a string`);
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
node.props.push({
|
|
317
|
+
type: BOOLEAN_PROP,
|
|
318
|
+
name,
|
|
319
|
+
value: true
|
|
320
|
+
});
|
|
321
|
+
pos++;
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
throw new Error(`Invalid attribute in <${node.name}>`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const endToken = tokens[pos];
|
|
328
|
+
if (endToken.type === SLASH_TOKEN) {
|
|
329
|
+
pos += 2;
|
|
330
|
+
} else if (endToken.type === CLOSE_TAG_TOKEN) {
|
|
331
|
+
pos++;
|
|
332
|
+
stack.push(node);
|
|
333
|
+
}
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
default:
|
|
338
|
+
throw new Error(`Unexpected token: ${JSON.stringify(token)} after <${stack[stack.length - 1].name}>`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (stack.length > 1) {
|
|
342
|
+
throw new Error(`Unclosed tag for <${stack[stack.length - 1].name}>`);
|
|
343
|
+
}
|
|
344
|
+
return root;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const flat = arr => {
|
|
348
|
+
return arr.length === 1 ? arr[0] : arr;
|
|
349
|
+
};
|
|
350
|
+
function createHtml() {
|
|
351
|
+
const cache = new WeakMap();
|
|
352
|
+
const rawTextElements = new Set(RawTextElements);
|
|
353
|
+
rawTextElements.delete("template");
|
|
354
|
+
const walker = document.createTreeWalker(document, 129);
|
|
355
|
+
const createElement = name => {
|
|
356
|
+
return SVGElements.has(name) ? document.createElementNS("http://www.w3.org/2000/svg", name) : MathMLElements.has(name) ? document.createElementNS("http://www.w3.org/1998/Math/MathML", name) : document.createElement(name);
|
|
357
|
+
};
|
|
358
|
+
const createTaggedJSX = components => {
|
|
359
|
+
const tag = (strings, ...values) => {
|
|
360
|
+
const root = getCachedRoot(strings);
|
|
361
|
+
return renderChildren(root, values, components);
|
|
362
|
+
};
|
|
363
|
+
tag.components = components;
|
|
364
|
+
tag.jsx = tag;
|
|
365
|
+
tag.define = newComponents => {
|
|
366
|
+
return createTaggedJSX({
|
|
367
|
+
...components,
|
|
368
|
+
...newComponents
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
return tag;
|
|
372
|
+
};
|
|
373
|
+
const getCachedRoot = strings => {
|
|
374
|
+
let root = cache.get(strings);
|
|
375
|
+
if (!root) {
|
|
376
|
+
root = parse(tokenize(strings, rawTextElements), VoidElements);
|
|
377
|
+
buildTemplate(root, false);
|
|
378
|
+
cache.set(strings, root);
|
|
379
|
+
}
|
|
380
|
+
return root;
|
|
381
|
+
};
|
|
382
|
+
const buildTemplate = (node, insideTemplate) => {
|
|
383
|
+
if (node.type === ELEMENT_NODE) {
|
|
384
|
+
if (!insideTemplate) {
|
|
385
|
+
const template = document.createElement("template");
|
|
386
|
+
template.content.appendChild(buildNodes(node));
|
|
387
|
+
node.template = template;
|
|
388
|
+
insideTemplate = true;
|
|
389
|
+
}
|
|
390
|
+
node.children.forEach(child => buildTemplate(child, insideTemplate));
|
|
391
|
+
} else if (node.type === COMPONENT_NODE || node.type === ROOT_NODE) {
|
|
392
|
+
node.children.forEach(child => buildTemplate(child, false));
|
|
393
|
+
} else if (node.type === TEXT_NODE && !insideTemplate) {
|
|
394
|
+
textTemplate.innerHTML = node.value;
|
|
395
|
+
node.value = textTemplate.content.textContent ?? "";
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
const textTemplate = document.createElement("template");
|
|
399
|
+
const buildNodes = node => {
|
|
400
|
+
switch (node.type) {
|
|
401
|
+
case TEXT_NODE:
|
|
402
|
+
textTemplate.innerHTML = node.value;
|
|
403
|
+
return document.createTextNode(textTemplate.content.textContent ?? "");
|
|
404
|
+
case EXPRESSION_NODE:
|
|
405
|
+
return document.createComment("+");
|
|
406
|
+
case COMPONENT_NODE:
|
|
407
|
+
return document.createComment(node.name);
|
|
408
|
+
case ELEMENT_NODE:
|
|
409
|
+
let hasSpread = false;
|
|
410
|
+
const elem = createElement(node.name);
|
|
411
|
+
const claimAttr = node.name === "a" ? "href" : node.name === "form" ? "action" : undefined;
|
|
412
|
+
node.props = node.props.filter(prop => {
|
|
413
|
+
if (prop.type === STATIC_PROP) {
|
|
414
|
+
if (prop.name.startsWith("prop:")) return true;
|
|
415
|
+
elem.setAttribute(prop.name, prop.value);
|
|
416
|
+
if (!hasSpread && prop.name === claimAttr) node.claim = true;
|
|
417
|
+
return hasSpread;
|
|
418
|
+
} else if (prop.type === BOOLEAN_PROP) {
|
|
419
|
+
elem.setAttribute(prop.name, "");
|
|
420
|
+
if (!hasSpread && prop.name === claimAttr) node.claim = true;
|
|
421
|
+
return hasSpread;
|
|
422
|
+
} else if (prop.type === SPREAD_PROP) {
|
|
423
|
+
hasSpread = true;
|
|
424
|
+
return hasSpread;
|
|
425
|
+
}
|
|
426
|
+
return true;
|
|
427
|
+
});
|
|
428
|
+
const childRoot = node.name === "template" ? elem.content : elem;
|
|
429
|
+
childRoot.append(...node.children.map(buildNodes));
|
|
430
|
+
return elem;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
const renderNode = (node, values, components) => {
|
|
434
|
+
switch (node.type) {
|
|
435
|
+
case TEXT_NODE:
|
|
436
|
+
return node.value;
|
|
437
|
+
case EXPRESSION_NODE:
|
|
438
|
+
return values[node.value];
|
|
439
|
+
case COMPONENT_NODE:
|
|
440
|
+
const component = typeof node.name === "string" ? components[node.name] : values[node.name];
|
|
441
|
+
if (component && typeof component === "function") {
|
|
442
|
+
return createComponent(component, gatherProps(node, values, components));
|
|
443
|
+
} else {
|
|
444
|
+
throw new Error(`Component "${node.name}" not found in registry`);
|
|
445
|
+
}
|
|
446
|
+
case ELEMENT_NODE:
|
|
447
|
+
const element = renderChildren(node, values, components);
|
|
448
|
+
const props = gatherProps(node, values, components);
|
|
449
|
+
spread(element, props, true);
|
|
450
|
+
if (node.claim) claimElement(element);
|
|
451
|
+
return element;
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
const renderChildren = (node, values, components) => {
|
|
455
|
+
if (node.type !== ELEMENT_NODE || !node.template) {
|
|
456
|
+
return flat(node.children.map(n => renderNode(n, values, components)));
|
|
457
|
+
}
|
|
458
|
+
const element = node.template.content.firstChild.cloneNode(true);
|
|
459
|
+
walker.currentNode = element;
|
|
460
|
+
const walkNodes = (nodes, walker) => {
|
|
461
|
+
for (const node of nodes) {
|
|
462
|
+
if (node.type === ELEMENT_NODE || node.type === EXPRESSION_NODE || node.type === COMPONENT_NODE) {
|
|
463
|
+
const domNode = walker.nextNode();
|
|
464
|
+
if (node.type === EXPRESSION_NODE || node.type === COMPONENT_NODE) {
|
|
465
|
+
insert(domNode.parentNode, renderNode(node, values, components), domNode);
|
|
466
|
+
walker.currentNode = domNode;
|
|
467
|
+
} else {
|
|
468
|
+
if (node.props.length) {
|
|
469
|
+
const props = gatherProps(node, values, components);
|
|
470
|
+
spread(domNode, props, true);
|
|
471
|
+
}
|
|
472
|
+
if (node.claim) claimElement(domNode);
|
|
473
|
+
walkNodes(node.children, node.name === "template" ? document.createTreeWalker(domNode.content, 129) : walker);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
walkNodes(node.children, node.name === "template" ? document.createTreeWalker(element.content, 129) : walker);
|
|
479
|
+
return element;
|
|
480
|
+
};
|
|
481
|
+
const gatherProps = (node, values, components, props = {}) => {
|
|
482
|
+
for (const prop of node.props) {
|
|
483
|
+
switch (prop.type) {
|
|
484
|
+
case BOOLEAN_PROP:
|
|
485
|
+
props[prop.name] = true;
|
|
486
|
+
break;
|
|
487
|
+
case STATIC_PROP:
|
|
488
|
+
props[prop.name] = prop.value;
|
|
489
|
+
break;
|
|
490
|
+
case EXPRESSION_PROP:
|
|
491
|
+
applyGetter(props, prop.name, values[prop.value]);
|
|
492
|
+
break;
|
|
493
|
+
case SPREAD_PROP:
|
|
494
|
+
const spreadValue = values[prop.value];
|
|
495
|
+
if (!spreadValue || typeof spreadValue !== "object") throw new Error("Can only spread objects");
|
|
496
|
+
props = mergeProps(props, spreadValue);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (node.type === COMPONENT_NODE && node.children.length) {
|
|
501
|
+
Object.defineProperty(props, "children", {
|
|
502
|
+
get() {
|
|
503
|
+
return renderChildren(node, values, components);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return props;
|
|
508
|
+
};
|
|
509
|
+
const applyGetter = (props, name, value) => {
|
|
510
|
+
if (typeof value === "function" && value.length === 0 && name !== "ref" && !name.startsWith("on")) {
|
|
511
|
+
Object.defineProperty(props, name, {
|
|
512
|
+
get() {
|
|
513
|
+
return value();
|
|
514
|
+
},
|
|
515
|
+
enumerable: true
|
|
516
|
+
});
|
|
517
|
+
} else {
|
|
518
|
+
props[name] = value;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
return createTaggedJSX({});
|
|
522
|
+
}
|
|
523
|
+
const html = createHtml();
|
|
17
524
|
|
|
18
525
|
export { html as default };
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/html",
|
|
3
3
|
"description": "Tagged-template-literal templating for Solid — write components with no build step.",
|
|
4
|
-
"version": "2.0.0-rc.
|
|
4
|
+
"version": "2.0.0-rc.4",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://solidjs.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/solidjs/solid.git",
|
|
11
|
-
"directory": "packages/
|
|
11
|
+
"directory": "packages/html"
|
|
12
12
|
},
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public"
|
|
@@ -36,21 +36,21 @@
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
|
-
"peerDependencies": {
|
|
40
|
-
"@solidjs/web": "^2.0.0-rc.2"
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"@solidjs/web": "2.0.0-rc.2",
|
|
44
|
-
"solid-js": "2.0.0-rc.2"
|
|
45
|
-
},
|
|
46
39
|
"scripts": {
|
|
47
40
|
"build": "npm-run-all -nl build:*",
|
|
48
41
|
"build:clean": "rimraf dist/ coverage/",
|
|
49
42
|
"build:js": "rollup -c",
|
|
50
43
|
"types": "npm-run-all -nl types:clean types:html types:cjs",
|
|
51
44
|
"types:clean": "rimraf types/ types-cjs/",
|
|
52
|
-
"types:html": "tsc --project ./tsconfig.json
|
|
45
|
+
"types:html": "tsc --project ./tsconfig.json",
|
|
53
46
|
"types:cjs": "node ../../scripts/sync-dual-types.mjs ./types ./types-cjs",
|
|
54
47
|
"test": "vitest run"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@solidjs/web": "^2.0.0-rc.4"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@solidjs/web": "workspace:*",
|
|
54
|
+
"solid-js": "workspace:*"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|
package/types/index.d.ts
CHANGED
package/types/parse.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Token } from "./tokenize.js";
|
|
2
|
+
export declare const ROOT_NODE = 0;
|
|
3
|
+
export declare const ELEMENT_NODE = 1;
|
|
4
|
+
export declare const COMPONENT_NODE = 2;
|
|
5
|
+
export declare const TEXT_NODE = 3;
|
|
6
|
+
export declare const EXPRESSION_NODE = 4;
|
|
7
|
+
export declare const BOOLEAN_PROP = 0;
|
|
8
|
+
export declare const STATIC_PROP = 1;
|
|
9
|
+
export declare const EXPRESSION_PROP = 2;
|
|
10
|
+
export declare const SPREAD_PROP = 3;
|
|
11
|
+
export type NodeType = typeof ROOT_NODE | typeof ELEMENT_NODE | typeof COMPONENT_NODE | typeof TEXT_NODE | typeof EXPRESSION_NODE;
|
|
12
|
+
export type PropType = typeof BOOLEAN_PROP | typeof STATIC_PROP | typeof EXPRESSION_PROP | typeof SPREAD_PROP;
|
|
13
|
+
export type ChildNode = ElementNode | TextNode | ExpressionNode | ComponentNode;
|
|
14
|
+
export interface RootNode {
|
|
15
|
+
type: typeof ROOT_NODE;
|
|
16
|
+
children: ChildNode[];
|
|
17
|
+
}
|
|
18
|
+
export interface ElementNode {
|
|
19
|
+
type: typeof ELEMENT_NODE;
|
|
20
|
+
name: string;
|
|
21
|
+
props: PropNode[];
|
|
22
|
+
children: ChildNode[];
|
|
23
|
+
template?: HTMLTemplateElement;
|
|
24
|
+
/**
|
|
25
|
+
* Element-claim contract target (`a[href]`, `form[action]`, or a spread
|
|
26
|
+
* that may carry those). Stamped at template build — before static props
|
|
27
|
+
* are filtered out — so clones can be claimed even when the attribute was
|
|
28
|
+
* baked into the template.
|
|
29
|
+
*/
|
|
30
|
+
claim?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface ComponentNode {
|
|
33
|
+
type: typeof COMPONENT_NODE;
|
|
34
|
+
name: string | number;
|
|
35
|
+
props: PropNode[];
|
|
36
|
+
children: ChildNode[];
|
|
37
|
+
}
|
|
38
|
+
export interface TextNode {
|
|
39
|
+
type: typeof TEXT_NODE;
|
|
40
|
+
value: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ExpressionNode {
|
|
43
|
+
type: typeof EXPRESSION_NODE;
|
|
44
|
+
value: number;
|
|
45
|
+
}
|
|
46
|
+
export interface BooleanProp {
|
|
47
|
+
name: string;
|
|
48
|
+
type: typeof BOOLEAN_PROP;
|
|
49
|
+
value: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface StringProp {
|
|
52
|
+
name: string;
|
|
53
|
+
type: typeof STATIC_PROP;
|
|
54
|
+
value: string;
|
|
55
|
+
quote?: "'" | '"';
|
|
56
|
+
}
|
|
57
|
+
export interface ExpressionProp {
|
|
58
|
+
name: string;
|
|
59
|
+
type: typeof EXPRESSION_PROP;
|
|
60
|
+
value: number;
|
|
61
|
+
}
|
|
62
|
+
export interface SpreadProp {
|
|
63
|
+
type: typeof SPREAD_PROP;
|
|
64
|
+
value: number;
|
|
65
|
+
}
|
|
66
|
+
export type PropNode = BooleanProp | StringProp | ExpressionProp | SpreadProp;
|
|
67
|
+
export declare const parse: (tokens: Token[], voidElements: Set<string>) => RootNode;
|