@torpor/view 0.4.18 → 1.0.1
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 +41 -16
- package/dist/Cleanup-D0wvGW5d.d.cts +9 -0
- package/dist/Cleanup-D0wvGW5d.d.cts.map +1 -0
- package/dist/Cleanup-D0wvGW5d.d.mts +9 -0
- package/dist/Cleanup-D0wvGW5d.d.mts.map +1 -0
- package/dist/Component-0MH0zd-m.d.cts +11 -0
- package/dist/Component-0MH0zd-m.d.cts.map +1 -0
- package/dist/Component-0MH0zd-m.d.mts +11 -0
- package/dist/Component-0MH0zd-m.d.mts.map +1 -0
- package/dist/Effect-BTKurOpr.d.mts +381 -0
- package/dist/Effect-BTKurOpr.d.mts.map +1 -0
- package/dist/Effect-COl3aRJV.d.cts +381 -0
- package/dist/Effect-COl3aRJV.d.cts.map +1 -0
- package/dist/compile.cjs +1734 -739
- package/dist/compile.d.cts +85 -50
- package/dist/compile.d.cts.map +1 -1
- package/dist/compile.d.mts +85 -50
- package/dist/compile.d.mts.map +1 -1
- package/dist/compile.mjs +1737 -741
- package/dist/compile.mjs.map +1 -1
- package/dist/dev.cjs +62 -63
- package/dist/dev.d.cts +4 -3
- package/dist/dev.d.cts.map +1 -1
- package/dist/dev.d.mts +4 -3
- package/dist/dev.d.mts.map +1 -1
- package/dist/dev.mjs +61 -62
- package/dist/dev.mjs.map +1 -1
- package/dist/{devContext-B3yskfhA.cjs → devContext-BulTn3U5.cjs} +6 -9
- package/dist/{devContext-CQ8RhBX-.mjs → devContext-ZNsPyOgB.mjs} +3 -4
- package/dist/devContext-ZNsPyOgB.mjs.map +1 -0
- package/dist/fromWebSocket-1QOjcrHE.cjs +182 -0
- package/dist/fromWebSocket-B52kN3-W.d.mts +91 -0
- package/dist/fromWebSocket-B52kN3-W.d.mts.map +1 -0
- package/dist/fromWebSocket-DeB35Q4w.d.cts +91 -0
- package/dist/fromWebSocket-DeB35Q4w.d.cts.map +1 -0
- package/dist/fromWebSocket-Dxc_uwm1.mjs +137 -0
- package/dist/fromWebSocket-Dxc_uwm1.mjs.map +1 -0
- package/dist/index.cjs +2109 -494
- package/dist/index.d.cts +608 -112
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +608 -112
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2088 -490
- package/dist/index.mjs.map +1 -1
- package/dist/ssr.cjs +78 -17
- package/dist/ssr.d.cts +64 -11
- package/dist/ssr.d.cts.map +1 -1
- package/dist/ssr.d.mts +64 -11
- package/dist/ssr.d.mts.map +1 -1
- package/dist/ssr.mjs +67 -14
- package/dist/ssr.mjs.map +1 -1
- package/package.json +21 -19
- package/dist/Cleanup-CYsytTEc.d.cts +0 -9
- package/dist/Cleanup-CYsytTEc.d.cts.map +0 -1
- package/dist/Cleanup-CgjyN0lW.d.mts +0 -9
- package/dist/Cleanup-CgjyN0lW.d.mts.map +0 -1
- package/dist/Component-DmWGMgak.d.mts +0 -11
- package/dist/Component-DmWGMgak.d.mts.map +0 -1
- package/dist/Component-kGLRFYg2.d.cts +0 -11
- package/dist/Component-kGLRFYg2.d.cts.map +0 -1
- package/dist/Region-Bb6HAb-F.d.cts +0 -201
- package/dist/Region-Bb6HAb-F.d.cts.map +0 -1
- package/dist/Region-CTchywkr.d.mts +0 -201
- package/dist/Region-CTchywkr.d.mts.map +0 -1
- package/dist/devContext-CQ8RhBX-.mjs.map +0 -1
- package/dist/formatText-BNP3P38F.d.cts +0 -17
- package/dist/formatText-BNP3P38F.d.cts.map +0 -1
- package/dist/formatText-D6Ov8Ub0.d.mts +0 -17
- package/dist/formatText-D6Ov8Ub0.d.mts.map +0 -1
- package/dist/formatText-DTIRmc3X.mjs +0 -61
- package/dist/formatText-DTIRmc3X.mjs.map +0 -1
- package/dist/formatText-Dhl2O0Ee.cjs +0 -96
package/dist/compile.mjs
CHANGED
|
@@ -1,12 +1,209 @@
|
|
|
1
|
-
//#region src/compile/utils/
|
|
2
|
-
function
|
|
3
|
-
let
|
|
4
|
-
for (let j = start + 1; j < content.length; j++) if (content[j] === "`" && content[j - 1] !== "\\" && level === 0) return j;
|
|
5
|
-
else if (content[j] === "{" && (level > 0 || content[j - 1] === "$")) level += 1;
|
|
6
|
-
else if (content[j] === "}" && level > 0) level -= 1;
|
|
1
|
+
//#region src/compile/utils/endOfString.ts
|
|
2
|
+
function endOfString(char, content, start) {
|
|
3
|
+
for (let j = start + 1; j < content.length; j++) if (content[j] === char && content[j - 1] !== "\\") return j;
|
|
7
4
|
return content.length;
|
|
8
5
|
}
|
|
9
|
-
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/compile/utils/codeScanner.ts
|
|
8
|
+
/**
|
|
9
|
+
* Shared scanner for skipping over non-code parts of JavaScript expression
|
|
10
|
+
* text: string literals, template literals (including their interpolations),
|
|
11
|
+
* comments, and regex literals. Used everywhere the compiler scans
|
|
12
|
+
* expression text and needs to tell code from literal text.
|
|
13
|
+
*
|
|
14
|
+
* Note: this is for *code* contexts. For torpor template text (where quotes
|
|
15
|
+
* are literal characters and `{...}` are interpolation delimiters), use this
|
|
16
|
+
* only for the *interiors* of `{...}` expressions -- the template level
|
|
17
|
+
* itself has different rules.
|
|
18
|
+
*/
|
|
19
|
+
const REGEX_KEYWORDS = /* @__PURE__ */ new Set([
|
|
20
|
+
"return",
|
|
21
|
+
"typeof",
|
|
22
|
+
"instanceof",
|
|
23
|
+
"in",
|
|
24
|
+
"of",
|
|
25
|
+
"new",
|
|
26
|
+
"delete",
|
|
27
|
+
"void",
|
|
28
|
+
"throw",
|
|
29
|
+
"case",
|
|
30
|
+
"do",
|
|
31
|
+
"else",
|
|
32
|
+
"yield",
|
|
33
|
+
"await"
|
|
34
|
+
]);
|
|
35
|
+
/**
|
|
36
|
+
* If the character at `start` begins a string literal, a template literal, a
|
|
37
|
+
* comment (line or block), or a regex literal, returns the index just after
|
|
38
|
+
* it. Returns -1 if it doesn't (a `/` that is division, or any other
|
|
39
|
+
* character).
|
|
40
|
+
*/
|
|
41
|
+
function skipStringOrComment(value, start) {
|
|
42
|
+
return skipInRange(value, start);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Returns the [start, end) ranges of `value` that are code -- i.e. not the
|
|
46
|
+
* contents of string literals, regex literals, or comments. Template string
|
|
47
|
+
* contents are skipped, except for the contents of `${...}` interpolations,
|
|
48
|
+
* which are code again.
|
|
49
|
+
*/
|
|
50
|
+
function codeRanges(value) {
|
|
51
|
+
const ranges = [];
|
|
52
|
+
let codeStart = 0;
|
|
53
|
+
let i = 0;
|
|
54
|
+
while (i < value.length) {
|
|
55
|
+
const next = skipInRange(value, i, ranges, codeStart);
|
|
56
|
+
if (next !== -1) {
|
|
57
|
+
i = next;
|
|
58
|
+
codeStart = i;
|
|
59
|
+
} else i += 1;
|
|
60
|
+
}
|
|
61
|
+
pushRange(ranges, codeStart, value.length);
|
|
62
|
+
return ranges;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `skipStringOrComment` with optional collection of the interrupted code
|
|
66
|
+
* range (for `codeRanges`), so there is a single implementation of the scan
|
|
67
|
+
* order.
|
|
68
|
+
*/
|
|
69
|
+
function skipInRange(value, start, ranges, codeStart) {
|
|
70
|
+
const char = value[start];
|
|
71
|
+
if (char === "'" || char === "\"") {
|
|
72
|
+
if (ranges) pushRange(ranges, codeStart, start);
|
|
73
|
+
return endOfString(char, value, start) + 1;
|
|
74
|
+
}
|
|
75
|
+
if (char === "`") {
|
|
76
|
+
if (ranges) pushRange(ranges, codeStart, start);
|
|
77
|
+
return skipTemplateString(value, start, ranges);
|
|
78
|
+
}
|
|
79
|
+
if (char === "/" && value[start + 1] === "/") {
|
|
80
|
+
if (ranges) pushRange(ranges, codeStart, start);
|
|
81
|
+
const end = value.indexOf("\n", start + 2);
|
|
82
|
+
return end === -1 ? value.length : end;
|
|
83
|
+
}
|
|
84
|
+
if (char === "/" && value[start + 1] === "*") {
|
|
85
|
+
if (ranges) pushRange(ranges, codeStart, start);
|
|
86
|
+
const end = value.indexOf("*/", start + 2);
|
|
87
|
+
return end === -1 ? value.length : end + 2;
|
|
88
|
+
}
|
|
89
|
+
if (char === "/" && isRegexPosition(value, start)) {
|
|
90
|
+
if (ranges) pushRange(ranges, codeStart, start);
|
|
91
|
+
return skipRegexLiteral(value, start);
|
|
92
|
+
}
|
|
93
|
+
return -1;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Skips over a template string, given the index of its opening backtick, and
|
|
97
|
+
* returns the index just after the closing backtick. Handles escaped
|
|
98
|
+
* characters, and scans `${...}` interpolations correctly (nested strings,
|
|
99
|
+
* templates, comments and regexes within them can't desync the scan).
|
|
100
|
+
*/
|
|
101
|
+
function skipTemplateString(value, start, ranges) {
|
|
102
|
+
let i = start + 1;
|
|
103
|
+
while (i < value.length) {
|
|
104
|
+
const char = value[i];
|
|
105
|
+
if (char === "\\") i += 2;
|
|
106
|
+
else if (char === "`") return i + 1;
|
|
107
|
+
else if (char === "$" && value[i + 1] === "{") i = endOfInterpolation(value, i + 2, ranges) + 1;
|
|
108
|
+
else i += 1;
|
|
109
|
+
}
|
|
110
|
+
return value.length;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Skips over the code inside a `${...}` interpolation, given the index just
|
|
114
|
+
* after the `${`, collecting its code ranges when provided. Returns the
|
|
115
|
+
* index of the matching `}`.
|
|
116
|
+
*/
|
|
117
|
+
function endOfInterpolation(value, start, ranges) {
|
|
118
|
+
let level = 0;
|
|
119
|
+
let codeStart = start;
|
|
120
|
+
let i = start;
|
|
121
|
+
while (i < value.length) {
|
|
122
|
+
const char = value[i];
|
|
123
|
+
if (char === "{") {
|
|
124
|
+
level += 1;
|
|
125
|
+
i += 1;
|
|
126
|
+
} else if (char === "}") {
|
|
127
|
+
if (level === 0) {
|
|
128
|
+
pushRange(ranges, codeStart, i);
|
|
129
|
+
return i;
|
|
130
|
+
}
|
|
131
|
+
level -= 1;
|
|
132
|
+
i += 1;
|
|
133
|
+
} else {
|
|
134
|
+
const next = skipInRange(value, i, ranges, codeStart);
|
|
135
|
+
if (next !== -1) {
|
|
136
|
+
i = next;
|
|
137
|
+
codeStart = i;
|
|
138
|
+
} else i += 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
pushRange(ranges, codeStart, value.length);
|
|
142
|
+
return value.length;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Decides whether the `/` at `slashIndex` starts a regex literal (rather
|
|
146
|
+
* than being division), by looking at the previous significant character.
|
|
147
|
+
* The standard lexical heuristic -- not a full parse, but correct for
|
|
148
|
+
* real-world code.
|
|
149
|
+
*/
|
|
150
|
+
function isRegexPosition(value, slashIndex) {
|
|
151
|
+
let i = slashIndex - 1;
|
|
152
|
+
while (i >= 0) {
|
|
153
|
+
const char = value[i];
|
|
154
|
+
if (char === " " || char === " " || char === "\n" || char === "\r") i -= 1;
|
|
155
|
+
else if (char === "/" && value[i - 1] === "*") {
|
|
156
|
+
const open = value.lastIndexOf("/*", i - 2);
|
|
157
|
+
if (open === -1) return true;
|
|
158
|
+
i = open - 1;
|
|
159
|
+
} else break;
|
|
160
|
+
}
|
|
161
|
+
if (i < 0) return true;
|
|
162
|
+
const char = value[i];
|
|
163
|
+
if (char === ")" || char === "]" || char === "}" || char === "\"" || char === "'" || char === "`") return false;
|
|
164
|
+
if (/[a-z0-9_$]/i.test(char)) {
|
|
165
|
+
let wordStart = i;
|
|
166
|
+
while (wordStart >= 0 && /[a-z0-9_$]/i.test(value[wordStart])) wordStart -= 1;
|
|
167
|
+
return REGEX_KEYWORDS.has(value.substring(wordStart + 1, i + 1));
|
|
168
|
+
}
|
|
169
|
+
if (char === "+" || char === "-") {
|
|
170
|
+
if (value[i - 1] === char) {
|
|
171
|
+
const before = value[i - 2];
|
|
172
|
+
return !(before && /[a-z0-9_$)\]]/i.test(before));
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Skips over a regex literal, given the index of its opening `/`, and
|
|
180
|
+
* returns the index just after its flags. Returns -1 if the literal is
|
|
181
|
+
* unterminated (in which case the `/` is treated as division).
|
|
182
|
+
*/
|
|
183
|
+
function skipRegexLiteral(value, start) {
|
|
184
|
+
let i = start + 1;
|
|
185
|
+
let inClass = false;
|
|
186
|
+
while (i < value.length) {
|
|
187
|
+
const char = value[i];
|
|
188
|
+
if (char === "\\") i += 2;
|
|
189
|
+
else if (char === "\n") return -1;
|
|
190
|
+
else if (char === "[") {
|
|
191
|
+
inClass = true;
|
|
192
|
+
i += 1;
|
|
193
|
+
} else if (char === "]") {
|
|
194
|
+
inClass = false;
|
|
195
|
+
i += 1;
|
|
196
|
+
} else if (char === "/" && !inClass) {
|
|
197
|
+
i += 1;
|
|
198
|
+
while (i < value.length && /[a-z]/i.test(value[i])) i += 1;
|
|
199
|
+
return i;
|
|
200
|
+
} else i += 1;
|
|
201
|
+
}
|
|
202
|
+
return -1;
|
|
203
|
+
}
|
|
204
|
+
function pushRange(ranges, start, end) {
|
|
205
|
+
if (ranges && end > start) ranges.push([start, end]);
|
|
206
|
+
}
|
|
10
207
|
//#endregion
|
|
11
208
|
//#region src/compile/utils/Builder.ts
|
|
12
209
|
var Builder = class {
|
|
@@ -23,7 +220,7 @@ var Builder = class {
|
|
|
23
220
|
append(text) {
|
|
24
221
|
if (this.#mapped) {
|
|
25
222
|
this.lineMap.push(this.#text.length);
|
|
26
|
-
for (let i
|
|
223
|
+
for (let i = 0; i < text.length; i++) if (text[i] === "\n") this.lineMap.push(this.#text.length + i);
|
|
27
224
|
this.#text += "\n" + text;
|
|
28
225
|
return;
|
|
29
226
|
}
|
|
@@ -39,12 +236,12 @@ var Builder = class {
|
|
|
39
236
|
} else {
|
|
40
237
|
if (text[i] === "}" || text[i] === ")" || text[i] === "]") this.#space -= 1;
|
|
41
238
|
this.#text += " ".repeat(Math.max(0, this.#space));
|
|
42
|
-
const start
|
|
239
|
+
const start = i;
|
|
43
240
|
while (i < text.length && text[i] !== "\n") {
|
|
44
|
-
if (text[i] === "`") i =
|
|
241
|
+
if (text[i] === "`") i = skipStringOrComment(text, i) - 1;
|
|
45
242
|
i++;
|
|
46
243
|
}
|
|
47
|
-
const line = text.substring(start
|
|
244
|
+
const line = text.substring(start, i + 1);
|
|
48
245
|
if (line.startsWith("*")) this.#text += " ";
|
|
49
246
|
this.#text += line;
|
|
50
247
|
if (text[i - 1] === "{" || text[i - 1] === "(" || text[i - 1] === "[") this.#space += 1;
|
|
@@ -57,7 +254,99 @@ var Builder = class {
|
|
|
57
254
|
return this.#text;
|
|
58
255
|
}
|
|
59
256
|
};
|
|
60
|
-
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region src/compile/utils/isParentNode.ts
|
|
259
|
+
function isParentNode(node) {
|
|
260
|
+
return "children" in node;
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/compile/utils/collectMarkupExpressions.ts
|
|
264
|
+
/**
|
|
265
|
+
* Walks a markup tree and returns every expression string it contains —
|
|
266
|
+
* `@control (...)` statements, reactive attribute values, and `{...}` text
|
|
267
|
+
* interpolations — concatenated. Used for import detection: `$`-primitives
|
|
268
|
+
* used inside markup (e.g. `@if ($pending(() => $state.data))`,
|
|
269
|
+
* `disabled={$pending(...)}`) must be imported, and the script-only scan in
|
|
270
|
+
* `buildCode`/`buildServerCode` doesn't see them.
|
|
271
|
+
*
|
|
272
|
+
* Static attribute values and literal text outside `{...}` are excluded, so a
|
|
273
|
+
* plain string containing e.g. `$pending` does not produce a spurious import.
|
|
274
|
+
*/
|
|
275
|
+
function collectMarkupExpressions(root) {
|
|
276
|
+
let out = "";
|
|
277
|
+
const walk = (node) => {
|
|
278
|
+
if (node.type === "control") out += node.statement + "\n";
|
|
279
|
+
if (node.type === "element" || node.type === "component" || node.type === "special") {
|
|
280
|
+
for (const attribute of node.attributes) if (attribute.reactive && attribute.value !== void 0) out += attribute.value + "\n";
|
|
281
|
+
}
|
|
282
|
+
if (node.type === "text") out += extractInterpolations(node.content) + "\n";
|
|
283
|
+
if (isParentNode(node)) for (const child of node.children) walk(child);
|
|
284
|
+
};
|
|
285
|
+
walk(root);
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Extracts the `{expr}` interpolation segments from text content (level 0
|
|
290
|
+
* text is prose and is dropped), skipping string literals, template strings,
|
|
291
|
+
* comments and regex literals inside the braces (they're copied into the
|
|
292
|
+
* output verbatim).
|
|
293
|
+
*/
|
|
294
|
+
function extractInterpolations(content) {
|
|
295
|
+
let out = "";
|
|
296
|
+
let level = 0;
|
|
297
|
+
for (let i = 0; i < content.length; i++) {
|
|
298
|
+
const char = content[i];
|
|
299
|
+
if (char === "{") {
|
|
300
|
+
if (level === 0) out += "\n";
|
|
301
|
+
level++;
|
|
302
|
+
out += char;
|
|
303
|
+
} else if (char === "}") {
|
|
304
|
+
level--;
|
|
305
|
+
out += char;
|
|
306
|
+
} else if (level > 0) {
|
|
307
|
+
const skipped = skipStringOrComment(content, i);
|
|
308
|
+
if (skipped !== -1) {
|
|
309
|
+
out += content.substring(i, skipped);
|
|
310
|
+
i = skipped - 1;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
out += char;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/compile/utils/markupRendersComponent.ts
|
|
320
|
+
/**
|
|
321
|
+
* Returns true if `markup` renders anything that forwards the component's
|
|
322
|
+
* `$context` parameter: a child component invocation (`<Child />`), a
|
|
323
|
+
* self-recursive `<@component self>`, or a `<slot>` definition (whose
|
|
324
|
+
* generated render call passes `$context` through to the filling parent).
|
|
325
|
+
*
|
|
326
|
+
* `contextProps` only captures *direct* `$context.x` reads in the script, so
|
|
327
|
+
* this is needed to decide whether the `$context` parameter is actually used:
|
|
328
|
+
* every child-component / slot render call passes `$context` positionally
|
|
329
|
+
* (see buildComponentNode / buildSlotNode and their server counterparts),
|
|
330
|
+
* even when the component never names `$context` explicitly.
|
|
331
|
+
*
|
|
332
|
+
* Bias is towards `true`: when uncertain we keep `$context` named, which is
|
|
333
|
+
* always safe at the cost of a missed unused-parameter cleanup.
|
|
334
|
+
*/
|
|
335
|
+
function markupRendersComponent(markup) {
|
|
336
|
+
return nodeRendersComponent(markup);
|
|
337
|
+
}
|
|
338
|
+
function nodeRendersComponent(node) {
|
|
339
|
+
if (node.type === "component") return true;
|
|
340
|
+
if (node.type === "special") {
|
|
341
|
+
const tagName = node.tagName;
|
|
342
|
+
if (tagName === "@component" || tagName === "slot") return true;
|
|
343
|
+
}
|
|
344
|
+
const children = node.children;
|
|
345
|
+
if (children) {
|
|
346
|
+
for (const child of children) if (nodeRendersComponent(child)) return true;
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
61
350
|
//#endregion
|
|
62
351
|
//#region src/compile/build/client/addMappedText.ts
|
|
63
352
|
function addMappedText(preText, text, postText, span, status, b) {
|
|
@@ -75,7 +364,6 @@ function addMappedText(preText, text, postText, span, status, b) {
|
|
|
75
364
|
});
|
|
76
365
|
} else b.append(preText + text + postText);
|
|
77
366
|
}
|
|
78
|
-
|
|
79
367
|
//#endregion
|
|
80
368
|
//#region src/compile/build/client/addPopDevBoundary.ts
|
|
81
369
|
function addPopDevBoundary(status, b) {
|
|
@@ -85,7 +373,6 @@ function addPopDevBoundary(status, b) {
|
|
|
85
373
|
b.append(`t_pop_dev_bound();`);
|
|
86
374
|
}
|
|
87
375
|
}
|
|
88
|
-
|
|
89
376
|
//#endregion
|
|
90
377
|
//#region src/compile/build/client/addPushDevBoundary.ts
|
|
91
378
|
function addPushDevBoundary(type, name, status, b) {
|
|
@@ -99,14 +386,73 @@ function addPushDevBoundary(type, name, status, b) {
|
|
|
99
386
|
b.append("");
|
|
100
387
|
}
|
|
101
388
|
}
|
|
102
|
-
|
|
103
389
|
//#endregion
|
|
104
390
|
//#region src/compile/types/comments.ts
|
|
105
|
-
const ANCHOR_COMMENT = "<!>";
|
|
106
391
|
const HYDRATION_START_COMMENT = "<![>";
|
|
107
392
|
const HYDRATION_END_COMMENT = "<!]>";
|
|
108
393
|
const HYDRATION_BREAK_COMMENT = "<!^>";
|
|
109
|
-
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/compile/utils/isControlNode.ts
|
|
396
|
+
function isControlNode(node) {
|
|
397
|
+
return node.type === "control";
|
|
398
|
+
}
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/compile/utils/isElementNode.ts
|
|
401
|
+
function isElementNode(node) {
|
|
402
|
+
return node.type === "element";
|
|
403
|
+
}
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/compile/utils/nonRenderingOperations.ts
|
|
406
|
+
/**
|
|
407
|
+
* Control operations that produce no DOM output within a fragment.
|
|
408
|
+
*
|
|
409
|
+
* Used by both the fragment builder (to find the first child that actually
|
|
410
|
+
* renders) and the whitespace trimmer (so that whitespace adjacent to these
|
|
411
|
+
* nodes is treated as leading/trailing whitespace of the container, rather
|
|
412
|
+
* than as significant inter-sibling whitespace — without this, every row of
|
|
413
|
+
* a `@for { @key = ...; <tr>...</tr> }` would emit a leading whitespace
|
|
414
|
+
* text node, since the whitespace sits between the @key and the <tr>).
|
|
415
|
+
*/
|
|
416
|
+
const NON_RENDERING_OPERATIONS = /* @__PURE__ */ new Set([
|
|
417
|
+
"@key",
|
|
418
|
+
"@const",
|
|
419
|
+
"@console",
|
|
420
|
+
"@debugger",
|
|
421
|
+
"@function",
|
|
422
|
+
"@async function"
|
|
423
|
+
]);
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/compile/utils/getSingleElementRoot.ts
|
|
426
|
+
/**
|
|
427
|
+
* Returns the single rendering `Element` root child of `children` if the
|
|
428
|
+
* fragment is "single-root element" — i.e. it has exactly one child that
|
|
429
|
+
* renders to the DOM, that child is an `Element`, and every other child is a
|
|
430
|
+
* non-rendering control node (`@key`, `@const`, …) or a comment. Returns
|
|
431
|
+
* `undefined` otherwise.
|
|
432
|
+
*
|
|
433
|
+
* When this returns an `Element`, the compiler emits the
|
|
434
|
+
* `t_fragment_el` / `t_root_el` / `t_add_element` codegen path, which clones
|
|
435
|
+
* the cached template's `firstElementChild` directly into the parent and
|
|
436
|
+
* skips the per-instance `DocumentFragment` allocation that `getFragment`
|
|
437
|
+
* produces.
|
|
438
|
+
*
|
|
439
|
+
* Text-root fragments (`{row.label}` standalone inside a `@for` body) are
|
|
440
|
+
* intentionally excluded: their root is a `Text`, not an `Element`, so
|
|
441
|
+
* `template.content.firstElementChild` would be `null`. Multi-root fragments
|
|
442
|
+
* (`<tr>...</tr><tr>...</tr>`) are excluded because no single element
|
|
443
|
+
* represents the whole fragment.
|
|
444
|
+
*/
|
|
445
|
+
function getSingleElementRoot(children) {
|
|
446
|
+
let renderingRoot;
|
|
447
|
+
for (const child of children) {
|
|
448
|
+
if (child.type === "comment") continue;
|
|
449
|
+
if (isControlNode(child) && NON_RENDERING_OPERATIONS.has(child.operation)) continue;
|
|
450
|
+
if (renderingRoot) return void 0;
|
|
451
|
+
if (!isElementNode(child)) return void 0;
|
|
452
|
+
renderingRoot = child;
|
|
453
|
+
}
|
|
454
|
+
return renderingRoot;
|
|
455
|
+
}
|
|
110
456
|
//#endregion
|
|
111
457
|
//#region src/compile/utils/isReactive.ts
|
|
112
458
|
/**
|
|
@@ -119,7 +465,6 @@ const HYDRATION_BREAK_COMMENT = "<!^>";
|
|
|
119
465
|
function isReactive(text) {
|
|
120
466
|
return text.includes("{") && text.includes("}");
|
|
121
467
|
}
|
|
122
|
-
|
|
123
468
|
//#endregion
|
|
124
469
|
//#region src/compile/utils/trimQuotes.ts
|
|
125
470
|
/**
|
|
@@ -138,7 +483,24 @@ function trimQuotes(text) {
|
|
|
138
483
|
}
|
|
139
484
|
return text.substring(start, end);
|
|
140
485
|
}
|
|
141
|
-
|
|
486
|
+
//#endregion
|
|
487
|
+
//#region src/compile/utils/voidTags.ts
|
|
488
|
+
const voidTags = [
|
|
489
|
+
"area",
|
|
490
|
+
"base",
|
|
491
|
+
"br",
|
|
492
|
+
"col",
|
|
493
|
+
"embed",
|
|
494
|
+
"hr",
|
|
495
|
+
"img",
|
|
496
|
+
"input",
|
|
497
|
+
"link",
|
|
498
|
+
"meta",
|
|
499
|
+
"param",
|
|
500
|
+
"source",
|
|
501
|
+
"track",
|
|
502
|
+
"wbr"
|
|
503
|
+
];
|
|
142
504
|
//#endregion
|
|
143
505
|
//#region src/compile/build/client/buildFragmentText.ts
|
|
144
506
|
/**
|
|
@@ -147,7 +509,10 @@ function trimQuotes(text) {
|
|
|
147
509
|
function buildFragmentText(node, status, b) {
|
|
148
510
|
const fragments = [];
|
|
149
511
|
buildNodeFragmentText(node, status, fragments);
|
|
150
|
-
if (fragments.length)
|
|
512
|
+
if (fragments.length) {
|
|
513
|
+
if (fragments.some((f) => !f.singleRootElement && f.usesFragmentCache !== false)) b.append(`const t_fragments: DocumentFragment[] = [];`);
|
|
514
|
+
if (fragments.some((f) => f.singleRootElement)) b.append(`const t_fragment_els: Element[] = [];`);
|
|
515
|
+
}
|
|
151
516
|
}
|
|
152
517
|
function buildNodeFragmentText(node, status, fragments, currentFragment) {
|
|
153
518
|
switch (node.type) {
|
|
@@ -184,7 +549,8 @@ function buildRootFragmentText(node, status, fragments) {
|
|
|
184
549
|
ns: status.ns,
|
|
185
550
|
effects: [],
|
|
186
551
|
events: [],
|
|
187
|
-
animations: []
|
|
552
|
+
animations: [],
|
|
553
|
+
singleRootElement: getSingleElementRoot(node.children) !== void 0
|
|
188
554
|
};
|
|
189
555
|
fragments.push(node.fragment);
|
|
190
556
|
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
@@ -195,19 +561,26 @@ function buildControlFragmentText(node, status, fragments, currentFragment) {
|
|
|
195
561
|
case "@switch group":
|
|
196
562
|
case "@for group":
|
|
197
563
|
case "@await group":
|
|
564
|
+
case "@try group":
|
|
198
565
|
case "@replace group":
|
|
199
566
|
case "@html group":
|
|
200
|
-
currentFragment.text +=
|
|
567
|
+
currentFragment.text += "<!>";
|
|
201
568
|
for (let child of node.children) buildNodeFragmentText(child, status, fragments, currentFragment);
|
|
202
569
|
break;
|
|
203
570
|
default:
|
|
571
|
+
if (NON_RENDERING_OPERATIONS.has(node.operation)) {
|
|
572
|
+
for (let child of node.children) buildNodeFragmentText(child, status, fragments, currentFragment);
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
204
575
|
node.fragment = {
|
|
205
576
|
number: fragments.length,
|
|
206
577
|
text: "",
|
|
207
578
|
ns: status.ns,
|
|
208
579
|
effects: [],
|
|
209
580
|
events: [],
|
|
210
|
-
animations: []
|
|
581
|
+
animations: [],
|
|
582
|
+
singleRootElement: getSingleElementRoot(node.children) !== void 0,
|
|
583
|
+
usesFragmentCache: node.operation !== "@html"
|
|
211
584
|
};
|
|
212
585
|
fragments.push(node.fragment);
|
|
213
586
|
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
@@ -215,7 +588,7 @@ function buildControlFragmentText(node, status, fragments, currentFragment) {
|
|
|
215
588
|
}
|
|
216
589
|
}
|
|
217
590
|
function buildComponentFragmentText(node, status, fragments, currentFragment) {
|
|
218
|
-
currentFragment.text +=
|
|
591
|
+
currentFragment.text += "<!>";
|
|
219
592
|
if (node.children.length) {
|
|
220
593
|
node.fragment = {
|
|
221
594
|
number: fragments.length,
|
|
@@ -223,16 +596,18 @@ function buildComponentFragmentText(node, status, fragments, currentFragment) {
|
|
|
223
596
|
ns: status.ns,
|
|
224
597
|
effects: [],
|
|
225
598
|
events: [],
|
|
226
|
-
animations: []
|
|
599
|
+
animations: [],
|
|
600
|
+
singleRootElement: getSingleElementRoot(node.children) !== void 0
|
|
227
601
|
};
|
|
228
602
|
fragments.push(node.fragment);
|
|
229
603
|
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
230
604
|
}
|
|
231
605
|
}
|
|
232
606
|
function buildElementFragmentText(node, status, fragments, currentFragment) {
|
|
607
|
+
const tagName = node.tagName === "@element" ? "el" : node.tagName;
|
|
233
608
|
let needsClass = node.scopeStyles;
|
|
234
|
-
currentFragment.text += `<${
|
|
235
|
-
let attributesText = node.attributes.filter((a) => !a.name.startsWith("on")).map((a) => {
|
|
609
|
+
currentFragment.text += `<${tagName}`;
|
|
610
|
+
let attributesText = node.attributes.filter((a) => !a.name.startsWith("on")).filter((a) => !(a.name === "self" && node.tagName === "@element")).map((a) => {
|
|
236
611
|
if (a.value && a.reactive) {
|
|
237
612
|
if (a.name.startsWith("data-")) return `${a.name}=""`;
|
|
238
613
|
} else if (a.name === "class" && node.scopeStyles) {
|
|
@@ -244,29 +619,33 @@ function buildElementFragmentText(node, status, fragments, currentFragment) {
|
|
|
244
619
|
if (attributesText) currentFragment.text += " " + attributesText;
|
|
245
620
|
if (needsClass) currentFragment.text += ` class="torp-${status.styleHash}"`;
|
|
246
621
|
currentFragment.text += ">";
|
|
247
|
-
|
|
248
|
-
|
|
622
|
+
if (!voidTags.includes(node.tagName)) {
|
|
623
|
+
for (let child of node.children) buildNodeFragmentText(child, status, fragments, currentFragment);
|
|
624
|
+
currentFragment.text += `</${tagName}>`;
|
|
625
|
+
}
|
|
249
626
|
}
|
|
250
627
|
function buildSpecialFragmentText(node, status, fragments, currentFragment) {
|
|
251
628
|
switch (node.tagName) {
|
|
252
629
|
case "slot":
|
|
253
|
-
currentFragment.text +=
|
|
630
|
+
currentFragment.text += "<!>";
|
|
254
631
|
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
255
632
|
break;
|
|
256
633
|
case "fill":
|
|
634
|
+
case "filldef":
|
|
257
635
|
node.fragment = {
|
|
258
636
|
number: fragments.length,
|
|
259
637
|
text: "",
|
|
260
638
|
ns: status.ns,
|
|
261
639
|
effects: [],
|
|
262
640
|
events: [],
|
|
263
|
-
animations: []
|
|
641
|
+
animations: [],
|
|
642
|
+
singleRootElement: getSingleElementRoot(node.children) !== void 0
|
|
264
643
|
};
|
|
265
644
|
fragments.push(node.fragment);
|
|
266
645
|
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
267
646
|
break;
|
|
268
647
|
case "@element":
|
|
269
|
-
|
|
648
|
+
buildElementFragmentText(node, status, fragments, currentFragment);
|
|
270
649
|
break;
|
|
271
650
|
case "@component":
|
|
272
651
|
buildComponentFragmentText(node, status, fragments, currentFragment);
|
|
@@ -274,13 +653,67 @@ function buildSpecialFragmentText(node, status, fragments, currentFragment) {
|
|
|
274
653
|
default: throw new Error(`Invalid special node: ${node.tagName}`);
|
|
275
654
|
}
|
|
276
655
|
}
|
|
277
|
-
|
|
656
|
+
//#endregion
|
|
657
|
+
//#region src/compile/utils/bindingTarget.ts
|
|
658
|
+
/**
|
|
659
|
+
* Builds the statements that write a component's prop value back to the
|
|
660
|
+
* expression it is bound to (e.g. for `&name={$state.user?.name}`).
|
|
661
|
+
*
|
|
662
|
+
* The bound expression may use optional chaining, which can't be assigned to
|
|
663
|
+
* directly. In that case the write is compiled to a guarded assignment: the
|
|
664
|
+
* prop value is read first (so the write re-runs when the child changes it),
|
|
665
|
+
* the target object is read with `$peek` (so that replacing it doesn't re-run
|
|
666
|
+
* the write with a stale prop value), and the value is written through it
|
|
667
|
+
* when it exists:
|
|
668
|
+
*
|
|
669
|
+
* ```
|
|
670
|
+
* bindingTarget("$state.user?.name", `props["name"]`, "name", imports)
|
|
671
|
+
* // => const t_bind_name = props["name"];
|
|
672
|
+
* // const t_target_name = $peek(() => $state.user);
|
|
673
|
+
* // if (t_target_name != null) t_target_name.name = t_bind_name;
|
|
674
|
+
* ```
|
|
675
|
+
*
|
|
676
|
+
* @param value The bound expression
|
|
677
|
+
* @param right The value to assign to it
|
|
678
|
+
* @param name The prop name, used to make the temp variable names unique
|
|
679
|
+
* @param imports The set of runtime imports to add `$peek` to when the
|
|
680
|
+
* expression needs a guarded write
|
|
681
|
+
*
|
|
682
|
+
* @returns The assignment statements
|
|
683
|
+
*/
|
|
684
|
+
function bindingTarget(value, right, name, imports) {
|
|
685
|
+
let lastChainStart = -1;
|
|
686
|
+
for (let i = 0; i < value.length; i++) {
|
|
687
|
+
let char = value[i];
|
|
688
|
+
if (char === "\"" || char === "'" || char === "`" || char === "/") {
|
|
689
|
+
const skipped = skipStringOrComment(value, i);
|
|
690
|
+
if (skipped !== -1) {
|
|
691
|
+
i = skipped - 1;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (char === "?" && value[i + 1] === "." && !/[0-9]/.test(value[i + 2] ?? "")) {
|
|
696
|
+
let next = value[i + 2];
|
|
697
|
+
if (next === "(") throw new Error(`Cannot bind to "${value}": optional calls are not supported in binding expressions`);
|
|
698
|
+
lastChainStart = i;
|
|
699
|
+
if (next === "[") i += 1;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (lastChainStart === -1) return `${value} = ${right};`;
|
|
704
|
+
let guard = value.substring(0, lastChainStart);
|
|
705
|
+
let next = value[lastChainStart + 2];
|
|
706
|
+
if (next !== "[" && !/[A-Za-z0-9_$]/.test(next ?? "")) throw new Error(`Cannot bind to "${value}": unsupported optional chain in binding expression`);
|
|
707
|
+
let suffix = value.substring(lastChainStart + (next === "[" ? 2 : 1));
|
|
708
|
+
let varSuffix = name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
709
|
+
imports?.add("$peek");
|
|
710
|
+
return `const t_bind_${varSuffix} = ${right};\nconst t_target_${varSuffix} = $peek(() => ${guard});\nif (t_target_${varSuffix} != null) t_target_${varSuffix}${suffix} = t_bind_${varSuffix};`;
|
|
711
|
+
}
|
|
278
712
|
//#endregion
|
|
279
713
|
//#region src/compile/utils/isSpecialNode.ts
|
|
280
714
|
function isSpecialNode(node) {
|
|
281
715
|
return node.type === "special";
|
|
282
716
|
}
|
|
283
|
-
|
|
284
717
|
//#endregion
|
|
285
718
|
//#region src/compile/build/utils/nextVarName.ts
|
|
286
719
|
/**
|
|
@@ -298,7 +731,6 @@ function nextVarName(name, status) {
|
|
|
298
731
|
status.varNames[name] += 1;
|
|
299
732
|
return varName;
|
|
300
733
|
}
|
|
301
|
-
|
|
302
734
|
//#endregion
|
|
303
735
|
//#region src/compile/build/client/addMappedTextWithOffsets.ts
|
|
304
736
|
function addMappedTextWithOffsets(preText, text, postText, spans, offsets, lengths, status, b) {
|
|
@@ -320,35 +752,50 @@ function addMappedTextWithOffsets(preText, text, postText, spans, offsets, lengt
|
|
|
320
752
|
}
|
|
321
753
|
} else b.append(preText + text + postText);
|
|
322
754
|
}
|
|
323
|
-
|
|
324
755
|
//#endregion
|
|
325
756
|
//#region src/compile/build/client/buildAddFragment.ts
|
|
326
757
|
function buildAddFragment(node, status, b, parentName, anchorName) {
|
|
327
758
|
if (node.fragment) {
|
|
328
759
|
const fragment = node.fragment;
|
|
329
760
|
const fragmentName = `t_fragment_${fragment.number}`;
|
|
330
|
-
status.imports.add("t_add_fragment");
|
|
331
761
|
if (fragment.effects.length > 0) {
|
|
332
762
|
status.imports.add("$run");
|
|
763
|
+
let forVarMask;
|
|
764
|
+
for (let effect of fragment.effects) if (effect.forVarMask !== void 0) {
|
|
765
|
+
if (forVarMask === void 0) forVarMask = 0;
|
|
766
|
+
forVarMask |= effect.forVarMask;
|
|
767
|
+
}
|
|
768
|
+
let trailing = "";
|
|
769
|
+
if (forVarMask !== void 0) trailing = `, undefined, { forVarMask: ${forVarMask} }`;
|
|
333
770
|
b.append("$run(() => {");
|
|
334
771
|
if (status.options.mapped === true) for (let effect of fragment.effects) addMappedTextWithOffsets("", effect.functionBody, "", effect.spans, effect.offsets, effect.lengths, status, b);
|
|
335
772
|
else b.append(fragment.effects.map((e) => e.functionBody).join("\n"));
|
|
336
|
-
b.append(`}${status.options.dev === true ? `, "setAttributes"` :
|
|
773
|
+
b.append(`}${status.options.dev === true ? `, "setAttributes"${trailing}` : trailing});`);
|
|
337
774
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
parentName,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
status.imports.add("
|
|
347
|
-
|
|
775
|
+
if (fragment.singleRootElement) {
|
|
776
|
+
status.imports.add("t_add_element");
|
|
777
|
+
b.append(`t_add_element(${fragment.endVarName ?? fragmentName}, ${parentName}, ${anchorName});`);
|
|
778
|
+
if (fragment.endVarName) {
|
|
779
|
+
status.imports.add("t_next");
|
|
780
|
+
b.append(`t_next(${fragment.endVarName});`);
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
status.imports.add("t_add_fragment");
|
|
784
|
+
let params = [
|
|
785
|
+
fragmentName,
|
|
786
|
+
parentName,
|
|
787
|
+
anchorName
|
|
788
|
+
];
|
|
789
|
+
if (fragment.endVarName) params.push(fragment.endVarName);
|
|
790
|
+
if (fragment.endVarName && fragment.rootVarName) params.push(fragment.rootVarName);
|
|
791
|
+
b.append(`t_add_fragment(${params.join(", ")});`);
|
|
792
|
+
if (fragment.endVarName) {
|
|
793
|
+
status.imports.add("t_next");
|
|
794
|
+
b.append(`t_next(${fragment.endVarName});`);
|
|
795
|
+
}
|
|
348
796
|
}
|
|
349
797
|
}
|
|
350
798
|
}
|
|
351
|
-
|
|
352
799
|
//#endregion
|
|
353
800
|
//#region src/compile/parse/utils/isSpaceChar.ts
|
|
354
801
|
/**
|
|
@@ -363,7 +810,6 @@ function isSpaceChar(text, index) {
|
|
|
363
810
|
let code = text.charCodeAt(index);
|
|
364
811
|
return code === 32 || code >= 9 && code <= 13;
|
|
365
812
|
}
|
|
366
|
-
|
|
367
813
|
//#endregion
|
|
368
814
|
//#region src/compile/parse/utils/isSpace.ts
|
|
369
815
|
/**
|
|
@@ -377,25 +823,11 @@ function isSpace(text) {
|
|
|
377
823
|
for (let i = 0; i < text.length; i++) if (!isSpaceChar(text, i)) return false;
|
|
378
824
|
return true;
|
|
379
825
|
}
|
|
380
|
-
|
|
381
|
-
//#endregion
|
|
382
|
-
//#region src/compile/utils/isControlNode.ts
|
|
383
|
-
function isControlNode(node) {
|
|
384
|
-
return node.type === "control";
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
//#endregion
|
|
388
|
-
//#region src/compile/utils/isElementNode.ts
|
|
389
|
-
function isElementNode(node) {
|
|
390
|
-
return node.type === "element";
|
|
391
|
-
}
|
|
392
|
-
|
|
393
826
|
//#endregion
|
|
394
827
|
//#region src/compile/utils/isTextNode.ts
|
|
395
828
|
function isTextNode(node) {
|
|
396
829
|
return node.type === "text";
|
|
397
830
|
}
|
|
398
|
-
|
|
399
831
|
//#endregion
|
|
400
832
|
//#region src/compile/build/client/buildFragment.ts
|
|
401
833
|
/**
|
|
@@ -420,9 +852,14 @@ function buildFragment(node, status, b, parentName, anchorName) {
|
|
|
420
852
|
declareFragmentVars(node.fragment, node, fragmentPath, status, b, parentName, anchorName, varPaths, false, false);
|
|
421
853
|
b.append(`]);`);
|
|
422
854
|
} else {
|
|
423
|
-
status.imports.add("t_fragment");
|
|
424
855
|
const fragmentText = fragment.text.replaceAll("`", "\\`").replaceAll(/\s+/g, " ");
|
|
425
|
-
|
|
856
|
+
if (fragment.singleRootElement) {
|
|
857
|
+
status.imports.add("t_fragment_el");
|
|
858
|
+
b.append(`const ${fragmentName} = t_fragment_el($parent.ownerDocument!, t_fragment_els, ${fragment.number}, \`${fragmentText}\`${fragment.ns ? ", true" : ""});`);
|
|
859
|
+
} else {
|
|
860
|
+
status.imports.add("t_fragment");
|
|
861
|
+
b.append(`const ${fragmentName} = t_fragment($parent.ownerDocument!, t_fragments, ${fragment.number}, \`${fragmentText}\`${fragment.ns ? ", true" : ""});`);
|
|
862
|
+
}
|
|
426
863
|
let fragmentPath = {
|
|
427
864
|
parent: null,
|
|
428
865
|
type: "fragment",
|
|
@@ -436,17 +873,27 @@ function buildFragment(node, status, b, parentName, anchorName) {
|
|
|
436
873
|
}
|
|
437
874
|
function maybeAddRootNodeDeclaration(node, fragment, fragmentName, status, b, varPaths) {
|
|
438
875
|
if (!node.children.length) return;
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
876
|
+
const rootName = `t_root_${fragment.number}`;
|
|
877
|
+
const firstRendering = firstRenderingChild(node.children);
|
|
878
|
+
const isTextRoot = !!(firstRendering && isTextNode(firstRendering));
|
|
879
|
+
const rootFn = fragment.singleRootElement ? "t_root_el" : "t_root";
|
|
880
|
+
status.imports.add(rootFn);
|
|
881
|
+
const params = [fragmentName];
|
|
882
|
+
if (!fragment.singleRootElement && isTextRoot) params.push("true");
|
|
883
|
+
const rootPath = `${rootFn}(${params.join(", ")})`;
|
|
884
|
+
b.append(`const ${rootName} = ${rootPath};`);
|
|
885
|
+
varPaths.set(`${rootFn}(${fragmentName})`, rootName);
|
|
886
|
+
if (!fragment.singleRootElement) fragment.rootVarName = rootName;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Returns the first child that actually renders to the DOM, skipping comments
|
|
890
|
+
* and no-output control nodes like @key/@const.
|
|
891
|
+
*/
|
|
892
|
+
function firstRenderingChild(children) {
|
|
893
|
+
for (const child of children) {
|
|
894
|
+
if (child.type === "comment") continue;
|
|
895
|
+
if (isControlNode(child) && NON_RENDERING_OPERATIONS.has(child.operation)) continue;
|
|
896
|
+
return child;
|
|
450
897
|
}
|
|
451
898
|
}
|
|
452
899
|
function declareFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare) {
|
|
@@ -469,6 +916,7 @@ function declareFragmentVars(fragment, node, path, status, b, parentName, anchor
|
|
|
469
916
|
case "special":
|
|
470
917
|
declareSpecialFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare);
|
|
471
918
|
break;
|
|
919
|
+
case "comment": break;
|
|
472
920
|
default: throw new Error(`Invalid node type: ${node.type}`);
|
|
473
921
|
}
|
|
474
922
|
}
|
|
@@ -481,6 +929,7 @@ function declareControlFragmentVars(fragment, node, path, status, b, parentName,
|
|
|
481
929
|
case "@switch group":
|
|
482
930
|
case "@for group":
|
|
483
931
|
case "@await group":
|
|
932
|
+
case "@try group":
|
|
484
933
|
case "@replace group":
|
|
485
934
|
case "@html group":
|
|
486
935
|
declareParentAndAnchorFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, node.operation.substring(1).replace(" group", ""), declare);
|
|
@@ -529,10 +978,17 @@ function declareElementFragmentVars(fragment, node, path, status, b, parentName,
|
|
|
529
978
|
if (topLevel) fragment.endVarName = node.varName;
|
|
530
979
|
const varPath = getFragmentVarPath(fragment, status, node.varName, elementPath, varPaths);
|
|
531
980
|
if (status.options.useCreateElement === true) b.append(`let ${node.varName};`);
|
|
532
|
-
else if (node.tagName === "@element") b.append(`let ${node.varName} = ${varPath} as ${elementTypeName(node)};`);
|
|
533
981
|
else {
|
|
534
|
-
b.append(`
|
|
535
|
-
|
|
982
|
+
if (node.tagName === "@element") b.append(`let ${node.varName} = ${varPath} as ${elementTypeName(node)};`);
|
|
983
|
+
else {
|
|
984
|
+
b.append(`const ${node.varName} = ${varPath} as ${elementTypeName(node)};`);
|
|
985
|
+
node.varName;
|
|
986
|
+
}
|
|
987
|
+
const refAttribute = node.attributes.find((a) => a.name === "&ref");
|
|
988
|
+
if (refAttribute) {
|
|
989
|
+
let { value, fullyReactive } = refAttribute;
|
|
990
|
+
if (value != null && fullyReactive) b.append(`${value} = ${node.varName};`);
|
|
991
|
+
}
|
|
536
992
|
}
|
|
537
993
|
}
|
|
538
994
|
}
|
|
@@ -576,7 +1032,7 @@ function declareTextFragmentVars(fragment, node, path, status, b, varPaths, last
|
|
|
576
1032
|
if (status.options.useCreateElement === true) b.append(`let ${node.varName};`);
|
|
577
1033
|
else {
|
|
578
1034
|
b.append(`const ${node.varName} = ${varPath};`);
|
|
579
|
-
|
|
1035
|
+
node.varName;
|
|
580
1036
|
}
|
|
581
1037
|
}
|
|
582
1038
|
} else {
|
|
@@ -593,8 +1049,9 @@ function declareSpecialFragmentVars(fragment, node, path, status, b, parentName,
|
|
|
593
1049
|
switch (node.tagName) {
|
|
594
1050
|
case "slot":
|
|
595
1051
|
case "fill":
|
|
1052
|
+
case "filldef":
|
|
596
1053
|
for (let [i, child] of node.children.entries()) {
|
|
597
|
-
if (node.tagName === "slot" && isSpecialNode(child) && child.tagName === "fill") continue;
|
|
1054
|
+
if (node.tagName === "slot" && isSpecialNode(child) && (child.tagName === "fill" || child.tagName === "filldef")) continue;
|
|
598
1055
|
declareFragmentVars(fragment, child, path, status, b, parentName, anchorName, varPaths, i === node.children.length - 1, declare);
|
|
599
1056
|
}
|
|
600
1057
|
break;
|
|
@@ -608,7 +1065,8 @@ function declareSpecialFragmentVars(fragment, node, path, status, b, parentName,
|
|
|
608
1065
|
}
|
|
609
1066
|
}
|
|
610
1067
|
function declareParentAndAnchorFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, name, declare) {
|
|
611
|
-
|
|
1068
|
+
const topLevel = !path.parent;
|
|
1069
|
+
if (topLevel) node.parentName = `t_fragment_${fragment.number}`;
|
|
612
1070
|
else {
|
|
613
1071
|
const parentPath = path;
|
|
614
1072
|
const oldChildren = parentPath.children;
|
|
@@ -621,7 +1079,7 @@ function declareParentAndAnchorFragmentVars(fragment, node, path, status, b, par
|
|
|
621
1079
|
if (status.options.useCreateElement === true) b.append(`let ${node.parentName} as HTMLElement;`);
|
|
622
1080
|
else {
|
|
623
1081
|
b.append(`const ${node.parentName} = ${parentVarPath} as HTMLElement;`);
|
|
624
|
-
|
|
1082
|
+
node.parentName;
|
|
625
1083
|
}
|
|
626
1084
|
varPaths.set(parentVarPath, node.parentName);
|
|
627
1085
|
}
|
|
@@ -634,12 +1092,13 @@ function declareParentAndAnchorFragmentVars(fragment, node, path, status, b, par
|
|
|
634
1092
|
};
|
|
635
1093
|
path.children.push(anchorPath);
|
|
636
1094
|
node.varName = nextVarName(`${name}_anchor`, status);
|
|
1095
|
+
if (topLevel) fragment.endVarName = node.varName;
|
|
637
1096
|
const anchorVarPath = getFragmentVarPath(fragment, status, node.varName, anchorPath, varPaths);
|
|
638
1097
|
if (status.options.useCreateElement === true) b.append(`let ${node.varName};`);
|
|
639
1098
|
else {
|
|
640
1099
|
status.imports.add("t_anchor");
|
|
641
1100
|
b.append(`let ${node.varName} = t_anchor(${anchorVarPath}) as HTMLElement;`);
|
|
642
|
-
|
|
1101
|
+
node.varName;
|
|
643
1102
|
}
|
|
644
1103
|
} else {
|
|
645
1104
|
status.imports.add("t_cmt");
|
|
@@ -652,7 +1111,8 @@ function getFragmentVarPath(fragment, status, name, path, varPaths) {
|
|
|
652
1111
|
let node = path;
|
|
653
1112
|
while (node.parent) node = node.parent;
|
|
654
1113
|
let varName = `t_fragment_${fragment.number}`;
|
|
655
|
-
|
|
1114
|
+
const rootFn = fragment.singleRootElement ? "t_root_el" : "t_root";
|
|
1115
|
+
let varPath = getFragmentVarPathPart(node, varName, status, true, rootFn);
|
|
656
1116
|
for (let [existingPath, existingName] of varPaths) if (varPath.includes(existingPath)) varPath = varPath.replace(existingPath, existingName);
|
|
657
1117
|
if (name !== "?") {
|
|
658
1118
|
varPaths.set(varPath, name);
|
|
@@ -677,10 +1137,10 @@ function getFragmentVarPath(fragment, status, name, path, varPaths) {
|
|
|
677
1137
|
varPath = varPath.replaceAll("t_next_text", "t_next");
|
|
678
1138
|
return varPath;
|
|
679
1139
|
}
|
|
680
|
-
function getFragmentVarPathPart(path, varPath, status, root = false) {
|
|
1140
|
+
function getFragmentVarPathPart(path, varPath, status, root = false, rootFn = "t_root") {
|
|
681
1141
|
if (root) {
|
|
682
|
-
status.imports.add(
|
|
683
|
-
varPath =
|
|
1142
|
+
status.imports.add(rootFn);
|
|
1143
|
+
varPath = `${rootFn}(${varPath})`;
|
|
684
1144
|
} else {
|
|
685
1145
|
status.imports.add("t_child");
|
|
686
1146
|
varPath = `t_child(${varPath})`;
|
|
@@ -695,37 +1155,72 @@ function getFragmentVarPathPart(path, varPath, status, root = false) {
|
|
|
695
1155
|
}
|
|
696
1156
|
return varPath;
|
|
697
1157
|
}
|
|
698
|
-
|
|
699
|
-
|
|
1158
|
+
//#endregion
|
|
1159
|
+
//#region src/compile/utils/forVarsReadIn.ts
|
|
1160
|
+
/**
|
|
1161
|
+
* Returns a bitmask of the for-loop variables whose substituted paths appear
|
|
1162
|
+
* in `expr`. Bit N is set when the Nth for-var (by position in `forVarNames`)
|
|
1163
|
+
* is read.
|
|
1164
|
+
*
|
|
1165
|
+
* After `replaceForVarNames` has run, every reference to a for-var in the
|
|
1166
|
+
* body has been rewritten to its data-bag path (e.g. `row` →
|
|
1167
|
+
* `t_item_42.data.row`, or `t_item_42.data` for single-var loops). This
|
|
1168
|
+
* helper scans the POST-substitution `expr` for those paths and returns a
|
|
1169
|
+
* bitmask — used to annotate each effect with `forVarMask` so the keyed-list
|
|
1170
|
+
* reconciler's no-proxy force-rerun path (`rerunRegionEffects`) can skip
|
|
1171
|
+
* effects that don't depend on the changed for-vars via a single bitwise AND.
|
|
1172
|
+
*
|
|
1173
|
+
* Conservative by design: a false positive (claiming a dependency that
|
|
1174
|
+
* doesn't really exist) just causes an unnecessary re-run, while a false
|
|
1175
|
+
* negative would drop a needed update. String literals that happen to
|
|
1176
|
+
* contain the substituted path are extremely unlikely and harmless.
|
|
1177
|
+
*/
|
|
1178
|
+
function forVarsReadIn(expr, forVarNames) {
|
|
1179
|
+
let mask = 0;
|
|
1180
|
+
for (let i = 0; i < forVarNames.length; i++) {
|
|
1181
|
+
const subPath = forVarNames[i][1];
|
|
1182
|
+
if (subPath && expr.includes(subPath)) mask |= 1 << i;
|
|
1183
|
+
}
|
|
1184
|
+
return mask;
|
|
1185
|
+
}
|
|
700
1186
|
//#endregion
|
|
701
1187
|
//#region src/compile/build/client/replaceForVarNames.ts
|
|
702
1188
|
/**
|
|
703
1189
|
* Variables used within a for loop need to be retrieved from the loop data.
|
|
704
1190
|
*/
|
|
705
1191
|
function replaceForVarNames(value, status) {
|
|
706
|
-
|
|
707
|
-
|
|
1192
|
+
if (status.forVarNames.length === 0) return value;
|
|
1193
|
+
let result = "";
|
|
1194
|
+
let index = 0;
|
|
1195
|
+
for (let [start, end] of codeRanges(value)) {
|
|
1196
|
+
result += value.substring(index, start);
|
|
1197
|
+
result += replaceVarNames(value.substring(start, end), status);
|
|
1198
|
+
index = end;
|
|
1199
|
+
}
|
|
1200
|
+
result += value.substring(index);
|
|
1201
|
+
return result;
|
|
1202
|
+
}
|
|
1203
|
+
function replaceVarNames(code, status) {
|
|
1204
|
+
for (let varName of status.forVarNames) code = code.replaceAll(new RegExp(`(^|\\s|\\(|\\[|\\{|!|\\?|\\.\\.\\.)${varName[0]}($|\\s|\\.|,|\\(|\\)|\\[|\\]|\\}|;|\\?)`, "g"), `$1${varName[1]}$2`);
|
|
1205
|
+
return code;
|
|
708
1206
|
}
|
|
709
|
-
|
|
710
1207
|
//#endregion
|
|
711
1208
|
//#region src/compile/build/client/buildRun.ts
|
|
712
1209
|
function buildRun(functionName, functionBody, status, b) {
|
|
713
1210
|
functionBody = replaceForVarNames(functionBody, status);
|
|
714
1211
|
addPushDevBoundary("run", functionName, status, b);
|
|
1212
|
+
let forVarMask = status.forVarNames.length > 0 ? forVarsReadIn(functionBody, status.forVarNames) : void 0;
|
|
1213
|
+
let trailing = "";
|
|
1214
|
+
if (status.options.dev === true) {
|
|
1215
|
+
trailing = `, "${functionName}"`;
|
|
1216
|
+
if (forVarMask !== void 0) trailing += `, { forVarMask: ${forVarMask} }`;
|
|
1217
|
+
} else if (forVarMask !== void 0) trailing = `, undefined, { forVarMask: ${forVarMask} }`;
|
|
715
1218
|
status.imports.add("$run");
|
|
716
1219
|
b.append("$run(() => {");
|
|
717
1220
|
b.append(functionBody);
|
|
718
|
-
b.append(`}${
|
|
1221
|
+
b.append(`}${trailing});`);
|
|
719
1222
|
addPopDevBoundary(status, b);
|
|
720
1223
|
}
|
|
721
|
-
|
|
722
|
-
//#endregion
|
|
723
|
-
//#region src/compile/utils/endOfString.ts
|
|
724
|
-
function endOfString(char, content, start) {
|
|
725
|
-
for (let j = start + 1; j < content.length; j++) if (content[j] === char && content[j - 1] !== "\\") return j;
|
|
726
|
-
return content.length;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
1224
|
//#endregion
|
|
730
1225
|
//#region src/compile/build/client/getAttributeOffsets.ts
|
|
731
1226
|
function getAttributeOffsets(value, span) {
|
|
@@ -738,7 +1233,6 @@ function getAttributeOffsets(value, span) {
|
|
|
738
1233
|
let level = 0;
|
|
739
1234
|
for (let i = 0; i < value.length; i++) {
|
|
740
1235
|
const char = value[i];
|
|
741
|
-
const nextChar = value[i + 1];
|
|
742
1236
|
if (char === "{") {
|
|
743
1237
|
level++;
|
|
744
1238
|
if (level === 1) {
|
|
@@ -761,25 +1255,10 @@ function getAttributeOffsets(value, span) {
|
|
|
761
1255
|
}
|
|
762
1256
|
} else if (char === "`" && level === 0) textContent += "\\";
|
|
763
1257
|
else if (level > 0) {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
continue;
|
|
769
|
-
} else if (char === "/" && nextChar === "*") {
|
|
770
|
-
let start = i;
|
|
771
|
-
i = value.indexOf("*/", i) + 1;
|
|
772
|
-
textContent += value.substring(start, i + 1);
|
|
773
|
-
continue;
|
|
774
|
-
} else if (char === "\"" || char === "'") {
|
|
775
|
-
let start = i;
|
|
776
|
-
i = endOfString(char, value, i);
|
|
777
|
-
textContent += value.substring(start, i + 1);
|
|
778
|
-
continue;
|
|
779
|
-
} else if (char === "`") {
|
|
780
|
-
let start = i;
|
|
781
|
-
i = endOfTemplateString(value, i);
|
|
782
|
-
textContent += value.substring(start, i + 1);
|
|
1258
|
+
const skipped = skipStringOrComment(value, i);
|
|
1259
|
+
if (skipped !== -1) {
|
|
1260
|
+
textContent += value.substring(i, skipped);
|
|
1261
|
+
i = skipped - 1;
|
|
783
1262
|
continue;
|
|
784
1263
|
}
|
|
785
1264
|
}
|
|
@@ -793,7 +1272,6 @@ function getAttributeOffsets(value, span) {
|
|
|
793
1272
|
lengths
|
|
794
1273
|
};
|
|
795
1274
|
}
|
|
796
|
-
|
|
797
1275
|
//#endregion
|
|
798
1276
|
//#region src/compile/build/client/buildComponentNode.ts
|
|
799
1277
|
function buildComponentNode(node, status, b, root = false) {
|
|
@@ -810,51 +1288,31 @@ function buildComponentNode(node, status, b, root = false) {
|
|
|
810
1288
|
name = name.substring(1);
|
|
811
1289
|
props.push({
|
|
812
1290
|
name,
|
|
813
|
-
preText: "",
|
|
814
1291
|
value,
|
|
815
|
-
|
|
816
|
-
spans: [span],
|
|
817
|
-
offsets: [],
|
|
818
|
-
lengths: []
|
|
1292
|
+
spans: [span]
|
|
819
1293
|
});
|
|
820
1294
|
runs.push(`${propsName}["${name}"] = ${value};`);
|
|
821
|
-
bindingRuns.push(
|
|
1295
|
+
bindingRuns.push(bindingTarget(value, `${propsName}["${name}"]`, name, status.imports));
|
|
822
1296
|
} else if (name === "class") {
|
|
823
|
-
status.
|
|
824
|
-
const params = [value];
|
|
825
|
-
if (node.scopeStyles) params.push(`"torp-${status.styleHash}"`);
|
|
826
|
-
value = params.join(", ");
|
|
1297
|
+
if (node.scopeStyles) value = `[${value}, "torp-${status.styleHash}"]`;
|
|
827
1298
|
props.push({
|
|
828
1299
|
name,
|
|
829
|
-
preText: "t_class(",
|
|
830
1300
|
value,
|
|
831
|
-
|
|
832
|
-
spans: [span],
|
|
833
|
-
offsets: [],
|
|
834
|
-
lengths: []
|
|
1301
|
+
spans: [span]
|
|
835
1302
|
});
|
|
836
|
-
runs.push(`${propsName}["${name}"] =
|
|
1303
|
+
runs.push(`${propsName}["${name}"] = ${value};`);
|
|
837
1304
|
} else if (name === "style") {
|
|
838
|
-
status.imports.add("t_style");
|
|
839
1305
|
props.push({
|
|
840
1306
|
name,
|
|
841
|
-
preText: "t_style(",
|
|
842
1307
|
value,
|
|
843
|
-
|
|
844
|
-
spans: [span],
|
|
845
|
-
offsets: [],
|
|
846
|
-
lengths: []
|
|
1308
|
+
spans: [span]
|
|
847
1309
|
});
|
|
848
|
-
runs.push(`${propsName}["${name}"] =
|
|
1310
|
+
runs.push(`${propsName}["${name}"] = ${value};`);
|
|
849
1311
|
} else {
|
|
850
1312
|
props.push({
|
|
851
1313
|
name,
|
|
852
|
-
preText: "",
|
|
853
1314
|
value,
|
|
854
|
-
|
|
855
|
-
spans: [span],
|
|
856
|
-
offsets: [],
|
|
857
|
-
lengths: []
|
|
1315
|
+
spans: [span]
|
|
858
1316
|
});
|
|
859
1317
|
runs.push(`${propsName}["${name}"] = ${value};`);
|
|
860
1318
|
}
|
|
@@ -862,38 +1320,28 @@ function buildComponentNode(node, status, b, root = false) {
|
|
|
862
1320
|
const { newValue, spans, offsets, lengths } = getAttributeOffsets(value, span);
|
|
863
1321
|
value = newValue;
|
|
864
1322
|
if (name === "class") {
|
|
865
|
-
status.
|
|
866
|
-
const params = [value];
|
|
867
|
-
if (node.scopeStyles) params.push(`"torp-${status.styleHash}"`);
|
|
868
|
-
value = params.join(", ");
|
|
1323
|
+
if (node.scopeStyles) value = `[${value}, "torp-${status.styleHash}"]`;
|
|
869
1324
|
props.push({
|
|
870
1325
|
name,
|
|
871
|
-
preText: "t_class(",
|
|
872
1326
|
value,
|
|
873
|
-
postText: ")",
|
|
874
1327
|
spans,
|
|
875
1328
|
offsets,
|
|
876
1329
|
lengths
|
|
877
1330
|
});
|
|
878
|
-
runs.push(`${propsName}["${name}"] =
|
|
1331
|
+
runs.push(`${propsName}["${name}"] = ${value};`);
|
|
879
1332
|
} else if (name === "style") {
|
|
880
|
-
status.imports.add("t_style");
|
|
881
1333
|
props.push({
|
|
882
1334
|
name,
|
|
883
|
-
preText: "t_style(",
|
|
884
1335
|
value,
|
|
885
|
-
postText: ")",
|
|
886
1336
|
spans,
|
|
887
1337
|
offsets,
|
|
888
1338
|
lengths
|
|
889
1339
|
});
|
|
890
|
-
runs.push(`${propsName}["${name}"] =
|
|
1340
|
+
runs.push(`${propsName}["${name}"] = ${value};`);
|
|
891
1341
|
} else {
|
|
892
1342
|
props.push({
|
|
893
1343
|
name,
|
|
894
|
-
preText: "",
|
|
895
1344
|
value,
|
|
896
|
-
postText: "",
|
|
897
1345
|
spans,
|
|
898
1346
|
offsets,
|
|
899
1347
|
lengths
|
|
@@ -904,27 +1352,22 @@ function buildComponentNode(node, status, b, root = false) {
|
|
|
904
1352
|
if (name === "class" && node.scopeStyles) value = `"${trimQuotes(value)} torp-${status.styleHash}"`;
|
|
905
1353
|
props.push({
|
|
906
1354
|
name,
|
|
907
|
-
preText: "",
|
|
908
1355
|
value,
|
|
909
|
-
postText: "",
|
|
910
|
-
spans: [span]
|
|
911
|
-
offsets: [],
|
|
912
|
-
lengths: []
|
|
1356
|
+
postText: " as const",
|
|
1357
|
+
spans: [span]
|
|
913
1358
|
});
|
|
914
1359
|
} else props.push({
|
|
915
1360
|
name,
|
|
916
|
-
preText: "",
|
|
917
1361
|
value: "true",
|
|
918
|
-
|
|
919
|
-
spans: [span],
|
|
920
|
-
offsets: [],
|
|
921
|
-
lengths: []
|
|
1362
|
+
spans: [span]
|
|
922
1363
|
});
|
|
923
|
-
b.append(`
|
|
1364
|
+
b.append(`let ${propsName} = $watch({`);
|
|
924
1365
|
for (let p of props) {
|
|
925
1366
|
let value = replaceForVarNames(p.value, status);
|
|
926
|
-
|
|
927
|
-
|
|
1367
|
+
p.preText ??= "";
|
|
1368
|
+
p.postText ??= "";
|
|
1369
|
+
if (p.spans.length === 1) addMappedText(p.name ? `${p.name}: ` + p.preText : p.preText, value, p.postText + ",", p.spans[0], status, b);
|
|
1370
|
+
else addMappedTextWithOffsets(p.name ? `${p.name}: ` + p.preText : p.preText, value, p.postText + ",", p.spans, p.offsets, p.lengths, status, b);
|
|
928
1371
|
}
|
|
929
1372
|
b.append("});");
|
|
930
1373
|
if (runs.length) buildRun("setProps", replaceForVarNames(runs.join("\n"), status), status, b);
|
|
@@ -946,14 +1389,14 @@ function buildComponentNode(node, status, b, root = false) {
|
|
|
946
1389
|
const slotsName = componentHasSlots ? nextVarName("slots", status) : "undefined";
|
|
947
1390
|
if (componentHasSlots) {
|
|
948
1391
|
b.append(`const ${slotsName}: Record<string, SlotRender> = {};`);
|
|
949
|
-
for (let slot of node.children) if (isSpecialNode(slot) && slot.tagName === "fill") {
|
|
1392
|
+
for (let slot of node.children) if (isSpecialNode(slot) && (slot.tagName === "fill" || slot.tagName === "filldef")) {
|
|
950
1393
|
const nameAttribute = slot.attributes.find((a) => a.name === "name");
|
|
951
1394
|
const slotName = nameAttribute?.value ? trimQuotes(nameAttribute.value) : "_";
|
|
952
1395
|
const slotParams = [
|
|
953
1396
|
"$sparent: ParentNode",
|
|
954
1397
|
"$sanchor: Node | null",
|
|
955
|
-
slot.hasSlotProps ? "$
|
|
956
|
-
"// @ts-ignore\n$context?: Record<PropertyKey, any>"
|
|
1398
|
+
slot.hasSlotProps ? "$slot: Record<PropertyKey, any>" : "_$slot?: Record<PropertyKey, any>",
|
|
1399
|
+
"// @ts-ignore\n// eslint-disable-next-line no-unused-vars\n$context?: Record<PropertyKey, any>"
|
|
957
1400
|
];
|
|
958
1401
|
b.append(`${slot.hasSlotProps ? "// @ts-ignore\n" : ""}${slotsName}["${slotName}"] = (\n${slotParams.join(",\n")}\n) => {`);
|
|
959
1402
|
buildFragment(slot, status, b, "$sparent", "$sanchor");
|
|
@@ -974,10 +1417,175 @@ function buildComponentNode(node, status, b, root = false) {
|
|
|
974
1417
|
let selfAttribute = node.attributes.find((a) => a.name === "self");
|
|
975
1418
|
if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) componentName = selfAttribute.value;
|
|
976
1419
|
}
|
|
977
|
-
addMappedText("", `${componentName}(${renderParams})`, ";", node.span, status, b);
|
|
978
|
-
b.append("");
|
|
1420
|
+
addMappedText("", `${componentName}(${renderParams})`, ";", node.span, status, b);
|
|
1421
|
+
b.append("");
|
|
1422
|
+
}
|
|
1423
|
+
//#endregion
|
|
1424
|
+
//#region src/compile/build/client/buildAwaitNode.ts
|
|
1425
|
+
function buildAwaitNode(node, status, b) {
|
|
1426
|
+
const anchorName = node.varName ?? "null";
|
|
1427
|
+
const parentName = node.parentName || anchorName + ".parentNode";
|
|
1428
|
+
const regionName = nextVarName("await_region", status);
|
|
1429
|
+
const branches = node.children.filter((n) => isControlNode(n));
|
|
1430
|
+
const awaitBranch = branches.find((n) => n.operation === "@await");
|
|
1431
|
+
const withBranch = branches.find((n) => n.operation === "@with");
|
|
1432
|
+
status.imports.add("t_region");
|
|
1433
|
+
status.imports.add("t_run_await");
|
|
1434
|
+
b.append("");
|
|
1435
|
+
b.append("/* @await */");
|
|
1436
|
+
addPushDevBoundary("control", "@await", status, b);
|
|
1437
|
+
b.append(`
|
|
1438
|
+
const ${regionName} = t_region(${status.options.dev === true ? `"await"` : ""});
|
|
1439
|
+
t_run_await(${regionName}, ${anchorName}, (${status.inHead ? "" : "t_before"}) => {`);
|
|
1440
|
+
if (awaitBranch !== void 0 && awaitBranch.children.length > 0) {
|
|
1441
|
+
buildFragment(awaitBranch, status, b, parentName, "t_before");
|
|
1442
|
+
status.fragmentStack.push({
|
|
1443
|
+
fragment: awaitBranch.fragment,
|
|
1444
|
+
path: ""
|
|
1445
|
+
});
|
|
1446
|
+
for (let child of awaitBranch.children) buildNode(child, status, b, parentName, "t_before");
|
|
1447
|
+
status.fragmentStack.pop();
|
|
1448
|
+
buildAddFragment(awaitBranch, status, b, parentName, "t_before");
|
|
1449
|
+
}
|
|
1450
|
+
if (withBranch !== void 0) {
|
|
1451
|
+
b.append(`}, (${status.inHead ? "" : "t_before"}) => {`);
|
|
1452
|
+
if (withBranch.children.length > 0) {
|
|
1453
|
+
buildFragment(withBranch, status, b, parentName, "t_before");
|
|
1454
|
+
status.fragmentStack.push({
|
|
1455
|
+
fragment: withBranch.fragment,
|
|
1456
|
+
path: ""
|
|
1457
|
+
});
|
|
1458
|
+
for (let child of withBranch.children) buildNode(child, status, b, parentName, "t_before");
|
|
1459
|
+
status.fragmentStack.pop();
|
|
1460
|
+
buildAddFragment(withBranch, status, b, parentName, "t_before");
|
|
1461
|
+
}
|
|
1462
|
+
b.append(`}${status.options.dev === true ? `, "runAwait"` : ""});`);
|
|
1463
|
+
} else b.append(`}, null${status.options.dev === true ? `, "runAwait"` : ""});`);
|
|
1464
|
+
addPopDevBoundary(status, b);
|
|
1465
|
+
b.append("");
|
|
1466
|
+
}
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/compile/utils/hasNestedControl.ts
|
|
1469
|
+
/**
|
|
1470
|
+
* Returns true if any descendant of a `@for` body is a control node that
|
|
1471
|
+
* creates its own region and effects (`@if`, `@for`, `@await`, `@switch`,
|
|
1472
|
+
* `@replace`, `@html`).
|
|
1473
|
+
*
|
|
1474
|
+
* Operations that produce no DOM and no region (`@key`, `@const`, `@console`,
|
|
1475
|
+
* `@debugger`, `@function`) are fine — same category as `@key`. They are
|
|
1476
|
+
* descended through, so a real control statement nested under a `@key` (a
|
|
1477
|
+
* common shape — `@for` bodies almost always lead with `@key = ...`) is still
|
|
1478
|
+
* detected.
|
|
1479
|
+
*
|
|
1480
|
+
* Extracted from `isForBodyNoProxySafe` so the no-proxy specialization, the
|
|
1481
|
+
* leaf-row region-chain specialization (`isForBodyLeafSafe`), and any future
|
|
1482
|
+
* pass that needs the same structural predicate can share one source of
|
|
1483
|
+
* truth.
|
|
1484
|
+
*/
|
|
1485
|
+
function hasNestedControl(children) {
|
|
1486
|
+
for (const child of children) {
|
|
1487
|
+
if (isControlNode(child)) {
|
|
1488
|
+
if (!NON_RENDERING_OPERATIONS.has(child.operation)) return true;
|
|
1489
|
+
if (hasNestedControl(child.children)) return true;
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
if (isElementLike$1(child)) {
|
|
1493
|
+
if (hasNestedControl(child.children)) return true;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
function isElementLike$1(node) {
|
|
1499
|
+
return node.type === "element" || node.type === "component" || node.type === "special";
|
|
1500
|
+
}
|
|
1501
|
+
//#endregion
|
|
1502
|
+
//#region src/compile/utils/isForBodyLeafSafe.ts
|
|
1503
|
+
/**
|
|
1504
|
+
* Returns true if the body of a `@for` loop contains no nested control
|
|
1505
|
+
* statements (`@if`, `@for`, `@await`, `@switch`, `@replace`, `@html`).
|
|
1506
|
+
*
|
|
1507
|
+
* When true, every effect created by the row template lives directly on the
|
|
1508
|
+
* item's own region — never on a descendant region chained off it. The
|
|
1509
|
+
* compiler can then emit a "leaf-row" `createListItem` that skips the
|
|
1510
|
+
* per-item `pushRegion(item)` / `popRegion(oldRegion)` calls: `runListItems`
|
|
1511
|
+
* has already pushed the item onto the active region (via
|
|
1512
|
+
* `pushRegion(item, true)`) before calling `create()`, and a leaf body never
|
|
1513
|
+
* creates descendant regions, so `context.activeRegion` stays put at the item
|
|
1514
|
+
* for the whole callback. Saves two function calls per row plus the
|
|
1515
|
+
* `devContext.onRegionPushed` / `onRegionPopped` invocations, which on the
|
|
1516
|
+
* 10k-row js-framework-bench `runlots` op is 20000 fewer calls per pass.
|
|
1517
|
+
*
|
|
1518
|
+
* Note: every no-proxy-safe body is also leaf-safe (`isForBodyNoProxySafe`
|
|
1519
|
+
* includes `hasNestedControl`), but the converse is not true — a body that
|
|
1520
|
+
* writes to a for-var (disqualifying it from no-proxy) can still be leaf-safe
|
|
1521
|
+
* and earn this specialization.
|
|
1522
|
+
*/
|
|
1523
|
+
function isForBodyLeafSafe(forBodyChildren) {
|
|
1524
|
+
return !hasNestedControl(forBodyChildren);
|
|
1525
|
+
}
|
|
1526
|
+
//#endregion
|
|
1527
|
+
//#region src/compile/utils/isForBodyNoProxySafe.ts
|
|
1528
|
+
/**
|
|
1529
|
+
* Returns true if the body of a `@for` loop never writes to any of its loop
|
|
1530
|
+
* variables (`forVars`) — neither directly (`row = ...`, `row.x = ...`,
|
|
1531
|
+
* `row[k] = ...`) nor via compound assignment (`+=`, `++`, etc.) — AND the
|
|
1532
|
+
* body contains no nested control statements (`@if`, `@for`, `@await`, …).
|
|
1533
|
+
*
|
|
1534
|
+
* When both hold, the compiler can emit a "no-proxy" `@for` specialization
|
|
1535
|
+
* that skips the per-item shallow `$watch` Proxy around the loop variable
|
|
1536
|
+
* bag: reads of `data.<forVar>` are plain property accesses (no proxyGet
|
|
1537
|
+
* trap), and the compiler-emitted `updateListItem` callback re-runs item
|
|
1538
|
+
* effects manually (via `t_rerun_region_effects`) when a forVar's reference
|
|
1539
|
+
* actually changes.
|
|
1540
|
+
*
|
|
1541
|
+
* The "no nested controls" constraint keeps the manual effect re-run sound:
|
|
1542
|
+
* without it, a nested `@if`'s effects would live on a descendant region
|
|
1543
|
+
* chained off the original item, but after keyed reconciliation that chain
|
|
1544
|
+
* is orphaned (the live item is a different object), so the re-run walk
|
|
1545
|
+
* couldn't find them. With no nested controls every row effect is on the
|
|
1546
|
+
* item itself, which is exactly what `t_rerun_region_effects` walks.
|
|
1547
|
+
*
|
|
1548
|
+
* Detection is intentionally conservative: a regex scan over every
|
|
1549
|
+
* expression-like string in the body (text interpolation, attribute values,
|
|
1550
|
+
* event handlers, control statements). Anything ambiguous falls back to the
|
|
1551
|
+
* proxy path. The for-statement itself (`for (let row of ...)`) is NOT
|
|
1552
|
+
* scanned — that's the loop header, not the body — so `for (...; i++)` style
|
|
1553
|
+
* loops with `i++` in the header are still eligible.
|
|
1554
|
+
*/
|
|
1555
|
+
function isForBodyNoProxySafe(forBodyChildren, forVars) {
|
|
1556
|
+
if (forVars.length === 0) return false;
|
|
1557
|
+
if (hasNestedControl(forBodyChildren)) return false;
|
|
1558
|
+
const collected = [];
|
|
1559
|
+
for (const child of forBodyChildren) collectExpressionStrings(child, collected);
|
|
1560
|
+
for (const varName of forVars) {
|
|
1561
|
+
const escaped = varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1562
|
+
const writePattern = new RegExp(`\\[\\s*${escaped}\\s*\\]\\s*=(?![=>])|\\{\\s*${escaped}\\s*(:[^}]+)?\\}\\s*=(?![=>])|\\b${escaped}(?:\\.[a-zA-Z_$][\\w$]*|\\[[^\\]]+\\])?\\s*(?:=(?![=>])|[+\\-*/%&|^]=|\\+\\+|--)|(?:\\+\\+|--)\\s*${escaped}\\b`);
|
|
1563
|
+
for (const text of collected) if (writePattern.test(text)) return false;
|
|
1564
|
+
}
|
|
1565
|
+
return true;
|
|
1566
|
+
}
|
|
1567
|
+
function isElementLike(node) {
|
|
1568
|
+
return node.type === "element" || node.type === "component" || node.type === "special";
|
|
1569
|
+
}
|
|
1570
|
+
function collectExpressionStrings(node, out) {
|
|
1571
|
+
if (isTextNode(node)) {
|
|
1572
|
+
out.push(node.content);
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
if (isElementLike(node)) {
|
|
1576
|
+
for (const attr of node.attributes) {
|
|
1577
|
+
if (attr.value !== void 0) if (attr.name.startsWith("&")) out.push(`${attr.value} =`);
|
|
1578
|
+
else out.push(attr.value);
|
|
1579
|
+
if (attr.fullyReactive && attr.name !== attr.value) out.push(attr.name);
|
|
1580
|
+
}
|
|
1581
|
+
for (const child of node.children) collectExpressionStrings(child, out);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
if (isControlNode(node)) {
|
|
1585
|
+
out.push(node.statement);
|
|
1586
|
+
for (const child of node.children) collectExpressionStrings(child, out);
|
|
1587
|
+
}
|
|
979
1588
|
}
|
|
980
|
-
|
|
981
1589
|
//#endregion
|
|
982
1590
|
//#region src/compile/utils/trimMatched.ts
|
|
983
1591
|
/**
|
|
@@ -996,102 +1604,6 @@ function trimMatched(text, startChar, endChar) {
|
|
|
996
1604
|
}
|
|
997
1605
|
return text.substring(start, end);
|
|
998
1606
|
}
|
|
999
|
-
|
|
1000
|
-
//#endregion
|
|
1001
|
-
//#region src/compile/build/client/buildAwaitNode.ts
|
|
1002
|
-
function buildAwaitNode(node, status, b) {
|
|
1003
|
-
const anchorName = node.varName ?? "null";
|
|
1004
|
-
const parentName = node.parentName;
|
|
1005
|
-
const regionName = nextVarName("await_region", status);
|
|
1006
|
-
const indexName = nextVarName("await_index", status);
|
|
1007
|
-
const tokenName = nextVarName("await_token", status);
|
|
1008
|
-
const branches = node.children.filter((n) => isControlNode(n));
|
|
1009
|
-
let awaitBranch = branches.find((n) => n.operation === "@await");
|
|
1010
|
-
if (!awaitBranch) {}
|
|
1011
|
-
let thenBranch = branches.find((n) => n.operation === "@then");
|
|
1012
|
-
if (!thenBranch) thenBranch = {
|
|
1013
|
-
type: "control",
|
|
1014
|
-
operation: "@then",
|
|
1015
|
-
statement: "then",
|
|
1016
|
-
children: [],
|
|
1017
|
-
span: {
|
|
1018
|
-
start: 0,
|
|
1019
|
-
end: 0
|
|
1020
|
-
}
|
|
1021
|
-
};
|
|
1022
|
-
let catchBranch = branches.find((n) => n.operation === "@catch");
|
|
1023
|
-
if (!catchBranch) catchBranch = {
|
|
1024
|
-
type: "control",
|
|
1025
|
-
operation: "@catch",
|
|
1026
|
-
statement: "catch",
|
|
1027
|
-
children: [],
|
|
1028
|
-
span: {
|
|
1029
|
-
start: 0,
|
|
1030
|
-
end: 0
|
|
1031
|
-
}
|
|
1032
|
-
};
|
|
1033
|
-
const awaiterName = trimMatched(awaitBranch.statement.substring(5).trim(), "(", ")");
|
|
1034
|
-
const thenVar = trimMatched(thenBranch.statement.substring(4).trim(), "(", ")");
|
|
1035
|
-
const catchVar = trimMatched(catchBranch.statement.substring(5).trim(), "(", ")");
|
|
1036
|
-
status.imports.add("t_region");
|
|
1037
|
-
status.imports.add("t_run_control");
|
|
1038
|
-
status.imports.add("t_run_branch");
|
|
1039
|
-
status.imports.add("t_push_region");
|
|
1040
|
-
status.imports.add("t_pop_region");
|
|
1041
|
-
b.append("");
|
|
1042
|
-
b.append("/* @await */");
|
|
1043
|
-
addPushDevBoundary("control", `@${branches[0].statement}`, status, b);
|
|
1044
|
-
b.append(`
|
|
1045
|
-
const ${regionName} = t_region(${status.options.dev === true ? `"await"` : ""});
|
|
1046
|
-
let ${tokenName} = 0;
|
|
1047
|
-
let ${indexName} = -1;
|
|
1048
|
-
t_run_control(${regionName}, ${anchorName}, (t_before) => {`);
|
|
1049
|
-
let index = 0;
|
|
1050
|
-
buildAwaitBranch(awaitBranch, status, b, parentName, regionName, indexName, index++);
|
|
1051
|
-
b.append(`
|
|
1052
|
-
${tokenName}++;
|
|
1053
|
-
((t_token) => {`);
|
|
1054
|
-
awaitBranch.span.start += 7;
|
|
1055
|
-
addMappedText("", replaceForVarNames(awaiterName, status), "", awaitBranch.span, status, b);
|
|
1056
|
-
thenBranch.span.start -= 1;
|
|
1057
|
-
addMappedText(".then((", replaceForVarNames(thenVar, status), ") => {", thenBranch.span, status, b);
|
|
1058
|
-
b.append(`if (t_token === ${tokenName}) {`);
|
|
1059
|
-
buildAwaitBranch(thenBranch, status, b, parentName, regionName, indexName, index++);
|
|
1060
|
-
b.append(`}
|
|
1061
|
-
})`);
|
|
1062
|
-
catchBranch.span.start -= 1;
|
|
1063
|
-
addMappedText(".catch((", replaceForVarNames(catchVar, status), ") => {", catchBranch.span, status, b);
|
|
1064
|
-
b.append(`if (t_token === ${tokenName}) {`);
|
|
1065
|
-
buildAwaitBranch(catchBranch, status, b, parentName, regionName, indexName, index++);
|
|
1066
|
-
b.append(`}
|
|
1067
|
-
});
|
|
1068
|
-
})(${tokenName});
|
|
1069
|
-
}${status.options.dev === true ? `, "runAwait"` : ""});`);
|
|
1070
|
-
addPopDevBoundary(status, b);
|
|
1071
|
-
b.append("");
|
|
1072
|
-
}
|
|
1073
|
-
function buildAwaitBranch(node, status, b, parentName, regionName, indexName, index) {
|
|
1074
|
-
b.append(`if (!t_run_branch(${regionName}, ${indexName}, ${index})) return;`);
|
|
1075
|
-
if (node.children.length > 0) {
|
|
1076
|
-
b.append(`
|
|
1077
|
-
const t_new_region = t_region(${status.options.dev === true ? `"await_branch"` : ""});
|
|
1078
|
-
const t_old_control_region = t_push_region(${regionName});
|
|
1079
|
-
const t_old_region = t_push_region(t_new_region, true);
|
|
1080
|
-
`);
|
|
1081
|
-
buildFragment(node, status, b, parentName, "t_before");
|
|
1082
|
-
status.fragmentStack.push({
|
|
1083
|
-
fragment: node.fragment,
|
|
1084
|
-
path: ""
|
|
1085
|
-
});
|
|
1086
|
-
for (let child of node.children) buildNode(child, status, b, parentName, "t_before");
|
|
1087
|
-
status.fragmentStack.pop();
|
|
1088
|
-
buildAddFragment(node, status, b, parentName, "t_before");
|
|
1089
|
-
b.append("t_pop_region(t_old_region);");
|
|
1090
|
-
b.append("t_pop_region(t_old_control_region);");
|
|
1091
|
-
}
|
|
1092
|
-
b.append(`${indexName} = ${index};`);
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
1607
|
//#endregion
|
|
1096
1608
|
//#region src/compile/build/client/buildForNode.ts
|
|
1097
1609
|
const forLoopRegex = /for\s*\((.+?);.*?;.*?\)/;
|
|
@@ -1116,9 +1628,6 @@ function buildForNode(node, status, b) {
|
|
|
1116
1628
|
}
|
|
1117
1629
|
const regionName = nextVarName("for_region", status);
|
|
1118
1630
|
const listItemsName = nextVarName("new_items", status);
|
|
1119
|
-
const previousItemName = nextVarName("previous_item", status);
|
|
1120
|
-
const nextItemName = nextVarName("next_item", status);
|
|
1121
|
-
const newItemName = nextVarName("new_item", status);
|
|
1122
1631
|
const itemName = nextVarName("item", status);
|
|
1123
1632
|
const beforeName = nextVarName("before", status);
|
|
1124
1633
|
let keyStatement = "";
|
|
@@ -1130,10 +1639,13 @@ function buildForNode(node, status, b) {
|
|
|
1130
1639
|
keyStatement = matches[2];
|
|
1131
1640
|
}
|
|
1132
1641
|
}
|
|
1642
|
+
const noWatch = isForBodyNoProxySafe(node.children, forVarNames);
|
|
1643
|
+
if (noWatch) status.imports.add("t_rerun_region_effects");
|
|
1644
|
+
const singleVar = noWatch && forVarNames.length === 1;
|
|
1645
|
+
const leafRow = isForBodyLeafSafe(node.children);
|
|
1133
1646
|
status.imports.add("t_region");
|
|
1134
1647
|
status.imports.add("t_run_list");
|
|
1135
|
-
status.imports.add("
|
|
1136
|
-
status.imports.add("ListItem");
|
|
1648
|
+
status.imports.add("ListItemSpec");
|
|
1137
1649
|
b.append("");
|
|
1138
1650
|
b.append("/* @for */");
|
|
1139
1651
|
addPushDevBoundary("control", `@${node.statement}`, status, b);
|
|
@@ -1144,41 +1656,51 @@ function buildForNode(node, status, b) {
|
|
|
1144
1656
|
${parentName},
|
|
1145
1657
|
${anchorName},
|
|
1146
1658
|
${status.options.dev === true ? "function createNewItems() {" : "() => {"}
|
|
1147
|
-
let ${listItemsName}:
|
|
1148
|
-
let ${previousItemName} = ${regionName};
|
|
1149
|
-
let ${nextItemName} = ${regionName}.nextRegion;`);
|
|
1659
|
+
let ${listItemsName}: ListItemSpec[] = [];`);
|
|
1150
1660
|
addMappedText("", `${replaceForVarNames(node.statement, status)}`, " {", node.span, status, b);
|
|
1151
|
-
b.append(
|
|
1152
|
-
b.append(
|
|
1153
|
-
if (key !== void 0) addMappedText("", `${keyStatement || "undefined"}`, "
|
|
1154
|
-
|
|
1155
|
-
b.append(
|
|
1156
|
-
b.append(`
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
${previousItemName} = ${newItemName};
|
|
1160
|
-
${listItemsName}.push(${newItemName});
|
|
1161
|
-
}
|
|
1162
|
-
${regionName}.nextRegion = ${nextItemName};
|
|
1163
|
-
return ${listItemsName};
|
|
1164
|
-
},
|
|
1165
|
-
${status.options.dev === true ? `function createListItem(${itemName}, ${beforeName}) {` : `(${itemName}, ${beforeName}) => {`}`);
|
|
1661
|
+
if (singleVar) b.append(`${listItemsName}.push({ data: ${forVarNames[0]}, key: `);
|
|
1662
|
+
else b.append(`${listItemsName}.push({ data: { ${forVarNames.join(", ")} }, key: `);
|
|
1663
|
+
if (key !== void 0) addMappedText("", `${keyStatement || "undefined"}`, " });", key.span, status, b);
|
|
1664
|
+
else b.append(`undefined });`);
|
|
1665
|
+
b.append(`}`);
|
|
1666
|
+
b.append(`return ${listItemsName};`);
|
|
1667
|
+
b.append(`},
|
|
1668
|
+
${status.options.dev === true ? `function createListItem(${itemName}, ${beforeName}) {` : `(${itemName}, ${beforeName}) => {`}`);
|
|
1166
1669
|
let oldForVarNames = status.forVarNames;
|
|
1167
|
-
|
|
1168
|
-
|
|
1670
|
+
const forVarBitBase = oldForVarNames.length;
|
|
1671
|
+
status.forVarNames = [...status.forVarNames, ...singleVar ? forVarNames.map((v) => [v, `${itemName}.data`]) : forVarNames.map((v) => [v, `${itemName}.data.${v}`])];
|
|
1672
|
+
buildForItem(node, status, b, parentName, beforeName, itemName, leafRow);
|
|
1169
1673
|
status.forVarNames = oldForVarNames;
|
|
1170
1674
|
b.append(`},
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
)
|
|
1675
|
+
${status.options.dev === true ? "function updateListItem(t_old_item, t_new_item) {" : "(t_old_item, t_new_item) => {"}`);
|
|
1676
|
+
if (noWatch) {
|
|
1677
|
+
b.append(`let t_changed_mask = 0;`);
|
|
1678
|
+
if (singleVar) {
|
|
1679
|
+
const bit = 1 << forVarBitBase;
|
|
1680
|
+
b.append(`if (t_old_item.data !== t_new_item.data) {
|
|
1681
|
+
t_old_item.data = t_new_item.data;
|
|
1682
|
+
t_changed_mask = ${bit};
|
|
1683
|
+
}`);
|
|
1684
|
+
} else for (let i = 0; i < forVarNames.length; i++) {
|
|
1685
|
+
const varName = forVarNames[i];
|
|
1686
|
+
const bit = 1 << forVarBitBase + i;
|
|
1687
|
+
b.append(`if (t_old_item.data.${varName} !== t_new_item.data.${varName}) {
|
|
1688
|
+
t_old_item.data.${varName} = t_new_item.data.${varName};
|
|
1689
|
+
t_changed_mask |= ${bit};
|
|
1690
|
+
}`);
|
|
1691
|
+
}
|
|
1692
|
+
b.append(`if (t_changed_mask) t_rerun_region_effects(t_old_item, t_changed_mask);`);
|
|
1693
|
+
} else for (let varName of forVarNames) b.append(`t_old_item.data.${varName} = t_new_item.data.${varName};`);
|
|
1694
|
+
b.append(`}${noWatch ? ",\ntrue" : ""}\n);`);
|
|
1175
1695
|
addPopDevBoundary(status, b);
|
|
1176
1696
|
b.append("");
|
|
1177
1697
|
}
|
|
1178
|
-
function buildForItem(node, status, b, parentName, beforeName, itemName) {
|
|
1698
|
+
function buildForItem(node, status, b, parentName, beforeName, itemName, leafRow) {
|
|
1179
1699
|
const oldRegionName = nextVarName("old_region", status);
|
|
1180
|
-
|
|
1181
|
-
|
|
1700
|
+
if (!leafRow) {
|
|
1701
|
+
status.imports.add("t_push_region");
|
|
1702
|
+
b.append(`let ${oldRegionName} = t_push_region(${itemName});`);
|
|
1703
|
+
}
|
|
1182
1704
|
buildFragment(node, status, b, parentName, beforeName);
|
|
1183
1705
|
status.fragmentStack.push({
|
|
1184
1706
|
fragment: node.fragment,
|
|
@@ -1190,39 +1712,37 @@ function buildForItem(node, status, b, parentName, beforeName, itemName) {
|
|
|
1190
1712
|
}
|
|
1191
1713
|
status.fragmentStack.pop();
|
|
1192
1714
|
buildAddFragment(node, status, b, parentName, beforeName);
|
|
1193
|
-
|
|
1194
|
-
|
|
1715
|
+
if (!leafRow) {
|
|
1716
|
+
status.imports.add("t_pop_region");
|
|
1717
|
+
b.append(`t_pop_region(${oldRegionName});`);
|
|
1718
|
+
}
|
|
1195
1719
|
}
|
|
1196
|
-
|
|
1197
1720
|
//#endregion
|
|
1198
1721
|
//#region src/compile/build/client/buildHtmlNode.ts
|
|
1199
1722
|
function buildHtmlNode(node, status, b) {
|
|
1200
1723
|
const htmlAnchorName = node.varName;
|
|
1201
1724
|
const htmlParentName = node.parentName || htmlAnchorName + ".parentNode";
|
|
1202
|
-
const htmlRegionName = nextVarName("html_region", status);
|
|
1203
1725
|
node = node.children[0];
|
|
1204
1726
|
status.imports.add("t_region");
|
|
1205
1727
|
status.imports.add("t_run_control");
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
status.imports.add("t_pop_region");
|
|
1209
|
-
b.append("");
|
|
1210
|
-
b.append(`
|
|
1211
|
-
/* @html */
|
|
1212
|
-
const ${htmlRegionName} = t_region(${status.options.dev === true ? `"html"` : ""});
|
|
1213
|
-
t_run_control(${htmlRegionName}, ${htmlAnchorName}, (t_before) => {`);
|
|
1214
|
-
buildHtmlBranch(node, status, b, htmlParentName, htmlRegionName);
|
|
1215
|
-
b.append(`}${status.options.dev === true ? ", \"html\"" : ""});`);
|
|
1728
|
+
const firstNodeVar = nextVarName("html_first", status);
|
|
1729
|
+
const lastNodeVar = nextVarName("html_last", status);
|
|
1216
1730
|
b.append("");
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1731
|
+
b.append(`/* @html */`);
|
|
1732
|
+
b.append(`let ${firstNodeVar}: ChildNode | null = null;`);
|
|
1733
|
+
b.append(`let ${lastNodeVar}: ChildNode | null = null;`);
|
|
1734
|
+
b.append(`t_run_control(t_region(), ${htmlAnchorName}, (t_before) => {`);
|
|
1220
1735
|
b.append(`${replaceForVarNames(node.statement, status)};`);
|
|
1221
|
-
b.append(`if (
|
|
1222
|
-
b.append(`
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
`);
|
|
1736
|
+
b.append(`if (${firstNodeVar} !== null && ${lastNodeVar} !== null) {`);
|
|
1737
|
+
b.append(`let t_node: ChildNode | null = ${lastNodeVar};`);
|
|
1738
|
+
b.append(`while (t_node !== null && t_node !== ${firstNodeVar}) {`);
|
|
1739
|
+
b.append(`const t_prev: ChildNode | null = t_node.previousSibling;`);
|
|
1740
|
+
b.append(`t_node.remove();`);
|
|
1741
|
+
b.append(`t_node = t_prev;`);
|
|
1742
|
+
b.append(`}`);
|
|
1743
|
+
b.append(`if (${firstNodeVar}) ${firstNodeVar}.remove();`);
|
|
1744
|
+
b.append(`${firstNodeVar} = ${lastNodeVar} = null;`);
|
|
1745
|
+
b.append(`}`);
|
|
1226
1746
|
const templateName = nextVarName("template", status);
|
|
1227
1747
|
const fragmentName = `t_fragment_${node.fragment.number}`;
|
|
1228
1748
|
b.append(`let ${templateName} = document.createElement("template");`);
|
|
@@ -1230,10 +1750,29 @@ function buildHtmlBranch(node, status, b, parentName, regionName) {
|
|
|
1230
1750
|
node.span.end -= 2;
|
|
1231
1751
|
addMappedText("", `${templateName}.innerHTML = ${replaceForVarNames(node.statement, status)};`, "", node.span, status, b);
|
|
1232
1752
|
b.append(`let ${fragmentName} = ${templateName}.content.cloneNode(true) as DocumentFragment;`);
|
|
1233
|
-
|
|
1234
|
-
b.append(
|
|
1753
|
+
b.append(`${firstNodeVar} = ${fragmentName}.firstChild;`);
|
|
1754
|
+
b.append(`${lastNodeVar} = ${fragmentName}.lastChild;`);
|
|
1755
|
+
buildAddFragment(node, status, b, htmlParentName, "t_before");
|
|
1756
|
+
status.imports.add("t_first_inside");
|
|
1757
|
+
b.append(`if (${firstNodeVar} !== null && ${firstNodeVar}.parentNode !== ${htmlParentName}) {`);
|
|
1758
|
+
b.append(`${lastNodeVar} = ${htmlAnchorName}.previousSibling as ChildNode | null;`);
|
|
1759
|
+
b.append(`if (${lastNodeVar} !== null) {`);
|
|
1760
|
+
b.append(`const t_stashed_first = t_first_inside(${htmlAnchorName} as ChildNode);`);
|
|
1761
|
+
b.append(`if (t_stashed_first !== undefined && t_stashed_first !== null) {`);
|
|
1762
|
+
b.append(`${firstNodeVar} = t_stashed_first;`);
|
|
1763
|
+
b.append(`} else {`);
|
|
1764
|
+
b.append(`${firstNodeVar} = ${lastNodeVar};`);
|
|
1765
|
+
b.append(`let t_scan: ChildNode | null = ${lastNodeVar};`);
|
|
1766
|
+
b.append(`while (t_scan !== null && t_scan.previousSibling !== null && t_scan.previousSibling !== ${htmlAnchorName} && (t_scan.previousSibling.nodeType !== 3 || (t_scan.previousSibling.textContent ?? "").trim() !== "")) {`);
|
|
1767
|
+
b.append(`t_scan = t_scan.previousSibling;`);
|
|
1768
|
+
b.append(`}`);
|
|
1769
|
+
b.append(`${firstNodeVar} = t_scan;`);
|
|
1770
|
+
b.append(`}`);
|
|
1771
|
+
b.append(`}`);
|
|
1772
|
+
b.append(`}`);
|
|
1773
|
+
b.append(`}${status.options.dev === true ? ", \"html\"" : ""});`);
|
|
1774
|
+
b.append("");
|
|
1235
1775
|
}
|
|
1236
|
-
|
|
1237
1776
|
//#endregion
|
|
1238
1777
|
//#region src/compile/build/client/buildIfNode.ts
|
|
1239
1778
|
function buildIfNode(node, status, b) {
|
|
@@ -1291,7 +1830,6 @@ function buildIfBranch(node, status, b, parentName, regionName, indexName, index
|
|
|
1291
1830
|
b.append(`${indexName} = ${index};`);
|
|
1292
1831
|
b.append("}");
|
|
1293
1832
|
}
|
|
1294
|
-
|
|
1295
1833
|
//#endregion
|
|
1296
1834
|
//#region src/compile/build/client/buildReplaceNode.ts
|
|
1297
1835
|
function buildReplaceNode(node, status, b) {
|
|
@@ -1331,7 +1869,6 @@ function buildReplaceBranch(node, status, b, parentName, regionName) {
|
|
|
1331
1869
|
buildAddFragment(node, status, b, parentName, "t_before");
|
|
1332
1870
|
b.append("t_pop_region(t_old_region);");
|
|
1333
1871
|
}
|
|
1334
|
-
|
|
1335
1872
|
//#endregion
|
|
1336
1873
|
//#region src/compile/build/client/buildScriptNode.ts
|
|
1337
1874
|
function buildScriptNode(node, status, b) {
|
|
@@ -1342,7 +1879,6 @@ function maybeAppend$1(text, end) {
|
|
|
1342
1879
|
if (!text.endsWith(end)) text += end;
|
|
1343
1880
|
return text;
|
|
1344
1881
|
}
|
|
1345
|
-
|
|
1346
1882
|
//#endregion
|
|
1347
1883
|
//#region src/compile/build/client/buildSwitchNode.ts
|
|
1348
1884
|
function buildSwitchNode(node, status, b) {
|
|
@@ -1354,7 +1890,7 @@ function buildSwitchNode(node, status, b) {
|
|
|
1354
1890
|
if (branches.findIndex((n) => n.operation === "@default") === -1) branches.push({
|
|
1355
1891
|
type: "control",
|
|
1356
1892
|
operation: "@default",
|
|
1357
|
-
statement: "default",
|
|
1893
|
+
statement: "default:",
|
|
1358
1894
|
children: [],
|
|
1359
1895
|
span: {
|
|
1360
1896
|
start: 0,
|
|
@@ -1405,7 +1941,45 @@ function buildSwitchBranch(node, status, b, parentName, regionName, indexName, i
|
|
|
1405
1941
|
break;
|
|
1406
1942
|
}`);
|
|
1407
1943
|
}
|
|
1408
|
-
|
|
1944
|
+
//#endregion
|
|
1945
|
+
//#region src/compile/build/client/buildTryNode.ts
|
|
1946
|
+
function buildTryNode(node, status, b) {
|
|
1947
|
+
const anchorName = node.varName ?? "null";
|
|
1948
|
+
const parentName = node.parentName || anchorName + ".parentNode";
|
|
1949
|
+
const regionName = nextVarName("try_region", status);
|
|
1950
|
+
const branches = node.children.filter((n) => isControlNode(n));
|
|
1951
|
+
const tryBranch = branches.find((n) => n.operation === "@try");
|
|
1952
|
+
const catchBranch = branches.find((n) => n.operation === "@catch");
|
|
1953
|
+
status.imports.add("t_region");
|
|
1954
|
+
status.imports.add("t_run_try");
|
|
1955
|
+
b.append("");
|
|
1956
|
+
b.append("/* @try */");
|
|
1957
|
+
addPushDevBoundary("control", "@try", status, b);
|
|
1958
|
+
b.append(`
|
|
1959
|
+
const ${regionName} = t_region(${status.options.dev === true ? `"try"` : ""});
|
|
1960
|
+
t_run_try(${regionName}, ${anchorName}, (${status.inHead ? "" : "t_before"}) => {`);
|
|
1961
|
+
buildTryBranchBody(tryBranch, status, b, parentName);
|
|
1962
|
+
const catchVar = catchBranch ? trimMatched(catchBranch.statement.substring(5).trim(), "(", ")") || "err" : null;
|
|
1963
|
+
if (catchBranch && catchVar) {
|
|
1964
|
+
addMappedText("},", `(${status.inHead ? "" : "t_before"}, ${replaceForVarNames(catchVar, status)}) => {`, "", catchBranch.span, status, b);
|
|
1965
|
+
buildTryBranchBody(catchBranch, status, b, parentName);
|
|
1966
|
+
b.append(`}${status.options.dev === true ? `, "runTry"` : ""});`);
|
|
1967
|
+
} else b.append(`}, null${status.options.dev === true ? `, "runTry"` : ""});`);
|
|
1968
|
+
addPopDevBoundary(status, b);
|
|
1969
|
+
b.append("");
|
|
1970
|
+
}
|
|
1971
|
+
function buildTryBranchBody(node, status, b, parentName) {
|
|
1972
|
+
if (node.children.length > 0) {
|
|
1973
|
+
buildFragment(node, status, b, parentName, "t_before");
|
|
1974
|
+
status.fragmentStack.push({
|
|
1975
|
+
fragment: node.fragment,
|
|
1976
|
+
path: ""
|
|
1977
|
+
});
|
|
1978
|
+
for (let child of node.children) buildNode(child, status, b, parentName, "t_before");
|
|
1979
|
+
status.fragmentStack.pop();
|
|
1980
|
+
buildAddFragment(node, status, b, parentName, "t_before");
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1409
1983
|
//#endregion
|
|
1410
1984
|
//#region src/compile/build/client/buildControlNode.ts
|
|
1411
1985
|
function buildControlNode(node, status, b) {
|
|
@@ -1430,8 +2004,12 @@ function buildControlNode(node, status, b) {
|
|
|
1430
2004
|
buildAwaitNode(node, status, b);
|
|
1431
2005
|
break;
|
|
1432
2006
|
case "@await":
|
|
1433
|
-
case "@
|
|
2007
|
+
case "@with":
|
|
2008
|
+
case "@try":
|
|
1434
2009
|
case "@catch": break;
|
|
2010
|
+
case "@try group":
|
|
2011
|
+
buildTryNode(node, status, b);
|
|
2012
|
+
break;
|
|
1435
2013
|
case "@replace group":
|
|
1436
2014
|
buildReplaceNode(node, status, b);
|
|
1437
2015
|
break;
|
|
@@ -1454,7 +2032,6 @@ function buildControlNode(node, status, b) {
|
|
|
1454
2032
|
default: throw new Error(`Invalid operation: ${node.operation}`);
|
|
1455
2033
|
}
|
|
1456
2034
|
}
|
|
1457
|
-
|
|
1458
2035
|
//#endregion
|
|
1459
2036
|
//#region src/compile/utils/trimEnd.ts
|
|
1460
2037
|
/**
|
|
@@ -1470,30 +2047,29 @@ function trimEnd(text, char) {
|
|
|
1470
2047
|
while (text[end - 1] === char) end -= 1;
|
|
1471
2048
|
return text.substring(0, end);
|
|
1472
2049
|
}
|
|
1473
|
-
|
|
1474
2050
|
//#endregion
|
|
1475
2051
|
//#region src/compile/build/client/buildMount.ts
|
|
1476
2052
|
function buildMount(_functionName, functionBody, status, b) {
|
|
1477
2053
|
functionBody = replaceForVarNames(functionBody, status);
|
|
1478
|
-
status.imports.add("$
|
|
1479
|
-
b.append("// @ts-ignore\n$
|
|
2054
|
+
status.imports.add("$onmount");
|
|
2055
|
+
b.append("// @ts-ignore\n$onmount(() => {");
|
|
1480
2056
|
b.append(functionBody);
|
|
1481
2057
|
b.append("});");
|
|
1482
2058
|
}
|
|
1483
|
-
|
|
1484
2059
|
//#endregion
|
|
1485
2060
|
//#region src/compile/build/client/stashRun.ts
|
|
1486
2061
|
function stashRun(fragment, functionStart, value, functionEnd, span, status) {
|
|
1487
2062
|
value = replaceForVarNames(value, status);
|
|
1488
2063
|
let functionBody = functionStart + value + functionEnd;
|
|
2064
|
+
let forVarMask = status.forVarNames.length > 0 ? forVarsReadIn(value, status.forVarNames) : void 0;
|
|
1489
2065
|
fragment.effects.push({
|
|
1490
2066
|
functionBody,
|
|
1491
2067
|
spans: [span],
|
|
1492
2068
|
offsets: [functionStart.length],
|
|
1493
|
-
lengths: [value.length]
|
|
2069
|
+
lengths: [value.length],
|
|
2070
|
+
forVarMask
|
|
1494
2071
|
});
|
|
1495
2072
|
}
|
|
1496
|
-
|
|
1497
2073
|
//#endregion
|
|
1498
2074
|
//#region src/compile/build/client/stashRunWithOffsets.ts
|
|
1499
2075
|
function stashRunWithOffsets(fragment, functionStart, value, functionEnd, spans, offsets, lengths, status) {
|
|
@@ -1506,17 +2082,18 @@ function stashRunWithOffsets(fragment, functionStart, value, functionEnd, spans,
|
|
|
1506
2082
|
lengths[i] += changedLength;
|
|
1507
2083
|
for (let j = i + 1; j < offsets.length; j++) offsets[j] += changedLength;
|
|
1508
2084
|
}
|
|
2085
|
+
let forVarMask = status.forVarNames.length > 0 ? forVarsReadIn(functionBody, status.forVarNames) : void 0;
|
|
1509
2086
|
fragment.effects.push({
|
|
1510
2087
|
functionBody,
|
|
1511
2088
|
spans,
|
|
1512
2089
|
offsets,
|
|
1513
|
-
lengths
|
|
2090
|
+
lengths,
|
|
2091
|
+
forVarMask
|
|
1514
2092
|
});
|
|
1515
2093
|
}
|
|
1516
|
-
|
|
1517
2094
|
//#endregion
|
|
1518
2095
|
//#region src/compile/build/client/buildElementNode.ts
|
|
1519
|
-
function buildElementNode(node, status, b, parentName
|
|
2096
|
+
function buildElementNode(node, status, b, parentName) {
|
|
1520
2097
|
if (status.inHead) {
|
|
1521
2098
|
if (node.tagName === "title") buildTitleNode(node, status, b);
|
|
1522
2099
|
else buildHeadNode(node, status, b);
|
|
@@ -1527,8 +2104,10 @@ function buildElementNode(node, status, b, parentName, root = false) {
|
|
|
1527
2104
|
if (svgElement) status.ns = true;
|
|
1528
2105
|
const varName = node.varName;
|
|
1529
2106
|
if (varName) {
|
|
1530
|
-
if (node.tagName === "@element")
|
|
1531
|
-
|
|
2107
|
+
if (node.tagName === "@element") {
|
|
2108
|
+
buildDynamicElementNode(node, status, b);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
1532
2111
|
buildElementAttributes$1(node, varName, status, b);
|
|
1533
2112
|
}
|
|
1534
2113
|
const oldPreserveWhitespace = status.preserveWhitespace;
|
|
@@ -1539,23 +2118,18 @@ function buildElementNode(node, status, b, parentName, root = false) {
|
|
|
1539
2118
|
}
|
|
1540
2119
|
function buildDynamicElementNode(node, status, b) {
|
|
1541
2120
|
let selfAttribute = node.attributes.find((a) => a.name === "self");
|
|
1542
|
-
if (selfAttribute && selfAttribute.value
|
|
1543
|
-
status.imports.add("$run");
|
|
2121
|
+
if (selfAttribute && selfAttribute.value) {
|
|
1544
2122
|
status.imports.add("t_dynamic");
|
|
1545
2123
|
let selfValue = selfAttribute.value;
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
path: ""
|
|
1553
|
-
});
|
|
1554
|
-
for (let child of node.children) buildNode(child, status, b, parentName, "null");
|
|
1555
|
-
status.fragmentStack.pop();
|
|
1556
|
-
buildAddFragment(node, status, b, parentName, "null");
|
|
1557
|
-
b.append(`}${status.options.dev === true ? `, "setDynamic"` : ""});`);
|
|
2124
|
+
if (selfAttribute.fullyReactive) {
|
|
2125
|
+
status.imports.add("$run");
|
|
2126
|
+
b.append("$run(() => {");
|
|
2127
|
+
b.append(`${node.varName} = t_dynamic(${node.varName}, ${selfValue});`);
|
|
2128
|
+
b.append(`}${status.options.dev === true ? `, "setDynamic"` : ""});`);
|
|
2129
|
+
} else b.append(`${node.varName} = t_dynamic(${node.varName}, ${selfValue});`);
|
|
1558
2130
|
}
|
|
2131
|
+
buildElementAttributes$1(node, node.varName, status, b);
|
|
2132
|
+
for (let child of node.children) buildNode(child, status, b, node.varName, "null");
|
|
1559
2133
|
}
|
|
1560
2134
|
function buildTitleNode(node, status, b) {
|
|
1561
2135
|
let content = node.children[0].content || "";
|
|
@@ -1598,11 +2172,6 @@ function buildHeadNode(node, status, b) {
|
|
|
1598
2172
|
function buildElementAttributes$1(node, varName, status, b) {
|
|
1599
2173
|
let fragment = status.fragmentStack.at(-1)?.fragment;
|
|
1600
2174
|
if (!fragment) throw new Error("No fragment on stack");
|
|
1601
|
-
const refAttribute = node.attributes.find((a) => a.name === "&ref");
|
|
1602
|
-
if (refAttribute) {
|
|
1603
|
-
let { value, fullyReactive } = refAttribute;
|
|
1604
|
-
if (value != null && fullyReactive) b.append(`${value} = ${varName};`);
|
|
1605
|
-
}
|
|
1606
2175
|
for (let { name, value, reactive, fullyReactive, span } of node.attributes) if (name === "self" && node.tagName === "@element") {} else if (name === "&ref") {} else if (value != null && fullyReactive) if (name === "&group") buildBindGroupAttribute(node, varName, value, status, b);
|
|
1607
2176
|
else if (name === "&value" || name === "&checked") buildBindAttribute(node, varName, name, value, status, b);
|
|
1608
2177
|
else if (name === "onmount") buildMount("elMount", `return (${trimEnd(value.trim(), ";")})(${varName});`, status, b);
|
|
@@ -1615,9 +2184,8 @@ function buildElementAttributes$1(node, varName, status, b) {
|
|
|
1615
2184
|
const propName = status.ns ? "className.baseVal" : "className";
|
|
1616
2185
|
stashRun(fragment, `${status.ns ? "// @ts-ignore\n" : ""}${varName}.${propName} = t_class(`, params.join(", "), ");", span, status);
|
|
1617
2186
|
} else if (name === "style") {
|
|
1618
|
-
status.imports.add("t_attribute");
|
|
1619
2187
|
status.imports.add("t_style");
|
|
1620
|
-
stashRun(fragment,
|
|
2188
|
+
stashRun(fragment, `${varName}.style.cssText += t_style(`, value, ");", span, status);
|
|
1621
2189
|
} else if (name.includes("-")) {
|
|
1622
2190
|
status.imports.add("t_attribute");
|
|
1623
2191
|
stashRun(fragment, `t_attribute(${varName}, "${name}", `, value, ");", span, status);
|
|
@@ -1636,7 +2204,7 @@ function buildElementAttributes$1(node, varName, status, b) {
|
|
|
1636
2204
|
stashRunWithOffsets(fragment, `${status.ns ? "// @ts-ignore\n" : ""}${varName}.${propName} = t_class(`, params.join(", "), ");", spans, offsets, lengths, status);
|
|
1637
2205
|
} else if (name === "style") {
|
|
1638
2206
|
status.imports.add("t_style");
|
|
1639
|
-
stashRunWithOffsets(fragment, `${varName}.
|
|
2207
|
+
stashRunWithOffsets(fragment, `${varName}.style.cssText += t_style(`, value, ");", spans, offsets, lengths, status);
|
|
1640
2208
|
} else if (name.includes("-")) {
|
|
1641
2209
|
status.imports.add("t_attribute");
|
|
1642
2210
|
stashRunWithOffsets(fragment, `t_attribute(${varName}, "${name}", `, value, ");", spans, offsets, lengths, status);
|
|
@@ -1679,9 +2247,18 @@ function buildBindAttribute(node, varName, name, value, status, b) {
|
|
|
1679
2247
|
inputValue = "e.target.value";
|
|
1680
2248
|
break;
|
|
1681
2249
|
}
|
|
1682
|
-
} else if (node.tagName === "select")
|
|
2250
|
+
} else if (node.tagName === "select") {
|
|
2251
|
+
eventName = "change";
|
|
2252
|
+
if (node.attributes.find((a) => a.name === "multiple")) {
|
|
2253
|
+
buildRun("setBinding", `Array.from(${varName}.options).forEach((opt) => opt.selected = Array.isArray(${value}) && ${value}.includes(opt.value));`, status, b);
|
|
2254
|
+
status.imports.add("t_event");
|
|
2255
|
+
b.append(`t_event(${varName}, "change", (e) => ${value} = Array.from(e.target.selectedOptions).map((opt) => opt.value));`);
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
1683
2259
|
let set = `${value} || ${defaultValue}`;
|
|
1684
|
-
|
|
2260
|
+
const propName = name.substring(1);
|
|
2261
|
+
buildRun("setBinding", `${propName === "value" ? `${varName}.${propName} = String(${set})` : `${varName}.${propName} = ${set}`};`, status, b);
|
|
1685
2262
|
status.imports.add("t_event");
|
|
1686
2263
|
b.append(`t_event(${varName}, "${eventName}", (e) => ${value} = ${inputValue});`);
|
|
1687
2264
|
}
|
|
@@ -1717,7 +2294,6 @@ function buildTransitionAttribute(node, varName, name, value, status, b) {
|
|
|
1717
2294
|
b.append(`t_animate(${varName}, null, ${entryVarName});`);
|
|
1718
2295
|
}
|
|
1719
2296
|
}
|
|
1720
|
-
|
|
1721
2297
|
//#endregion
|
|
1722
2298
|
//#region src/compile/build/client/buildRootNode.ts
|
|
1723
2299
|
function buildRootNode(node, status, b, parentName, anchorName) {
|
|
@@ -1730,13 +2306,12 @@ function buildRootNode(node, status, b, parentName, anchorName) {
|
|
|
1730
2306
|
status.fragmentStack.pop();
|
|
1731
2307
|
buildAddFragment(node, status, b, parentName, anchorName);
|
|
1732
2308
|
}
|
|
1733
|
-
|
|
1734
2309
|
//#endregion
|
|
1735
2310
|
//#region src/compile/build/client/buildSlotNode.ts
|
|
1736
2311
|
function buildSlotNode(node, status, b) {
|
|
1737
2312
|
let slotName = node.attributes.find((a) => a.name === "name")?.value;
|
|
1738
2313
|
slotName = slotName ? trimQuotes(slotName) : "_";
|
|
1739
|
-
const propsName = nextVarName("
|
|
2314
|
+
const propsName = nextVarName("slot_props", status);
|
|
1740
2315
|
const slotAttributes = node.attributes.filter((a) => a.name !== "name");
|
|
1741
2316
|
const slotHasProps = slotAttributes.length;
|
|
1742
2317
|
if (slotHasProps) {
|
|
@@ -1773,7 +2348,7 @@ function buildSlotNode(node, status, b) {
|
|
|
1773
2348
|
"$context"
|
|
1774
2349
|
];
|
|
1775
2350
|
b.append(`$slots["${slotName}"](${params.join(", ")})`);
|
|
1776
|
-
const fill = node.children.find((c) => isSpecialNode(c) && c.tagName === "fill");
|
|
2351
|
+
const fill = node.children.find((c) => isSpecialNode(c) && (c.tagName === "fill" || c.tagName === "filldef"));
|
|
1777
2352
|
if (fill && isSpecialNode(fill) && fill.children.length) {
|
|
1778
2353
|
b.append(`} else {`);
|
|
1779
2354
|
buildFragment(fill, status, b, slotParentName, slotAnchorName);
|
|
@@ -1787,7 +2362,6 @@ function buildSlotNode(node, status, b) {
|
|
|
1787
2362
|
}
|
|
1788
2363
|
b.append(`}`);
|
|
1789
2364
|
}
|
|
1790
|
-
|
|
1791
2365
|
//#endregion
|
|
1792
2366
|
//#region src/compile/build/client/buildSpecialNode.ts
|
|
1793
2367
|
function buildSpecialNode(node, status, b, parentName, root = false) {
|
|
@@ -1795,9 +2369,10 @@ function buildSpecialNode(node, status, b, parentName, root = false) {
|
|
|
1795
2369
|
case "slot":
|
|
1796
2370
|
buildSlotNode(node, status, b);
|
|
1797
2371
|
break;
|
|
1798
|
-
case "fill":
|
|
2372
|
+
case "fill":
|
|
2373
|
+
case "filldef": break;
|
|
1799
2374
|
case "@element":
|
|
1800
|
-
buildElementNode(node, status, b, parentName
|
|
2375
|
+
buildElementNode(node, status, b, parentName);
|
|
1801
2376
|
break;
|
|
1802
2377
|
case "@component":
|
|
1803
2378
|
buildComponentNode(node, status, b, root);
|
|
@@ -1806,7 +2381,6 @@ function buildSpecialNode(node, status, b, parentName, root = false) {
|
|
|
1806
2381
|
}
|
|
1807
2382
|
return "";
|
|
1808
2383
|
}
|
|
1809
|
-
|
|
1810
2384
|
//#endregion
|
|
1811
2385
|
//#region src/compile/build/client/buildTextNode.ts
|
|
1812
2386
|
function buildTextNode(node, status) {
|
|
@@ -1823,7 +2397,6 @@ function buildTextNode(node, status) {
|
|
|
1823
2397
|
let maxLevel = 0;
|
|
1824
2398
|
for (let i = 0; i < content.length; i++) {
|
|
1825
2399
|
const char = content[i];
|
|
1826
|
-
const nextChar = content[i + 1];
|
|
1827
2400
|
if (char === "{") {
|
|
1828
2401
|
level++;
|
|
1829
2402
|
maxLevel = Math.max(level, maxLevel);
|
|
@@ -1842,25 +2415,10 @@ function buildTextNode(node, status) {
|
|
|
1842
2415
|
}
|
|
1843
2416
|
} else if (char === "`" && level === 0) textContent += "\\";
|
|
1844
2417
|
else if (level > 0) {
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
continue;
|
|
1850
|
-
} else if (char === "/" && nextChar === "*") {
|
|
1851
|
-
let start = i;
|
|
1852
|
-
i = content.indexOf("*/", i) + 1;
|
|
1853
|
-
textContent += content.substring(start, i + 1);
|
|
1854
|
-
continue;
|
|
1855
|
-
} else if (char === "\"" || char === "'") {
|
|
1856
|
-
let start = i;
|
|
1857
|
-
i = endOfString(char, content, i);
|
|
1858
|
-
textContent += content.substring(start, i + 1);
|
|
1859
|
-
continue;
|
|
1860
|
-
} else if (char === "`") {
|
|
1861
|
-
let start = i;
|
|
1862
|
-
i = endOfTemplateString(content, i);
|
|
1863
|
-
textContent += content.substring(start, i + 1);
|
|
2418
|
+
const skipped = skipStringOrComment(content, i);
|
|
2419
|
+
if (skipped !== -1) {
|
|
2420
|
+
textContent += content.substring(i, skipped);
|
|
2421
|
+
i = skipped - 1;
|
|
1864
2422
|
continue;
|
|
1865
2423
|
}
|
|
1866
2424
|
}
|
|
@@ -1880,7 +2438,6 @@ function buildTextNode(node, status) {
|
|
|
1880
2438
|
stashRunWithOffsets(fragment, `${node.varName}.textContent = `, textContent, ";", node.spans, offsets, lengths, status);
|
|
1881
2439
|
}
|
|
1882
2440
|
}
|
|
1883
|
-
|
|
1884
2441
|
//#endregion
|
|
1885
2442
|
//#region src/compile/build/client/buildNode.ts
|
|
1886
2443
|
function buildNode(node, status, b, parentName, anchorName, root = false) {
|
|
@@ -1899,7 +2456,7 @@ function buildNode(node, status, b, parentName, anchorName, root = false) {
|
|
|
1899
2456
|
buildComponentNode(node, status, b, root);
|
|
1900
2457
|
break;
|
|
1901
2458
|
case "element":
|
|
1902
|
-
buildElementNode(node, status, b, parentName
|
|
2459
|
+
buildElementNode(node, status, b, parentName);
|
|
1903
2460
|
break;
|
|
1904
2461
|
case "text":
|
|
1905
2462
|
buildTextNode(node, status);
|
|
@@ -1907,28 +2464,40 @@ function buildNode(node, status, b, parentName, anchorName, root = false) {
|
|
|
1907
2464
|
case "special":
|
|
1908
2465
|
buildSpecialNode(node, status, b, parentName, root);
|
|
1909
2466
|
break;
|
|
2467
|
+
case "comment": break;
|
|
1910
2468
|
default: throw new Error(`Invalid node type: ${node.type}`);
|
|
1911
2469
|
}
|
|
1912
2470
|
}
|
|
1913
|
-
|
|
1914
2471
|
//#endregion
|
|
1915
2472
|
//#region src/compile/build/client/buildCode.ts
|
|
1916
2473
|
const importsMap$1 = {
|
|
1917
2474
|
$watch: "import { $watch } from \"${folder}\";",
|
|
2475
|
+
$bind: "import { $bind } from \"${folder}\";",
|
|
1918
2476
|
$cache: "import { $cache } from \"${folder}\";",
|
|
2477
|
+
$async: "import { $async } from \"${folder}\";",
|
|
2478
|
+
$pending: "import { $pending } from \"${folder}\";",
|
|
2479
|
+
$refresh: "import { $refresh } from \"${folder}\";",
|
|
1919
2480
|
$run: "import { $run } from \"${folder}\";",
|
|
1920
|
-
$
|
|
2481
|
+
$onmount: "import { $onmount } from \"${folder}\";",
|
|
2482
|
+
$stream: "import { $stream } from \"${folder}\";",
|
|
2483
|
+
fromElement: "import { fromElement } from \"${folder}\";",
|
|
2484
|
+
fromServer: "import { fromServer } from \"${folder}\";",
|
|
2485
|
+
fromWebSocket: "import { fromWebSocket } from \"${folder}\";",
|
|
1921
2486
|
$unwrap: "import { $unwrap } from \"${folder}\";",
|
|
1922
2487
|
$peek: "import { $peek } from \"${folder}\";",
|
|
1923
2488
|
$batch: "import { $batch } from \"${folder}\";",
|
|
1924
2489
|
t_region: "import { t_region } from \"${folder}\";",
|
|
1925
2490
|
t_push_region: "import { t_push_region } from \"${folder}\";",
|
|
1926
2491
|
t_pop_region: "import { t_pop_region } from \"${folder}\";",
|
|
2492
|
+
t_rerun_region_effects: "import { t_rerun_region_effects } from \"${folder}\";",
|
|
1927
2493
|
t_run_control: "import { t_run_control } from \"${folder}\";",
|
|
1928
2494
|
t_run_branch: "import { t_run_branch } from \"${folder}\";",
|
|
1929
2495
|
t_list_item: "import { t_list_item } from \"${folder}\";",
|
|
1930
2496
|
t_run_list: "import { t_run_list } from \"${folder}\";",
|
|
2497
|
+
t_run_await: "import { t_run_await } from \"${folder}\";",
|
|
2498
|
+
t_run_try: "import { t_run_try } from \"${folder}\";",
|
|
1931
2499
|
t_add_fragment: "import { t_add_fragment } from \"${folder}\";",
|
|
2500
|
+
t_add_element: "import { t_add_element } from \"${folder}\";",
|
|
1932
2501
|
t_apply_props: "import { t_apply_props } from \"${folder}\";",
|
|
1933
2502
|
t_class: "import { t_class } from \"${folder}\";",
|
|
1934
2503
|
t_style: "import { t_style } from \"${folder}\";",
|
|
@@ -1936,19 +2505,25 @@ const importsMap$1 = {
|
|
|
1936
2505
|
t_dynamic: "import { t_dynamic } from \"${folder}\";",
|
|
1937
2506
|
t_fmt: "import { t_fmt } from \"${folder}\";",
|
|
1938
2507
|
t_fragment: "import { t_fragment } from \"${folder}\";",
|
|
2508
|
+
t_fragment_el: "import { t_fragment_el } from \"${folder}\";",
|
|
1939
2509
|
t_event: "import { t_event } from \"${folder}\";",
|
|
1940
2510
|
t_animate: "import { t_animate } from \"${folder}\";",
|
|
1941
2511
|
t_root: "import { t_root } from \"${folder}\";",
|
|
2512
|
+
t_root_el: "import { t_root_el } from \"${folder}\";",
|
|
1942
2513
|
t_anchor: "import { t_anchor } from \"${folder}\";",
|
|
2514
|
+
t_first_inside: "import { t_first_inside } from \"${folder}\";",
|
|
1943
2515
|
t_child: "import { t_child } from \"${folder}\";",
|
|
1944
2516
|
t_next: "import { t_next } from \"${folder}\";",
|
|
1945
2517
|
t_skip: "import { t_skip } from \"${folder}\";",
|
|
1946
2518
|
t_frg: "import { t_frg } from \"${folder}\";",
|
|
2519
|
+
t_save_hydration: "import { t_save_hydration } from \"${folder}\";",
|
|
2520
|
+
t_restore_hydration: "import { t_restore_hydration } from \"${folder}\";",
|
|
1947
2521
|
t_elm: "import { t_elm } from \"${folder}\";",
|
|
1948
2522
|
t_txt: "import { t_txt } from \"${folder}\";",
|
|
1949
2523
|
t_cmt: "import { t_cmt } from \"${folder}\";",
|
|
1950
2524
|
t_print: "import { t_print } from \"${folder}\";",
|
|
1951
2525
|
ListItem: "import { type ListItem } from \"${folder}\";",
|
|
2526
|
+
ListItemSpec: "import { type ListItemSpec } from \"${folder}\";",
|
|
1952
2527
|
SlotRender: "import { type SlotRender } from \"${folder}\";",
|
|
1953
2528
|
devContext: "import { devContext } from \"${folder}/dev\";",
|
|
1954
2529
|
t_push_dev_bound: "import { t_push_dev_bound } from \"${folder}/dev\";",
|
|
@@ -1961,13 +2536,10 @@ function buildCode(template, map, options) {
|
|
|
1961
2536
|
buildTemplate(template, map, imports, b, options);
|
|
1962
2537
|
let startSize = b.toString().length;
|
|
1963
2538
|
if (imports.size) {
|
|
2539
|
+
const folder = options?.renderFolder ?? "@torpor/view";
|
|
2540
|
+
const sortedImports = Array.from(imports).map((imp) => (importsMap$1[imp] ?? imp).replace("${folder}", folder)).sort().reverse();
|
|
1964
2541
|
b.prepend("");
|
|
1965
|
-
for (let imp of
|
|
1966
|
-
let importStatement = importsMap$1[imp] ?? imp;
|
|
1967
|
-
let folder = options?.renderFolder ?? "@torpor/view";
|
|
1968
|
-
importStatement = importStatement.replace("${folder}", folder);
|
|
1969
|
-
b.prepend(importStatement);
|
|
1970
|
-
}
|
|
2542
|
+
for (let imp of sortedImports) b.prepend(imp);
|
|
1971
2543
|
}
|
|
1972
2544
|
if (options?.mapped) {
|
|
1973
2545
|
let diff = b.toString().length - startSize + 1;
|
|
@@ -1980,13 +2552,27 @@ function buildCode(template, map, options) {
|
|
|
1980
2552
|
}
|
|
1981
2553
|
function buildTemplate(template, map, imports, b, options) {
|
|
1982
2554
|
let script = template.script.map((s) => s.script).join("\n");
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
if (/\$
|
|
2555
|
+
for (const component of template.components) {
|
|
2556
|
+
if (component.markup) script += "\n" + collectMarkupExpressions(component.markup);
|
|
2557
|
+
if (component.error) script += "\n" + collectMarkupExpressions(component.error);
|
|
2558
|
+
if (component.head) script += "\n" + collectMarkupExpressions(component.head);
|
|
2559
|
+
}
|
|
2560
|
+
let scriptCode = codeRanges(script).map(([start, end]) => script.substring(start, end)).join("");
|
|
2561
|
+
if (/\$watch\b/.test(scriptCode)) imports.add("$watch");
|
|
2562
|
+
if (/\$bind\b/.test(scriptCode)) imports.add("$bind");
|
|
2563
|
+
if (/\$cache\b/.test(scriptCode)) imports.add("$cache");
|
|
2564
|
+
if (/\$async\b/.test(scriptCode)) imports.add("$async");
|
|
2565
|
+
if (/\$pending\b/.test(scriptCode)) imports.add("$pending");
|
|
2566
|
+
if (/\$refresh\b/.test(scriptCode)) imports.add("$refresh");
|
|
2567
|
+
if (/\$run\b/.test(scriptCode)) imports.add("$run");
|
|
2568
|
+
if (/\$onmount\b/.test(scriptCode)) imports.add("$onmount");
|
|
2569
|
+
if (/\$stream\b/.test(scriptCode)) imports.add("$stream");
|
|
2570
|
+
if (/\bfromElement\b/.test(scriptCode)) imports.add("fromElement");
|
|
2571
|
+
if (/\bfromServer\b/.test(scriptCode)) imports.add("fromServer");
|
|
2572
|
+
if (/\bfromWebSocket\b/.test(scriptCode)) imports.add("fromWebSocket");
|
|
2573
|
+
if (/\$unwrap\b/.test(scriptCode)) imports.add("$unwrap");
|
|
2574
|
+
if (/\$peek\b/.test(scriptCode)) imports.add("$peek");
|
|
2575
|
+
if (/\$batch\b/.test(scriptCode)) imports.add("$batch");
|
|
1990
2576
|
let currentIndex = 0;
|
|
1991
2577
|
let current = template.components[0];
|
|
1992
2578
|
let status = makeStatus(imports, map, current, options);
|
|
@@ -1998,10 +2584,10 @@ function buildTemplate(template, map, imports, b, options) {
|
|
|
1998
2584
|
${current.markup ? "$parent" : "_$parent"}: ParentNode,
|
|
1999
2585
|
${current.markup ? "$anchor" : "_$anchor"}: Node | null,`);
|
|
2000
2586
|
if (current.params !== void 0) addMappedText("", current.params, ",", chunk.span, status, b);
|
|
2001
|
-
else b.append(`${current.props?.length ? "$props: Record<PropertyKey, any>" : "
|
|
2587
|
+
else b.append(`${current.props?.length ? "$props: Record<PropertyKey, any>" : "_$props?: Record<PropertyKey, any>"},`);
|
|
2002
2588
|
b.append(`
|
|
2003
|
-
|
|
2004
|
-
|
|
2589
|
+
${current.contextProps?.length || current.markup && markupRendersComponent(current.markup) || current.error && markupRendersComponent(current.error) ? "$context" : "_$context"}?: Record<PropertyKey, any>,
|
|
2590
|
+
${current.slotProps?.length ? "$slots" : "_$slots"}?: Record<string, SlotRender>,`);
|
|
2005
2591
|
} else if (chunk.script === ") /* @return_type */ {") b.append("): void {");
|
|
2006
2592
|
else if (chunk.script === "/* @start */") {
|
|
2007
2593
|
addPushDevBoundary("component", current.name || "Anon Component", status, b);
|
|
@@ -2010,10 +2596,26 @@ function buildTemplate(template, map, imports, b, options) {
|
|
|
2010
2596
|
if (current.markup) {
|
|
2011
2597
|
b.append("");
|
|
2012
2598
|
b.append("/* User interface */");
|
|
2599
|
+
if (current.error) {
|
|
2600
|
+
status.imports.add("t_region");
|
|
2601
|
+
status.imports.add("t_run_try");
|
|
2602
|
+
b.append(`const t_error_region = t_region();`);
|
|
2603
|
+
b.append(`t_run_try(t_error_region, $anchor, () => {`);
|
|
2604
|
+
}
|
|
2013
2605
|
buildFragmentText(current.markup, status, b);
|
|
2014
2606
|
b.append("");
|
|
2015
2607
|
buildNode(current.markup, status, b, "$parent", "$anchor", true);
|
|
2016
2608
|
}
|
|
2609
|
+
} else if (chunk.script === "/* @error */") {
|
|
2610
|
+
if (current.markup && current.error) {
|
|
2611
|
+
b.append(`}, (_, ${current.errorVar ?? "err"}) => {`);
|
|
2612
|
+
b.append("");
|
|
2613
|
+
b.append("/* User interface error */");
|
|
2614
|
+
buildFragmentText(current.error, status, b);
|
|
2615
|
+
b.append("");
|
|
2616
|
+
buildNode(current.error, status, b, "$parent", "$anchor", true);
|
|
2617
|
+
b.append(`}${status.options.dev === true ? ", \"runError\"" : ""});`);
|
|
2618
|
+
}
|
|
2017
2619
|
} else if (chunk.script === "/* @head */") {
|
|
2018
2620
|
if (current.head) {
|
|
2019
2621
|
b.append("");
|
|
@@ -2045,15 +2647,26 @@ function makeStatus(imports, map, current, options) {
|
|
|
2045
2647
|
options: options ?? {}
|
|
2046
2648
|
};
|
|
2047
2649
|
}
|
|
2048
|
-
|
|
2049
2650
|
//#endregion
|
|
2050
2651
|
//#region src/compile/build/client/buildStyles.ts
|
|
2051
2652
|
function buildStyles(style, styleHash) {
|
|
2052
2653
|
const b = new Builder();
|
|
2053
|
-
for (let block of style.
|
|
2654
|
+
for (let block of style.children) buildStyleNode(block, b, styleHash);
|
|
2054
2655
|
return b.toString();
|
|
2055
2656
|
}
|
|
2056
|
-
function
|
|
2657
|
+
function buildStyleNode(node, b, styleHash) {
|
|
2658
|
+
switch (node.type) {
|
|
2659
|
+
case "block":
|
|
2660
|
+
buildBlockNode(node, b, styleHash);
|
|
2661
|
+
break;
|
|
2662
|
+
case "attribute":
|
|
2663
|
+
buildAttributeNode(node, b);
|
|
2664
|
+
break;
|
|
2665
|
+
case "comment": break;
|
|
2666
|
+
default: throw new Error(`Invalid style node type: ${node.type}`);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
function buildBlockNode(block, b, styleHash) {
|
|
2057
2670
|
if (block.selector.startsWith("@")) b.append(`${block.selector} {`);
|
|
2058
2671
|
else {
|
|
2059
2672
|
block.selector = block.selector.replaceAll(/\s+/g, " ");
|
|
@@ -2079,8 +2692,7 @@ function buildStyleBlock(block, b, styleHash) {
|
|
|
2079
2692
|
}
|
|
2080
2693
|
b.append(`${selectors.join(" ").replaceAll(" ,", ",")} {`);
|
|
2081
2694
|
}
|
|
2082
|
-
for (let
|
|
2083
|
-
for (let child of block.children) buildStyleBlock(child, b, styleHash);
|
|
2695
|
+
for (let child of block.children) buildStyleNode(child, b, styleHash);
|
|
2084
2696
|
b.append(`}`);
|
|
2085
2697
|
}
|
|
2086
2698
|
function addSelector(selectors, selector, styleHash, start, end, global) {
|
|
@@ -2099,10 +2711,9 @@ function addSelector(selectors, selector, styleHash, start, end, global) {
|
|
|
2099
2711
|
selectors.push(s);
|
|
2100
2712
|
}
|
|
2101
2713
|
}
|
|
2102
|
-
function
|
|
2714
|
+
function buildAttributeNode(attribute, b) {
|
|
2103
2715
|
b.append(`${attribute.name}: ${attribute.value};`);
|
|
2104
2716
|
}
|
|
2105
|
-
|
|
2106
2717
|
//#endregion
|
|
2107
2718
|
//#region src/compile/build/server/buildServerComponentNode.ts
|
|
2108
2719
|
function buildServerComponentNode(node, status, b) {
|
|
@@ -2118,20 +2729,16 @@ function buildServerComponentNode(node, status, b) {
|
|
|
2118
2729
|
for (let { name, value } of node.attributes) {
|
|
2119
2730
|
if (name.startsWith("&")) name = name.substring(1);
|
|
2120
2731
|
if (name === "class" && value != null) {
|
|
2121
|
-
status.
|
|
2122
|
-
const params = [value];
|
|
2123
|
-
if (node.scopeStyles) params.push(`"torp-${status.styleHash}"`);
|
|
2124
|
-
props.push({
|
|
2125
|
-
name,
|
|
2126
|
-
value: `t_class(${params.join(", ")})`
|
|
2127
|
-
});
|
|
2128
|
-
} else if (name === "style" && value != null) {
|
|
2129
|
-
status.imports.add("t_style");
|
|
2732
|
+
if (node.scopeStyles) value = `[${value}, "torp-${status.styleHash}"]`;
|
|
2130
2733
|
props.push({
|
|
2131
2734
|
name,
|
|
2132
|
-
value
|
|
2735
|
+
value
|
|
2133
2736
|
});
|
|
2134
|
-
} else if (value != null) props.push({
|
|
2737
|
+
} else if (name === "style" && value != null) props.push({
|
|
2738
|
+
name,
|
|
2739
|
+
value
|
|
2740
|
+
});
|
|
2741
|
+
else if (value != null) props.push({
|
|
2135
2742
|
name,
|
|
2136
2743
|
value
|
|
2137
2744
|
});
|
|
@@ -2141,17 +2748,17 @@ function buildServerComponentNode(node, status, b) {
|
|
|
2141
2748
|
});
|
|
2142
2749
|
}
|
|
2143
2750
|
b.append(`const ${propsName} = {`);
|
|
2144
|
-
for (let p of props) b.append(`${p.name}: ${p.value},`);
|
|
2751
|
+
for (let p of props) b.append(p.name ? `${p.name}: ${p.value},` : `${p.value},`);
|
|
2145
2752
|
b.append("};");
|
|
2146
2753
|
}
|
|
2147
2754
|
const componentHasSlots = node.children.length;
|
|
2148
2755
|
const slotsName = componentHasSlots ? nextVarName("slots", status) : "undefined";
|
|
2149
2756
|
if (componentHasSlots) {
|
|
2150
2757
|
b.append(`const ${slotsName}: Record<string, ServerSlotRender> = {};`);
|
|
2151
|
-
for (let slot of node.children) if (isSpecialNode(slot) && slot.tagName === "fill") {
|
|
2758
|
+
for (let slot of node.children) if (isSpecialNode(slot) && (slot.tagName === "fill" || slot.tagName === "filldef")) {
|
|
2152
2759
|
const nameAttribute = slot.attributes.find((a) => a.name === "name");
|
|
2153
2760
|
const slotName = nameAttribute?.value ? trimQuotes(nameAttribute.value) : "_";
|
|
2154
|
-
const slotParams = [slot.hasSlotProps ? "$
|
|
2761
|
+
const slotParams = [slot.hasSlotProps ? "$slot: Record<PropertyKey, any>" : "_$slot?: Record<PropertyKey, any>", "// @ts-ignore\n// eslint-disable-next-line no-unused-vars\n$context?: Record<PropertyKey, any>"];
|
|
2155
2762
|
b.append(`${slot.hasSlotProps ? "// @ts-ignore\n" : ""}${slotsName}["${slotName}"] = (\n${slotParams.join(",\n")}\n) => {`);
|
|
2156
2763
|
b.append(`let t_body = "";`);
|
|
2157
2764
|
for (let child of slot.children) buildServerNode(child, status, b);
|
|
@@ -2175,9 +2782,8 @@ function buildServerComponentNode(node, status, b) {
|
|
|
2175
2782
|
b.append(`t_body += ${componentResult}.body;`);
|
|
2176
2783
|
b.append(`t_head += ${componentResult}.head;`);
|
|
2177
2784
|
status.output += HYDRATION_END_COMMENT;
|
|
2178
|
-
status.output +=
|
|
2785
|
+
status.output += "<!>";
|
|
2179
2786
|
}
|
|
2180
|
-
|
|
2181
2787
|
//#endregion
|
|
2182
2788
|
//#region src/compile/build/server/buildServerAwaitNode.ts
|
|
2183
2789
|
function buildServerAwaitNode(node, status, b) {
|
|
@@ -2186,9 +2792,9 @@ function buildServerAwaitNode(node, status, b) {
|
|
|
2186
2792
|
b.append(`t_body += \`${status.output}\`;`);
|
|
2187
2793
|
status.output = "";
|
|
2188
2794
|
}
|
|
2189
|
-
for (let branch of node.children) if (isControlNode(branch) && branch.operation === "@
|
|
2795
|
+
for (let branch of node.children) if (isControlNode(branch) && branch.operation === "@with") buildServerAwaitBranch(branch, status, b);
|
|
2190
2796
|
status.output += HYDRATION_END_COMMENT;
|
|
2191
|
-
status.output +=
|
|
2797
|
+
status.output += "<!>";
|
|
2192
2798
|
}
|
|
2193
2799
|
function buildServerAwaitBranch(node, status, b) {
|
|
2194
2800
|
for (let child of node.children) buildServerNode(child, status, b);
|
|
@@ -2197,7 +2803,6 @@ function buildServerAwaitBranch(node, status, b) {
|
|
|
2197
2803
|
status.output = "";
|
|
2198
2804
|
}
|
|
2199
2805
|
}
|
|
2200
|
-
|
|
2201
2806
|
//#endregion
|
|
2202
2807
|
//#region src/compile/build/server/buildServerForNode.ts
|
|
2203
2808
|
function buildServerForNode(node, status, b) {
|
|
@@ -2208,7 +2813,7 @@ function buildServerForNode(node, status, b) {
|
|
|
2208
2813
|
}
|
|
2209
2814
|
for (let branch of node.children) if (isControlNode(branch)) buildServerForBranch(branch, status, b);
|
|
2210
2815
|
status.output += HYDRATION_END_COMMENT;
|
|
2211
|
-
status.output +=
|
|
2816
|
+
status.output += "<!>";
|
|
2212
2817
|
}
|
|
2213
2818
|
function buildServerForBranch(node, status, b) {
|
|
2214
2819
|
b.append(`${node.statement} {`);
|
|
@@ -2220,16 +2825,14 @@ function buildServerForBranch(node, status, b) {
|
|
|
2220
2825
|
}
|
|
2221
2826
|
b.append("}");
|
|
2222
2827
|
}
|
|
2223
|
-
|
|
2224
2828
|
//#endregion
|
|
2225
2829
|
//#region src/compile/build/server/buildServerHtmlNode.ts
|
|
2226
2830
|
function buildServerHtmlNode(node, status) {
|
|
2227
2831
|
status.output += HYDRATION_START_COMMENT;
|
|
2228
2832
|
for (let branch of node.children) if (isControlNode(branch)) status.output += `\${${branch.statement}}`;
|
|
2229
2833
|
status.output += HYDRATION_END_COMMENT;
|
|
2230
|
-
status.output +=
|
|
2834
|
+
status.output += "<!>";
|
|
2231
2835
|
}
|
|
2232
|
-
|
|
2233
2836
|
//#endregion
|
|
2234
2837
|
//#region src/compile/build/server/buildServerIfNode.ts
|
|
2235
2838
|
function buildServerIfNode(node, status, b) {
|
|
@@ -2240,7 +2843,7 @@ function buildServerIfNode(node, status, b) {
|
|
|
2240
2843
|
}
|
|
2241
2844
|
for (let branch of node.children) if (isControlNode(branch)) buildServerIfBranch(branch, status, b);
|
|
2242
2845
|
status.output += HYDRATION_END_COMMENT;
|
|
2243
|
-
status.output +=
|
|
2846
|
+
status.output += "<!>";
|
|
2244
2847
|
}
|
|
2245
2848
|
function buildServerIfBranch(node, status, b) {
|
|
2246
2849
|
b.append(`${node.statement} {`);
|
|
@@ -2252,7 +2855,6 @@ function buildServerIfBranch(node, status, b) {
|
|
|
2252
2855
|
}
|
|
2253
2856
|
b.append("}");
|
|
2254
2857
|
}
|
|
2255
|
-
|
|
2256
2858
|
//#endregion
|
|
2257
2859
|
//#region src/compile/build/server/buildServerReplaceNode.ts
|
|
2258
2860
|
function buildServerReplaceNode(node, status, b) {
|
|
@@ -2263,7 +2865,7 @@ function buildServerReplaceNode(node, status, b) {
|
|
|
2263
2865
|
}
|
|
2264
2866
|
for (let branch of node.children) if (isControlNode(branch)) buildServerReplaceBranch(branch, status, b);
|
|
2265
2867
|
status.output += HYDRATION_END_COMMENT;
|
|
2266
|
-
status.output +=
|
|
2868
|
+
status.output += "<!>";
|
|
2267
2869
|
}
|
|
2268
2870
|
function buildServerReplaceBranch(node, status, b) {
|
|
2269
2871
|
b.append(`${node.statement};`);
|
|
@@ -2273,7 +2875,6 @@ function buildServerReplaceBranch(node, status, b) {
|
|
|
2273
2875
|
status.output = "";
|
|
2274
2876
|
}
|
|
2275
2877
|
}
|
|
2276
|
-
|
|
2277
2878
|
//#endregion
|
|
2278
2879
|
//#region src/compile/build/server/buildServerScriptNode.ts
|
|
2279
2880
|
function buildServerScriptNode(node, b) {
|
|
@@ -2283,7 +2884,6 @@ function maybeAppend(text, end) {
|
|
|
2283
2884
|
if (!text.endsWith(end)) text += end;
|
|
2284
2885
|
return text;
|
|
2285
2886
|
}
|
|
2286
|
-
|
|
2287
2887
|
//#endregion
|
|
2288
2888
|
//#region src/compile/build/server/buildServerSwitchNode.ts
|
|
2289
2889
|
function buildServerSwitchNode(node, status, b) {
|
|
@@ -2296,7 +2896,7 @@ function buildServerSwitchNode(node, status, b) {
|
|
|
2296
2896
|
for (let branch of node.children.filter((c) => isControlNode(c))) if (isControlNode(branch)) buildServerSwitchBranch(branch, status, b);
|
|
2297
2897
|
b.append("}");
|
|
2298
2898
|
status.output += HYDRATION_END_COMMENT;
|
|
2299
|
-
status.output +=
|
|
2899
|
+
status.output += "<!>";
|
|
2300
2900
|
}
|
|
2301
2901
|
function buildServerSwitchBranch(node, status, b) {
|
|
2302
2902
|
b.append(`${node.statement} {`);
|
|
@@ -2309,7 +2909,39 @@ function buildServerSwitchBranch(node, status, b) {
|
|
|
2309
2909
|
b.append("break;");
|
|
2310
2910
|
b.append("}");
|
|
2311
2911
|
}
|
|
2312
|
-
|
|
2912
|
+
//#endregion
|
|
2913
|
+
//#region src/compile/build/server/buildServerTryNode.ts
|
|
2914
|
+
function buildServerTryNode(node, status, b) {
|
|
2915
|
+
status.output += HYDRATION_START_COMMENT;
|
|
2916
|
+
if (status.output) {
|
|
2917
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2918
|
+
status.output = "";
|
|
2919
|
+
}
|
|
2920
|
+
const branches = node.children.filter((n) => isControlNode(n));
|
|
2921
|
+
let tryBranch = branches.find((n) => n.operation === "@try");
|
|
2922
|
+
let catchBranch = branches.find((n) => n.operation === "@catch");
|
|
2923
|
+
if (catchBranch) {
|
|
2924
|
+
b.append(`const t_try_body = t_body;`);
|
|
2925
|
+
b.append(`const t_try_head = t_head;`);
|
|
2926
|
+
b.append("try {");
|
|
2927
|
+
if (tryBranch) buildServerTryBranch(tryBranch, status, b);
|
|
2928
|
+
const catchVar = trimMatched(catchBranch.statement.substring(5).trim(), "(", ")");
|
|
2929
|
+
b.append(`} catch (${catchVar || "err"}) {`);
|
|
2930
|
+
b.append("t_body = t_try_body;");
|
|
2931
|
+
b.append("t_head = t_try_head;");
|
|
2932
|
+
buildServerTryBranch(catchBranch, status, b);
|
|
2933
|
+
b.append("}");
|
|
2934
|
+
} else if (tryBranch) buildServerTryBranch(tryBranch, status, b);
|
|
2935
|
+
status.output += HYDRATION_END_COMMENT;
|
|
2936
|
+
status.output += "<!>";
|
|
2937
|
+
}
|
|
2938
|
+
function buildServerTryBranch(node, status, b) {
|
|
2939
|
+
for (let child of node.children) buildServerNode(child, status, b);
|
|
2940
|
+
if (status.output) {
|
|
2941
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2942
|
+
status.output = "";
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2313
2945
|
//#endregion
|
|
2314
2946
|
//#region src/compile/build/server/buildServerControlNode.ts
|
|
2315
2947
|
function buildServerControlNode(node, status, b) {
|
|
@@ -2334,8 +2966,12 @@ function buildServerControlNode(node, status, b) {
|
|
|
2334
2966
|
buildServerAwaitNode(node, status, b);
|
|
2335
2967
|
break;
|
|
2336
2968
|
case "@await":
|
|
2337
|
-
case "@
|
|
2969
|
+
case "@with":
|
|
2970
|
+
case "@try":
|
|
2338
2971
|
case "@catch": break;
|
|
2972
|
+
case "@try group":
|
|
2973
|
+
buildServerTryNode(node, status, b);
|
|
2974
|
+
break;
|
|
2339
2975
|
case "@replace group":
|
|
2340
2976
|
buildServerReplaceNode(node, status, b);
|
|
2341
2977
|
break;
|
|
@@ -2366,27 +3002,6 @@ function buildServerControlNode(node, status, b) {
|
|
|
2366
3002
|
default: throw new Error(`Invalid operation: ${node.operation}`);
|
|
2367
3003
|
}
|
|
2368
3004
|
}
|
|
2369
|
-
|
|
2370
|
-
//#endregion
|
|
2371
|
-
//#region src/compile/utils/voidTags.ts
|
|
2372
|
-
const voidTags = [
|
|
2373
|
-
"area",
|
|
2374
|
-
"base",
|
|
2375
|
-
"br",
|
|
2376
|
-
"col",
|
|
2377
|
-
"embed",
|
|
2378
|
-
"hr",
|
|
2379
|
-
"img",
|
|
2380
|
-
"input",
|
|
2381
|
-
"link",
|
|
2382
|
-
"meta",
|
|
2383
|
-
"param",
|
|
2384
|
-
"source",
|
|
2385
|
-
"track",
|
|
2386
|
-
"wbr"
|
|
2387
|
-
];
|
|
2388
|
-
var voidTags_default = voidTags;
|
|
2389
|
-
|
|
2390
3005
|
//#endregion
|
|
2391
3006
|
//#region src/compile/build/server/buildServerElementNode.ts
|
|
2392
3007
|
function buildServerElementNode(node, status, b) {
|
|
@@ -2398,7 +3013,7 @@ function buildServerElementNode(node, status, b) {
|
|
|
2398
3013
|
if (selfAttribute) tagName = `$\{${selfAttribute.value}}`;
|
|
2399
3014
|
}
|
|
2400
3015
|
status.output += `<${tagName}${attributes}>`;
|
|
2401
|
-
if (!
|
|
3016
|
+
if (!voidTags.includes(tagName)) {
|
|
2402
3017
|
const oldPreserveWhitespace = status.preserveWhitespace;
|
|
2403
3018
|
status.preserveWhitespace = ["pre", "code"].includes(tagName);
|
|
2404
3019
|
for (let child of node.children) buildServerNode(child, status, b);
|
|
@@ -2410,6 +3025,7 @@ function buildElementAttributes(node, status) {
|
|
|
2410
3025
|
let needsClass = node.scopeStyles;
|
|
2411
3026
|
let attributes = [];
|
|
2412
3027
|
for (let { name, value, reactive, fullyReactive } of node.attributes) if (name === "self" && node.tagName === "@element") {} else if (name === "&ref") {} else if (name.startsWith("on") || name.startsWith(":on")) {} else if (name.startsWith("transition") && value) {} else if (value != null && fullyReactive) if (name === "&value" || name === "&checked" || name === "&group") {
|
|
3028
|
+
if (name === "&value" && node.tagName === "select" && node.attributes.some((a) => a.name === "multiple")) continue;
|
|
2413
3029
|
let defaultValue = "";
|
|
2414
3030
|
let typeAttribute = node.attributes.find((a) => a.name === "type");
|
|
2415
3031
|
if (typeAttribute && typeAttribute.value) switch (trimQuotes(typeAttribute.value)) {
|
|
@@ -2456,13 +3072,11 @@ function buildElementAttributes(node, status) {
|
|
|
2456
3072
|
if (needsClass) attributes.push(`class="torp-${status.styleHash}"`);
|
|
2457
3073
|
return attributes.join(" ");
|
|
2458
3074
|
}
|
|
2459
|
-
|
|
2460
3075
|
//#endregion
|
|
2461
3076
|
//#region src/compile/build/server/buildServerRootNode.ts
|
|
2462
3077
|
function buildServerRootNode(node, status, b) {
|
|
2463
3078
|
for (let child of node.children) buildServerNode(child, status, b);
|
|
2464
3079
|
}
|
|
2465
|
-
|
|
2466
3080
|
//#endregion
|
|
2467
3081
|
//#region src/compile/build/server/buildServerSlotNode.ts
|
|
2468
3082
|
function buildServerSlotNode(node, status, b) {
|
|
@@ -2473,7 +3087,7 @@ function buildServerSlotNode(node, status, b) {
|
|
|
2473
3087
|
}
|
|
2474
3088
|
let slotName = node.attributes.find((a) => a.name === "name")?.value;
|
|
2475
3089
|
slotName = slotName ? trimQuotes(slotName) : "_";
|
|
2476
|
-
const propsName = nextVarName("
|
|
3090
|
+
const propsName = nextVarName("slot_props", status);
|
|
2477
3091
|
const slotAttributes = node.attributes.filter((a) => a.name !== "name");
|
|
2478
3092
|
const slotHasProps = slotAttributes.length;
|
|
2479
3093
|
if (slotHasProps) {
|
|
@@ -2483,7 +3097,7 @@ function buildServerSlotNode(node, status, b) {
|
|
|
2483
3097
|
}
|
|
2484
3098
|
b.append(`if ($slots && $slots["${slotName}"]) {`);
|
|
2485
3099
|
b.append(`t_body += $slots["${slotName}"](${slotHasProps ? propsName : "undefined"}, $context);`);
|
|
2486
|
-
const fill = node.children.find((c) => isSpecialNode(c) && c.tagName === "fill");
|
|
3100
|
+
const fill = node.children.find((c) => isSpecialNode(c) && (c.tagName === "fill" || c.tagName === "filldef"));
|
|
2487
3101
|
if (fill && isSpecialNode(fill) && fill.children.length) {
|
|
2488
3102
|
b.append(`} else {`);
|
|
2489
3103
|
for (let child of fill.children) buildServerNode(child, status, b);
|
|
@@ -2494,9 +3108,8 @@ function buildServerSlotNode(node, status, b) {
|
|
|
2494
3108
|
}
|
|
2495
3109
|
b.append(`}`);
|
|
2496
3110
|
status.output += HYDRATION_END_COMMENT;
|
|
2497
|
-
status.output +=
|
|
3111
|
+
status.output += "<!>";
|
|
2498
3112
|
}
|
|
2499
|
-
|
|
2500
3113
|
//#endregion
|
|
2501
3114
|
//#region src/compile/build/server/buildServerSpecialNode.ts
|
|
2502
3115
|
function buildServerSpecialNode(node, status, b) {
|
|
@@ -2504,7 +3117,8 @@ function buildServerSpecialNode(node, status, b) {
|
|
|
2504
3117
|
case "slot":
|
|
2505
3118
|
buildServerSlotNode(node, status, b);
|
|
2506
3119
|
break;
|
|
2507
|
-
case "fill":
|
|
3120
|
+
case "fill":
|
|
3121
|
+
case "filldef": break;
|
|
2508
3122
|
case "@element":
|
|
2509
3123
|
buildServerElementNode(node, status, b);
|
|
2510
3124
|
break;
|
|
@@ -2514,7 +3128,6 @@ function buildServerSpecialNode(node, status, b) {
|
|
|
2514
3128
|
default: throw new Error(`Invalid special node: ${node.tagName}`);
|
|
2515
3129
|
}
|
|
2516
3130
|
}
|
|
2517
|
-
|
|
2518
3131
|
//#endregion
|
|
2519
3132
|
//#region src/compile/build/server/buildServerTextNode.ts
|
|
2520
3133
|
function buildServerTextNode(node, status) {
|
|
@@ -2523,7 +3136,6 @@ function buildServerTextNode(node, status) {
|
|
|
2523
3136
|
let level = 0;
|
|
2524
3137
|
for (let i = 0; i < content.length; i++) {
|
|
2525
3138
|
const char = content[i];
|
|
2526
|
-
const nextChar = content[i + 1];
|
|
2527
3139
|
if (char === "{") {
|
|
2528
3140
|
level++;
|
|
2529
3141
|
if (level === 1) {
|
|
@@ -2536,35 +3148,16 @@ function buildServerTextNode(node, status) {
|
|
|
2536
3148
|
if (level === 0) status.output += ")";
|
|
2537
3149
|
} else if (char === "`" && level === 0) status.output += "\\";
|
|
2538
3150
|
else if (level > 0) {
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
continue;
|
|
2544
|
-
} else if (char === "/" && nextChar === "*") {
|
|
2545
|
-
let start = i;
|
|
2546
|
-
i = content.indexOf("*/", i) + 1;
|
|
2547
|
-
status.output += escapeHtml(content.substring(start, i + 1));
|
|
2548
|
-
continue;
|
|
2549
|
-
} else if (char === "\"" || char === "'") {
|
|
2550
|
-
let start = i;
|
|
2551
|
-
i = endOfString(char, content, i);
|
|
2552
|
-
status.output += escapeHtml(content.substring(start, i + 1));
|
|
2553
|
-
continue;
|
|
2554
|
-
} else if (char === "`") {
|
|
2555
|
-
let start = i;
|
|
2556
|
-
i = endOfTemplateString(content, i);
|
|
2557
|
-
status.output += escapeHtml(content.substring(start, i + 1));
|
|
3151
|
+
const skipped = skipStringOrComment(content, i);
|
|
3152
|
+
if (skipped !== -1) {
|
|
3153
|
+
status.output += content.substring(i, skipped);
|
|
3154
|
+
i = skipped - 1;
|
|
2558
3155
|
continue;
|
|
2559
3156
|
}
|
|
2560
3157
|
}
|
|
2561
3158
|
status.output += char;
|
|
2562
3159
|
}
|
|
2563
3160
|
}
|
|
2564
|
-
function escapeHtml(text) {
|
|
2565
|
-
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
3161
|
//#endregion
|
|
2569
3162
|
//#region src/compile/build/server/buildServerNode.ts
|
|
2570
3163
|
function buildServerNode(node, status, b) {
|
|
@@ -2587,17 +3180,25 @@ function buildServerNode(node, status, b) {
|
|
|
2587
3180
|
case "special":
|
|
2588
3181
|
buildServerSpecialNode(node, status, b);
|
|
2589
3182
|
break;
|
|
3183
|
+
case "comment": break;
|
|
2590
3184
|
default: throw new Error(`Invalid node type: ${node.type}`);
|
|
2591
3185
|
}
|
|
2592
3186
|
}
|
|
2593
|
-
|
|
2594
3187
|
//#endregion
|
|
2595
3188
|
//#region src/compile/build/server/buildServerCode.ts
|
|
2596
3189
|
const importsMap = {
|
|
2597
3190
|
$watch: "import { $watch } from \"${folder}\";",
|
|
3191
|
+
$bind: "import { $bind } from \"${folder}\";",
|
|
2598
3192
|
$cache: "import { $cache } from \"${folder}\";",
|
|
3193
|
+
$async: "import { $async } from \"${folder}\";",
|
|
3194
|
+
$pending: "import { $pending } from \"${folder}\";",
|
|
3195
|
+
$refresh: "import { $refresh } from \"${folder}\";",
|
|
2599
3196
|
$run: "import { $run } from \"${folder}\";",
|
|
2600
|
-
$
|
|
3197
|
+
$onmount: "import { $onmount } from \"${folder}\";",
|
|
3198
|
+
$stream: "import { $stream } from \"${folder}\";",
|
|
3199
|
+
fromElement: "import { fromElement } from \"${folder}\";",
|
|
3200
|
+
fromServer: "import { fromServer } from \"${folder}\";",
|
|
3201
|
+
fromWebSocket: "import { fromWebSocket } from \"${folder}\";",
|
|
2601
3202
|
$unwrap: "import { $unwrap } from \"${folder}\";",
|
|
2602
3203
|
$peek: "import { $peek } from \"${folder}\";",
|
|
2603
3204
|
$batch: "import { $batch } from \"${folder}\";",
|
|
@@ -2613,33 +3214,46 @@ function buildServerCode(template, options) {
|
|
|
2613
3214
|
imports.add("ServerSlotRender");
|
|
2614
3215
|
buildServerTemplate(template, imports, b, options);
|
|
2615
3216
|
if (imports.size) {
|
|
3217
|
+
const folder = options?.renderFolder ?? "@torpor/view/ssr";
|
|
3218
|
+
const sortedImports = Array.from(imports).map((imp) => (importsMap[imp] ?? imp).replace("${folder}", folder)).sort().reverse();
|
|
2616
3219
|
b.prepend("");
|
|
2617
|
-
for (let imp of
|
|
2618
|
-
imp = importsMap[imp] || imp;
|
|
2619
|
-
b.prepend(imp.replace("${folder}", options?.renderFolder || "@torpor/view/ssr"));
|
|
2620
|
-
}
|
|
3220
|
+
for (let imp of sortedImports) b.prepend(imp);
|
|
2621
3221
|
}
|
|
2622
3222
|
return b.toString();
|
|
2623
3223
|
}
|
|
2624
3224
|
function buildServerTemplate(template, imports, b, options) {
|
|
2625
3225
|
let script = template.script.map((s) => s.script).join("\n");
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
if (/\$
|
|
3226
|
+
for (const component of template.components) {
|
|
3227
|
+
if (component.markup) script += "\n" + collectMarkupExpressions(component.markup);
|
|
3228
|
+
if (component.error) script += "\n" + collectMarkupExpressions(component.error);
|
|
3229
|
+
if (component.head) script += "\n" + collectMarkupExpressions(component.head);
|
|
3230
|
+
}
|
|
3231
|
+
let scriptCode = codeRanges(script).map(([start, end]) => script.substring(start, end)).join("");
|
|
3232
|
+
if (/\$watch\b/.test(scriptCode)) imports.add("$watch");
|
|
3233
|
+
if (/\$bind\b/.test(scriptCode)) imports.add("$bind");
|
|
3234
|
+
if (/\$cache\b/.test(scriptCode)) imports.add("$cache");
|
|
3235
|
+
if (/\$async\b/.test(scriptCode)) imports.add("$async");
|
|
3236
|
+
if (/\$pending\b/.test(scriptCode)) imports.add("$pending");
|
|
3237
|
+
if (/\$refresh\b/.test(scriptCode)) imports.add("$refresh");
|
|
3238
|
+
if (/\$run\b/.test(scriptCode)) imports.add("$run");
|
|
3239
|
+
if (/\$onmount\b/.test(scriptCode)) imports.add("$onmount");
|
|
3240
|
+
if (/\$stream\b/.test(scriptCode)) imports.add("$stream");
|
|
3241
|
+
if (/\bfromElement\b/.test(scriptCode)) imports.add("fromElement");
|
|
3242
|
+
if (/\bfromServer\b/.test(scriptCode)) imports.add("fromServer");
|
|
3243
|
+
if (/\bfromWebSocket\b/.test(scriptCode)) imports.add("fromWebSocket");
|
|
3244
|
+
if (/\$unwrap\b/.test(scriptCode)) imports.add("$unwrap");
|
|
3245
|
+
if (/\$peek\b/.test(scriptCode)) imports.add("$peek");
|
|
3246
|
+
if (/\$batch\b/.test(scriptCode)) imports.add("$batch");
|
|
2633
3247
|
let currentIndex = 0;
|
|
2634
3248
|
let current = template.components[0];
|
|
2635
3249
|
for (let chunk of template.script) if (chunk.script === "/* @params */") {
|
|
2636
3250
|
let params = [
|
|
2637
|
-
current.params ?? `${current.props?.length ? "$props: Record<PropertyKey, any>" : "
|
|
2638
|
-
`${current.contextProps?.length ? "$context" : "
|
|
2639
|
-
`${current.slotProps?.length ? "$slots" : "
|
|
3251
|
+
current.params ?? `${current.props?.length ? "$props: Record<PropertyKey, any>" : "_$props?: Record<PropertyKey, any>"}`,
|
|
3252
|
+
`${current.contextProps?.length || current.markup && markupRendersComponent(current.markup) || current.error && markupRendersComponent(current.error) ? "$context" : "_$context"}?: Record<PropertyKey, any>`,
|
|
3253
|
+
`${current.slotProps?.length ? "$slots" : "_$slots"}?: Record<string, ServerSlotRender>`
|
|
2640
3254
|
];
|
|
2641
|
-
b.append(params.join(",\n"));
|
|
2642
|
-
} else if (chunk.script === ") /* @return_type */ {") b.append("): { body: string
|
|
3255
|
+
b.append(params.join(",\n") + ",");
|
|
3256
|
+
} else if (chunk.script === ") /* @return_type */ {") b.append("): { body: string; head: string } {");
|
|
2643
3257
|
else if (chunk.script === "/* @start */") {
|
|
2644
3258
|
if (current.contextProps?.length) b.append(`$context = Object.assign({}, $context);`);
|
|
2645
3259
|
b.append(`let t_body = "";`);
|
|
@@ -2656,12 +3270,38 @@ function buildServerTemplate(template, imports, b, options) {
|
|
|
2656
3270
|
};
|
|
2657
3271
|
b.append("");
|
|
2658
3272
|
b.append("/* User interface */");
|
|
3273
|
+
if (current.error) {
|
|
3274
|
+
b.append(`const t_try_body = t_body;`);
|
|
3275
|
+
b.append(`const t_try_head = t_head;`);
|
|
3276
|
+
b.append("try {");
|
|
3277
|
+
}
|
|
2659
3278
|
buildServerNode(current.markup, status, b);
|
|
2660
3279
|
if (status.output) {
|
|
2661
3280
|
b.append(`t_body += \`${status.output}\`;`);
|
|
2662
3281
|
status.output = "";
|
|
2663
3282
|
}
|
|
2664
3283
|
}
|
|
3284
|
+
} else if (chunk.script === "/* @error */") {
|
|
3285
|
+
if (current.markup && current.error) {
|
|
3286
|
+
const status = {
|
|
3287
|
+
imports,
|
|
3288
|
+
output: "",
|
|
3289
|
+
styleHash: current.style?.hash || "",
|
|
3290
|
+
varNames: {},
|
|
3291
|
+
preserveWhitespace: false,
|
|
3292
|
+
options
|
|
3293
|
+
};
|
|
3294
|
+
b.append(`} catch (${current.errorVar}) {`);
|
|
3295
|
+
b.append("t_body = t_try_body;");
|
|
3296
|
+
b.append("t_head = t_try_head;");
|
|
3297
|
+
b.append("/* User interface error */");
|
|
3298
|
+
buildServerNode(current.error, status, b);
|
|
3299
|
+
if (status.output) {
|
|
3300
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
3301
|
+
status.output = "";
|
|
3302
|
+
}
|
|
3303
|
+
b.append("}");
|
|
3304
|
+
}
|
|
2665
3305
|
} else if (chunk.script === "/* @head */") {} else if (chunk.script === "/* @style */") {
|
|
2666
3306
|
if (current.style) {
|
|
2667
3307
|
b.append("");
|
|
@@ -2669,13 +3309,115 @@ function buildServerTemplate(template, imports, b, options) {
|
|
|
2669
3309
|
const styles = buildStyles(current.style, current.style.hash).replaceAll("\"", "\\\"").replaceAll(/\s+/g, " ");
|
|
2670
3310
|
b.append(`t_head += "<style id='${current.style.hash}'>${styles}</style>";\n`);
|
|
2671
3311
|
}
|
|
2672
|
-
} else if (chunk.script === "/* @end */") {
|
|
2673
|
-
b.append(`return { body: t_body, head: t_head };`);
|
|
2674
|
-
currentIndex += 1;
|
|
2675
|
-
current = template.components[currentIndex];
|
|
2676
|
-
} else b.append(chunk.script);
|
|
3312
|
+
} else if (chunk.script === "/* @end */") {
|
|
3313
|
+
b.append(`return { body: t_body, head: t_head };`);
|
|
3314
|
+
currentIndex += 1;
|
|
3315
|
+
current = template.components[currentIndex];
|
|
3316
|
+
} else b.append(chunk.script);
|
|
3317
|
+
}
|
|
3318
|
+
//#endregion
|
|
3319
|
+
//#region src/compile/utils/trimWhitespace.ts
|
|
3320
|
+
/**
|
|
3321
|
+
* Tag names whose contents should keep all whitespace untouched, matching
|
|
3322
|
+
* native HTML rendering of `pre`/`textarea` and the existing build-time
|
|
3323
|
+
* `preserveWhitespace` behavior for `code`.
|
|
3324
|
+
*/
|
|
3325
|
+
const PRESERVE_TAGS = /* @__PURE__ */ new Set([
|
|
3326
|
+
"pre",
|
|
3327
|
+
"textarea",
|
|
3328
|
+
"code"
|
|
3329
|
+
]);
|
|
3330
|
+
/**
|
|
3331
|
+
* Container tags whose child layout ignores inter-child whitespace entirely
|
|
3332
|
+
* (table layout collapses it). Inside these, whitespace-only text nodes
|
|
3333
|
+
* between children are removed rather than collapsed to a single space.
|
|
3334
|
+
*/
|
|
3335
|
+
const WHITESPACE_INSIGNIFICANT_TAGS = /* @__PURE__ */ new Set([
|
|
3336
|
+
"tr",
|
|
3337
|
+
"tbody",
|
|
3338
|
+
"thead",
|
|
3339
|
+
"tfoot",
|
|
3340
|
+
"colgroup",
|
|
3341
|
+
"table",
|
|
3342
|
+
"ul",
|
|
3343
|
+
"ol",
|
|
3344
|
+
"select"
|
|
3345
|
+
]);
|
|
3346
|
+
/**
|
|
3347
|
+
* Trims and collapses whitespace text nodes in a parsed markup tree,
|
|
3348
|
+
* Svelte 5-style:
|
|
3349
|
+
*
|
|
3350
|
+
* - pure-whitespace text nodes at the start/end of a container are removed
|
|
3351
|
+
* - pure-whitespace text nodes between siblings are collapsed to a single space
|
|
3352
|
+
* (or removed entirely inside table/list containers where whitespace is
|
|
3353
|
+
* insignificant — see WHITESPACE_INSIGNIFICANT_TAGS)
|
|
3354
|
+
* - whitespace inside `pre`, `textarea`, and `code` is preserved
|
|
3355
|
+
*
|
|
3356
|
+
* Whitespace adjacent to non-rendering control nodes (e.g. `@key`, `@const`)
|
|
3357
|
+
* is treated as leading/trailing whitespace of the container and removed,
|
|
3358
|
+
* because those nodes produce no DOM output — without this rule, every row
|
|
3359
|
+
* of `@for { @key = ...; <tr>...</tr> }` would emit a phantom leading
|
|
3360
|
+
* whitespace text node.
|
|
3361
|
+
*
|
|
3362
|
+
* Mixed text nodes (containing non-whitespace content) are left untouched;
|
|
3363
|
+
* their internal whitespace continues to be collapsed at build time. The
|
|
3364
|
+
* walk mutates the tree in place and is idempotent.
|
|
3365
|
+
*
|
|
3366
|
+
* @param node The root node of the markup to trim (a component's markup or head)
|
|
3367
|
+
*/
|
|
3368
|
+
function trimWhitespace(node) {
|
|
3369
|
+
trimChildren(node.children, false, void 0);
|
|
3370
|
+
}
|
|
3371
|
+
function trimChildren(children, preserveAll, parentTag) {
|
|
3372
|
+
for (const child of children) {
|
|
3373
|
+
const grandChildren = getChildren(child);
|
|
3374
|
+
if (grandChildren) {
|
|
3375
|
+
const childTag = getTagName(child);
|
|
3376
|
+
trimChildren(grandChildren, preserveAll || PRESERVE_TAGS.has(childTag ?? ""), childTag);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
if (preserveAll) return;
|
|
3380
|
+
if (parentTag !== void 0 && WHITESPACE_INSIGNIFICANT_TAGS.has(parentTag)) {
|
|
3381
|
+
for (let i = children.length - 1; i >= 0; i--) if (isWhitespaceText(children[i])) children.splice(i, 1);
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3384
|
+
const length = children.length;
|
|
3385
|
+
const hasRenderingBefore = Array.from({ length });
|
|
3386
|
+
const hasRenderingAfter = Array.from({ length });
|
|
3387
|
+
let seenBefore = false;
|
|
3388
|
+
let seenAfter = false;
|
|
3389
|
+
for (let i = 0; i < length; i++) {
|
|
3390
|
+
hasRenderingBefore[i] = seenBefore;
|
|
3391
|
+
if (isRenderingSibling(children[i])) seenBefore = true;
|
|
3392
|
+
const j = length - 1 - i;
|
|
3393
|
+
hasRenderingAfter[j] = seenAfter;
|
|
3394
|
+
if (isRenderingSibling(children[j])) seenAfter = true;
|
|
3395
|
+
}
|
|
3396
|
+
for (let i = length - 1; i >= 0; i--) if (isWhitespaceText(children[i]) && (!hasRenderingBefore[i] || !hasRenderingAfter[i])) children.splice(i, 1);
|
|
3397
|
+
for (const child of children) if (isTextNode(child) && child.content !== "" && isSpace(child.content)) child.content = " ";
|
|
3398
|
+
}
|
|
3399
|
+
/**
|
|
3400
|
+
* Returns true if `node` is a "rendering sibling" — i.e. a node that produces
|
|
3401
|
+
* visible DOM output and therefore acts as a boundary between leading,
|
|
3402
|
+
* inter-sibling, and trailing whitespace. Whitespace text nodes themselves
|
|
3403
|
+
* (the subject of the trim) and non-rendering control nodes (e.g. @key,
|
|
3404
|
+
* @const) are not rendering siblings.
|
|
3405
|
+
*/
|
|
3406
|
+
function isRenderingSibling(node) {
|
|
3407
|
+
if (isWhitespaceText(node)) return false;
|
|
3408
|
+
if (isControlNode(node) && NON_RENDERING_OPERATIONS.has(node.operation)) return false;
|
|
3409
|
+
return true;
|
|
3410
|
+
}
|
|
3411
|
+
function getChildren(node) {
|
|
3412
|
+
if (node.type === "text" || node.type === "comment") return void 0;
|
|
3413
|
+
return node.children;
|
|
3414
|
+
}
|
|
3415
|
+
function getTagName(node) {
|
|
3416
|
+
if (node.type === "element" || node.type === "component" || node.type === "special") return node.tagName;
|
|
3417
|
+
}
|
|
3418
|
+
function isWhitespaceText(node) {
|
|
3419
|
+
return isTextNode(node) && node.content !== "" && isSpace(node.content);
|
|
2677
3420
|
}
|
|
2678
|
-
|
|
2679
3421
|
//#endregion
|
|
2680
3422
|
//#region src/compile/build.ts
|
|
2681
3423
|
/**
|
|
@@ -2686,6 +3428,11 @@ function buildServerTemplate(template, imports, b, options) {
|
|
|
2686
3428
|
* @returns The component's code and styles
|
|
2687
3429
|
*/
|
|
2688
3430
|
function build(template, options) {
|
|
3431
|
+
if (!options?.preserveWhitespace) for (const component of template.components) {
|
|
3432
|
+
if (component.markup) trimWhitespace(component.markup);
|
|
3433
|
+
if (component.error) trimWhitespace(component.error);
|
|
3434
|
+
if (component.head) trimWhitespace(component.head);
|
|
3435
|
+
}
|
|
2689
3436
|
let map = [];
|
|
2690
3437
|
return {
|
|
2691
3438
|
code: options?.server ? buildServerCode(template, options) : buildCode(template, map, options),
|
|
@@ -2696,7 +3443,6 @@ function build(template, options) {
|
|
|
2696
3443
|
map
|
|
2697
3444
|
};
|
|
2698
3445
|
}
|
|
2699
|
-
|
|
2700
3446
|
//#endregion
|
|
2701
3447
|
//#region src/compile/buildType.ts
|
|
2702
3448
|
/**
|
|
@@ -2718,7 +3464,7 @@ function buildType(template, options) {
|
|
|
2718
3464
|
}
|
|
2719
3465
|
b.append(`
|
|
2720
3466
|
${component.documentation ?? ""}
|
|
2721
|
-
${component.default ? "declare" : "export declare"} function ${component.name}(
|
|
3467
|
+
${component.default ? "declare" : "export declare"} function ${component.name}${component.typeParams ?? ""}(
|
|
2722
3468
|
$parent: ParentNode,
|
|
2723
3469
|
$anchor: Node | null,
|
|
2724
3470
|
${component.params || "$props?: Record<PropertyKey, any>"},
|
|
@@ -2730,13 +3476,29 @@ function buildType(template, options) {
|
|
|
2730
3476
|
}
|
|
2731
3477
|
return b.toString();
|
|
2732
3478
|
}
|
|
2733
|
-
|
|
3479
|
+
//#endregion
|
|
3480
|
+
//#region src/compile/utils/endOfTemplateString.ts
|
|
3481
|
+
/**
|
|
3482
|
+
* Returns the index of the closing backtick of the template string that
|
|
3483
|
+
* starts at `start`.
|
|
3484
|
+
*
|
|
3485
|
+
* NOTE: For scanning JavaScript expression text, use `codeScanner.ts` instead
|
|
3486
|
+
* -- this variant doesn't handle escaped characters or nested templates in
|
|
3487
|
+
* interpolations, and is kept only for non-JS contexts (CSS in `@style`
|
|
3488
|
+
* blocks) where a simpler scan is wanted.
|
|
3489
|
+
*/
|
|
3490
|
+
function endOfTemplateString(content, start) {
|
|
3491
|
+
let level = 0;
|
|
3492
|
+
for (let j = start + 1; j < content.length; j++) if (content[j] === "`" && content[j - 1] !== "\\" && level === 0) return j;
|
|
3493
|
+
else if (content[j] === "{" && (level > 0 || content[j - 1] === "$")) level += 1;
|
|
3494
|
+
else if (content[j] === "}" && level > 0) level -= 1;
|
|
3495
|
+
return content.length;
|
|
3496
|
+
}
|
|
2734
3497
|
//#endregion
|
|
2735
3498
|
//#region src/compile/utils/isSpaceNode.ts
|
|
2736
3499
|
function isSpaceNode(node) {
|
|
2737
3500
|
return isTextNode(node) && isSpace(node.content);
|
|
2738
3501
|
}
|
|
2739
|
-
|
|
2740
3502
|
//#endregion
|
|
2741
3503
|
//#region src/compile/parse/utils/accept.ts
|
|
2742
3504
|
/**
|
|
@@ -2757,25 +3519,24 @@ function accept(value, status, advance = true) {
|
|
|
2757
3519
|
}
|
|
2758
3520
|
return false;
|
|
2759
3521
|
}
|
|
2760
|
-
|
|
2761
3522
|
//#endregion
|
|
2762
3523
|
//#region src/compile/parse/parseInlineScript.ts
|
|
2763
3524
|
function parseInlineScript(status) {
|
|
2764
3525
|
const start = status.i;
|
|
2765
3526
|
let braceCount = 0;
|
|
2766
|
-
while (status.i < status.source.length)
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
3527
|
+
while (status.i < status.source.length) {
|
|
3528
|
+
const skipped = skipStringOrComment(status.source, status.i);
|
|
3529
|
+
if (skipped !== -1) {
|
|
3530
|
+
status.i = skipped;
|
|
3531
|
+
continue;
|
|
3532
|
+
}
|
|
3533
|
+
if (accept("{", status)) braceCount += 1;
|
|
3534
|
+
else if (accept("}", status)) if (braceCount > 0) braceCount -= 1;
|
|
3535
|
+
else return status.source.substring(start, status.i - 1);
|
|
3536
|
+
else status.i += 1;
|
|
3537
|
+
}
|
|
2776
3538
|
return "";
|
|
2777
3539
|
}
|
|
2778
|
-
|
|
2779
3540
|
//#endregion
|
|
2780
3541
|
//#region src/compile/utils/trimStart.ts
|
|
2781
3542
|
/**
|
|
@@ -2791,7 +3552,6 @@ function trimStart(text, char) {
|
|
|
2791
3552
|
while (text[start] === char) start += 1;
|
|
2792
3553
|
return text.substring(start);
|
|
2793
3554
|
}
|
|
2794
|
-
|
|
2795
3555
|
//#endregion
|
|
2796
3556
|
//#region src/compile/parse/utils/consumeSpace.ts
|
|
2797
3557
|
/**
|
|
@@ -2808,7 +3568,6 @@ function consumeSpace(status) {
|
|
|
2808
3568
|
for (status.i; status.i < status.source.length; status.i++) if (!isSpaceChar(status.source, status.i)) return status.source.substring(start, status.i);
|
|
2809
3569
|
return "";
|
|
2810
3570
|
}
|
|
2811
|
-
|
|
2812
3571
|
//#endregion
|
|
2813
3572
|
//#region src/compile/parse/addSpaceElement.ts
|
|
2814
3573
|
function addSpaceElement(parent, status) {
|
|
@@ -2826,7 +3585,6 @@ function addSpaceElement(parent, status) {
|
|
|
2826
3585
|
}
|
|
2827
3586
|
}
|
|
2828
3587
|
}
|
|
2829
|
-
|
|
2830
3588
|
//#endregion
|
|
2831
3589
|
//#region src/compile/utils/isFullyReactive.ts
|
|
2832
3590
|
/**
|
|
@@ -2839,7 +3597,41 @@ function addSpaceElement(parent, status) {
|
|
|
2839
3597
|
function isFullyReactive(text) {
|
|
2840
3598
|
return text.trim().startsWith("{") && text.trim().endsWith("}");
|
|
2841
3599
|
}
|
|
2842
|
-
|
|
3600
|
+
//#endregion
|
|
3601
|
+
//#region src/compile/parse/utils/addError.ts
|
|
3602
|
+
/**
|
|
3603
|
+
* Adds an error to the parse status
|
|
3604
|
+
*
|
|
3605
|
+
* @param status The parse status
|
|
3606
|
+
* @param message The error message to add
|
|
3607
|
+
* @param start (Optional) The start position of the error (defaults to the
|
|
3608
|
+
* current parse position)
|
|
3609
|
+
*/
|
|
3610
|
+
function addError(status, message, start = status.i, end = status.i) {
|
|
3611
|
+
let startLine = 0;
|
|
3612
|
+
let lastLineStart = 0;
|
|
3613
|
+
let i = 0;
|
|
3614
|
+
for (; i < start; i++) if (status.source[i] === "\n") {
|
|
3615
|
+
startLine += 1;
|
|
3616
|
+
lastLineStart = i + 1;
|
|
3617
|
+
}
|
|
3618
|
+
let startChar = start - lastLineStart;
|
|
3619
|
+
let endLine = startLine;
|
|
3620
|
+
for (; i < end; i++) if (status.source[i] === "\n") {
|
|
3621
|
+
endLine += 1;
|
|
3622
|
+
lastLineStart = i + 1;
|
|
3623
|
+
}
|
|
3624
|
+
let endChar = end - lastLineStart;
|
|
3625
|
+
status.errors.push({
|
|
3626
|
+
message,
|
|
3627
|
+
startIndex: start,
|
|
3628
|
+
startLine,
|
|
3629
|
+
startChar,
|
|
3630
|
+
endIndex: end,
|
|
3631
|
+
endLine,
|
|
3632
|
+
endChar
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
2843
3635
|
//#endregion
|
|
2844
3636
|
//#region src/compile/parse/utils/isAlphaNumericChar.ts
|
|
2845
3637
|
/**
|
|
@@ -2854,7 +3646,6 @@ function isAlphaNumericChar(text, index) {
|
|
|
2854
3646
|
let code = text.charCodeAt(index);
|
|
2855
3647
|
return code > 47 && code < 58 || code > 64 && code < 91 || code > 96 && code < 123;
|
|
2856
3648
|
}
|
|
2857
|
-
|
|
2858
3649
|
//#endregion
|
|
2859
3650
|
//#region src/compile/parse/utils/consumeAlphaNumeric.ts
|
|
2860
3651
|
/**
|
|
@@ -2871,7 +3662,6 @@ function consumeAlphaNumeric(status) {
|
|
|
2871
3662
|
for (status.i; status.i < status.source.length; status.i++) if (!isAlphaNumericChar(status.source, status.i)) return status.source.substring(start, status.i);
|
|
2872
3663
|
return "";
|
|
2873
3664
|
}
|
|
2874
|
-
|
|
2875
3665
|
//#endregion
|
|
2876
3666
|
//#region src/compile/parse/utils/consumeUntil.ts
|
|
2877
3667
|
/**
|
|
@@ -2889,7 +3679,6 @@ function consumeUntil(chars, status) {
|
|
|
2889
3679
|
for (status.i; status.i < status.source.length; status.i++) if (chars.includes(status.source[status.i])) return status.source.substring(start, status.i);
|
|
2890
3680
|
return "";
|
|
2891
3681
|
}
|
|
2892
|
-
|
|
2893
3682
|
//#endregion
|
|
2894
3683
|
//#region src/compile/parse/parseTag.ts
|
|
2895
3684
|
function parseTag(status) {
|
|
@@ -2912,6 +3701,7 @@ function parseTag(status) {
|
|
|
2912
3701
|
type: [
|
|
2913
3702
|
"slot",
|
|
2914
3703
|
"fill",
|
|
3704
|
+
"filldef",
|
|
2915
3705
|
"@component",
|
|
2916
3706
|
"@element"
|
|
2917
3707
|
].includes(tagName) ? "special" : "element",
|
|
@@ -2965,10 +3755,12 @@ function parseAttribute(status) {
|
|
|
2965
3755
|
value = name.substring(1, name.length - 1);
|
|
2966
3756
|
valueStart++;
|
|
2967
3757
|
valueEnd = valueStart + name.length;
|
|
2968
|
-
|
|
3758
|
+
if (value.startsWith("...")) name = value;
|
|
3759
|
+
else name = value.split(".").at(-1);
|
|
2969
3760
|
if (name.endsWith("!")) name = name.substring(0, name.length - 1);
|
|
2970
3761
|
reactive = fullyReactive = true;
|
|
2971
3762
|
}
|
|
3763
|
+
if (value != null && reactive && value.startsWith("...")) addError(status, `Spread attributes are not supported`, valueStart, valueEnd);
|
|
2972
3764
|
return {
|
|
2973
3765
|
name,
|
|
2974
3766
|
value,
|
|
@@ -2999,7 +3791,6 @@ function parseAttributeValue(status) {
|
|
|
2999
3791
|
return value;
|
|
3000
3792
|
}
|
|
3001
3793
|
}
|
|
3002
|
-
|
|
3003
3794
|
//#endregion
|
|
3004
3795
|
//#region src/compile/parse/parseText.ts
|
|
3005
3796
|
function parseText(status, element) {
|
|
@@ -3013,18 +3804,16 @@ function parseText(status, element) {
|
|
|
3013
3804
|
let level = 0;
|
|
3014
3805
|
for (let k = j; k < status.source.length; k++) {
|
|
3015
3806
|
const char = status.source[k];
|
|
3016
|
-
|
|
3017
|
-
if (char === "/" && nextChar === "/") k = status.source.indexOf("\n", k);
|
|
3018
|
-
else if (char === "/" && nextChar === "*") k = status.source.indexOf("*/", k) + 1;
|
|
3019
|
-
else if (char === "\"" || char === "'") k = endOfString(char, status.source, k);
|
|
3020
|
-
else if (char === "`") k = endOfTemplateString(status.source, k);
|
|
3021
|
-
else if (char === "{") level += 1;
|
|
3807
|
+
if (char === "{") level += 1;
|
|
3022
3808
|
else if (char === "}") {
|
|
3023
3809
|
level -= 1;
|
|
3024
3810
|
if (level === 0) {
|
|
3025
3811
|
j = k;
|
|
3026
3812
|
break;
|
|
3027
3813
|
}
|
|
3814
|
+
} else {
|
|
3815
|
+
const skipped = skipStringOrComment(status.source, k);
|
|
3816
|
+
if (skipped !== -1) k = skipped - 1;
|
|
3028
3817
|
}
|
|
3029
3818
|
}
|
|
3030
3819
|
spans.push({
|
|
@@ -3060,7 +3849,38 @@ function parseText(status, element) {
|
|
|
3060
3849
|
status.i = end;
|
|
3061
3850
|
} else status.i += 1;
|
|
3062
3851
|
}
|
|
3063
|
-
|
|
3852
|
+
//#endregion
|
|
3853
|
+
//#region src/compile/parse/utils/usesSlot.ts
|
|
3854
|
+
const slotRegex = /\$slot\b/;
|
|
3855
|
+
/**
|
|
3856
|
+
* Checks whether the $slot variable is read in the nodes' own scope
|
|
3857
|
+
*
|
|
3858
|
+
* Note that the children of a component node are excluded, because they are
|
|
3859
|
+
* passed to the component as fills, which get their own $slot parameter.
|
|
3860
|
+
* The component's attributes do belong to this scope.
|
|
3861
|
+
*
|
|
3862
|
+
* @param nodes The nodes to check
|
|
3863
|
+
*
|
|
3864
|
+
* @returns True if a $slot variable is read in the nodes' own scope
|
|
3865
|
+
*/
|
|
3866
|
+
function usesSlot(nodes) {
|
|
3867
|
+
for (let node of nodes) if (node.type === "text") {
|
|
3868
|
+
const text = node;
|
|
3869
|
+
if (slotRegex.test(text.content)) return true;
|
|
3870
|
+
} else if (node.type === "control") {
|
|
3871
|
+
const control = node;
|
|
3872
|
+
if (slotRegex.test(control.statement)) return true;
|
|
3873
|
+
if (usesSlot(control.children)) return true;
|
|
3874
|
+
} else if (node.type === "element" || node.type === "special" || node.type === "component") {
|
|
3875
|
+
const element = node;
|
|
3876
|
+
for (let attribute of element.attributes) {
|
|
3877
|
+
if (slotRegex.test(attribute.name)) return true;
|
|
3878
|
+
if (attribute.value && slotRegex.test(attribute.value)) return true;
|
|
3879
|
+
}
|
|
3880
|
+
if (element.type !== "component" && usesSlot(element.children)) return true;
|
|
3881
|
+
}
|
|
3882
|
+
return false;
|
|
3883
|
+
}
|
|
3064
3884
|
//#endregion
|
|
3065
3885
|
//#region src/compile/parse/slottifyChildNodes.ts
|
|
3066
3886
|
/**
|
|
@@ -3068,65 +3888,33 @@ function parseText(status, element) {
|
|
|
3068
3888
|
* <fill> node
|
|
3069
3889
|
*/
|
|
3070
3890
|
function slottifyChildNodes(node) {
|
|
3071
|
-
|
|
3891
|
+
let nonFillNodes = [];
|
|
3892
|
+
for (let child of node.children) if (isFillNode(child)) if (nonFillNodes.find((n) => !isSpaceNode(n)) === void 0) nonFillNodes = [];
|
|
3893
|
+
else break;
|
|
3894
|
+
else nonFillNodes.push(child);
|
|
3895
|
+
if (nonFillNodes.find((n) => !isSpaceNode(n)) === void 0) nonFillNodes = [];
|
|
3072
3896
|
if (nonFillNodes.length) {
|
|
3073
3897
|
let defaultFillNode = node.children.find((n) => isFillNode(n) && !n.attributes.find((a) => a.name === "name"));
|
|
3074
3898
|
if (!defaultFillNode) {
|
|
3075
3899
|
defaultFillNode = {
|
|
3076
3900
|
type: "special",
|
|
3077
|
-
tagName: "
|
|
3901
|
+
tagName: "filldef",
|
|
3078
3902
|
attributes: [],
|
|
3079
3903
|
children: nonFillNodes,
|
|
3080
3904
|
span: {
|
|
3081
3905
|
start: 0,
|
|
3082
3906
|
end: 0
|
|
3083
|
-
}
|
|
3907
|
+
},
|
|
3908
|
+
hasSlotProps: usesSlot(nonFillNodes)
|
|
3084
3909
|
};
|
|
3085
3910
|
node.children.unshift(defaultFillNode);
|
|
3086
3911
|
}
|
|
3912
|
+
node.children = node.children.filter((c) => isFillNode(c) || !nonFillNodes.includes(c));
|
|
3087
3913
|
}
|
|
3088
|
-
node.children = node.children.filter((c) => isFillNode(c));
|
|
3089
3914
|
}
|
|
3090
3915
|
function isFillNode(n) {
|
|
3091
|
-
return isSpecialNode(n) && n.tagName === "fill";
|
|
3092
|
-
}
|
|
3093
|
-
|
|
3094
|
-
//#endregion
|
|
3095
|
-
//#region src/compile/parse/utils/addError.ts
|
|
3096
|
-
/**
|
|
3097
|
-
* Adds an error to the parse status
|
|
3098
|
-
*
|
|
3099
|
-
* @param status The parse status
|
|
3100
|
-
* @param message The error message to add
|
|
3101
|
-
* @param start (Optional) The start position of the error (defaults to the
|
|
3102
|
-
* current parse position)
|
|
3103
|
-
*/
|
|
3104
|
-
function addError(status, message, start = status.i, end = status.i) {
|
|
3105
|
-
let startLine = 0;
|
|
3106
|
-
let lastLineStart = 0;
|
|
3107
|
-
let i = 0;
|
|
3108
|
-
for (; i < start; i++) if (status.source[i] === "\n") {
|
|
3109
|
-
startLine += 1;
|
|
3110
|
-
lastLineStart = i + 1;
|
|
3111
|
-
}
|
|
3112
|
-
let startChar = start - lastLineStart;
|
|
3113
|
-
let endLine = startLine;
|
|
3114
|
-
for (; i < end; i++) if (status.source[i] === "\n") {
|
|
3115
|
-
endLine += 1;
|
|
3116
|
-
lastLineStart = i + 1;
|
|
3117
|
-
}
|
|
3118
|
-
let endChar = end - lastLineStart;
|
|
3119
|
-
status.errors.push({
|
|
3120
|
-
message,
|
|
3121
|
-
startIndex: start,
|
|
3122
|
-
startLine,
|
|
3123
|
-
startChar,
|
|
3124
|
-
endIndex: end,
|
|
3125
|
-
endLine,
|
|
3126
|
-
endChar
|
|
3127
|
-
});
|
|
3916
|
+
return isSpecialNode(n) && (n.tagName === "fill" || n.tagName === "filldef");
|
|
3128
3917
|
}
|
|
3129
|
-
|
|
3130
3918
|
//#endregion
|
|
3131
3919
|
//#region src/compile/parse/parseElement.ts
|
|
3132
3920
|
function parseElement(status) {
|
|
@@ -3138,16 +3926,40 @@ function parseElement(status) {
|
|
|
3138
3926
|
addError(status, `Non-matching close tag: ${element.tagName.substring(1)}`, start, start + element.tagName.length);
|
|
3139
3927
|
return element;
|
|
3140
3928
|
}
|
|
3141
|
-
if (
|
|
3142
|
-
if (!element.selfClosed && !
|
|
3929
|
+
if (voidTags.includes(element.tagName)) checkVoidElement(status, element);
|
|
3930
|
+
if (!element.selfClosed && !voidTags.includes(element.tagName)) {
|
|
3143
3931
|
status.stack.push(element);
|
|
3144
3932
|
while (status.i < status.source.length) {
|
|
3145
3933
|
addSpaceElement(element, status);
|
|
3146
3934
|
if (accept("}", status, false)) break;
|
|
3147
|
-
else if (accept("<!--", status))
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3935
|
+
else if (accept("<!--", status)) {
|
|
3936
|
+
const start = status.i;
|
|
3937
|
+
status.i = status.source.indexOf("-->", status.i) + 3;
|
|
3938
|
+
const text = {
|
|
3939
|
+
type: "comment",
|
|
3940
|
+
commentType: "html",
|
|
3941
|
+
content: status.source.substring(start, status.i - 3)
|
|
3942
|
+
};
|
|
3943
|
+
element.children.push(text);
|
|
3944
|
+
} else if (accept("@//", status)) {
|
|
3945
|
+
const start = status.i;
|
|
3946
|
+
status.i = status.source.indexOf("\n", status.i) + 1;
|
|
3947
|
+
const text = {
|
|
3948
|
+
type: "comment",
|
|
3949
|
+
commentType: "line",
|
|
3950
|
+
content: status.source.substring(start, status.i - 1)
|
|
3951
|
+
};
|
|
3952
|
+
element.children.push(text);
|
|
3953
|
+
} else if (accept("@/*", status)) {
|
|
3954
|
+
const start = status.i;
|
|
3955
|
+
status.i = status.source.indexOf("*/", status.i) + 2;
|
|
3956
|
+
const text = {
|
|
3957
|
+
type: "comment",
|
|
3958
|
+
commentType: "block",
|
|
3959
|
+
content: status.source.substring(start, status.i - 2)
|
|
3960
|
+
};
|
|
3961
|
+
element.children.push(text);
|
|
3962
|
+
} else if (accept("</", status, false)) {
|
|
3151
3963
|
if (checkCloseTag(status, element)) break;
|
|
3152
3964
|
} else if (accept("<", status, false)) {
|
|
3153
3965
|
const child = parseElement(status);
|
|
@@ -3166,10 +3978,8 @@ function parseElement(status) {
|
|
|
3166
3978
|
slottifyChildNodes(element);
|
|
3167
3979
|
current.slotProps ??= [];
|
|
3168
3980
|
current.slotProps.push(element.attributes.find((a) => a.name === "name")?.name ?? "default");
|
|
3169
|
-
} else if (element.tagName === "fill")
|
|
3170
|
-
|
|
3171
|
-
element.hasSlotProps = /\$sprops\b/.test(fillSource);
|
|
3172
|
-
} else if (element.tagName === "@component") {
|
|
3981
|
+
} else if (element.tagName === "fill" || element.tagName === "filldef") element.hasSlotProps = usesSlot(element.children);
|
|
3982
|
+
else if (element.tagName === "@component") {
|
|
3173
3983
|
const selfAttribute = element.attributes.find((a) => a.name === "self");
|
|
3174
3984
|
if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) {
|
|
3175
3985
|
element.type = "component";
|
|
@@ -3197,7 +4007,7 @@ function parseElement(status) {
|
|
|
3197
4007
|
} else addError(status, "@component element must have a self attribute", start, start + element.tagName.length + 1);
|
|
3198
4008
|
}
|
|
3199
4009
|
}
|
|
3200
|
-
if (!element.closed && !
|
|
4010
|
+
if (!element.closed && !voidTags.includes(element.tagName)) addError(status, `Unclosed non-void element: ${element.tagName}`, start, start + element.tagName.length + 1);
|
|
3201
4011
|
return element;
|
|
3202
4012
|
}
|
|
3203
4013
|
function checkVoidElement(status, element) {
|
|
@@ -3232,7 +4042,6 @@ function checkCloseTag(status, element) {
|
|
|
3232
4042
|
if (!found) addError(status, `Non-matching close tag: ${closingTagName} (expected ${element.tagName})`, start, start + element.tagName.length + 1);
|
|
3233
4043
|
return found;
|
|
3234
4044
|
}
|
|
3235
|
-
|
|
3236
4045
|
//#endregion
|
|
3237
4046
|
//#region src/compile/parse/parseControl.ts
|
|
3238
4047
|
const controlOperations = [
|
|
@@ -3244,7 +4053,8 @@ const controlOperations = [
|
|
|
3244
4053
|
"@case",
|
|
3245
4054
|
"@default",
|
|
3246
4055
|
"@await",
|
|
3247
|
-
"@
|
|
4056
|
+
"@with",
|
|
4057
|
+
"@try",
|
|
3248
4058
|
"@catch",
|
|
3249
4059
|
"@replace",
|
|
3250
4060
|
"@html",
|
|
@@ -3267,7 +4077,7 @@ function parseControl(status, parentNode) {
|
|
|
3267
4077
|
const node = parseControlOpen(status);
|
|
3268
4078
|
if (!node) return;
|
|
3269
4079
|
if (standaloneOperations.includes(node.operation)) {
|
|
3270
|
-
wrangleControlNode(node, parentNode);
|
|
4080
|
+
wrangleControlNode(node, parentNode, status);
|
|
3271
4081
|
return;
|
|
3272
4082
|
}
|
|
3273
4083
|
while (status.i < status.source.length) {
|
|
@@ -3286,7 +4096,7 @@ function parseControl(status, parentNode) {
|
|
|
3286
4096
|
break;
|
|
3287
4097
|
}
|
|
3288
4098
|
}
|
|
3289
|
-
wrangleControlNode(node, parentNode);
|
|
4099
|
+
wrangleControlNode(node, parentNode, status);
|
|
3290
4100
|
parseControlBranches(status, parentNode);
|
|
3291
4101
|
}
|
|
3292
4102
|
function parseControlOpen(status) {
|
|
@@ -3308,6 +4118,7 @@ function parseControlOpen(status) {
|
|
|
3308
4118
|
accept("}", status);
|
|
3309
4119
|
}
|
|
3310
4120
|
if (operation === "@replace" || operation === "@html") statement = trimMatched(statement.substring(statement.indexOf("(")).trim(), "(", ")");
|
|
4121
|
+
if (operation === "@await" && /^await\s*\(/.test(statement)) addError(status, "old `@await (expr) { … } then (…) { … }` syntax is removed — use `@await { … } with { … }` with `$async` getters", start, status.i);
|
|
3311
4122
|
} else {
|
|
3312
4123
|
addError(status, `Unknown operation: ${operation}`, status.i, status.i + operation.length);
|
|
3313
4124
|
return null;
|
|
@@ -3325,25 +4136,27 @@ function parseControlOpen(status) {
|
|
|
3325
4136
|
}
|
|
3326
4137
|
function parseControlStatement(start, operation, status) {
|
|
3327
4138
|
let parenCount = 0;
|
|
3328
|
-
while (status.i < status.source.length)
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
4139
|
+
while (status.i < status.source.length) {
|
|
4140
|
+
const skipped = skipStringOrComment(status.source, status.i);
|
|
4141
|
+
if (skipped !== -1) {
|
|
4142
|
+
status.i = skipped;
|
|
4143
|
+
continue;
|
|
4144
|
+
}
|
|
4145
|
+
if (accept("(", status)) parenCount += 1;
|
|
4146
|
+
else if (accept(")", status)) parenCount -= 1;
|
|
4147
|
+
else if (accept("{", status) || accept("\n", status) && standaloneOperations.includes(operation)) {
|
|
4148
|
+
if (parenCount === 0) return trimStart(status.source.substring(start, status.i - 1).trim(), "@");
|
|
4149
|
+
} else status.i += 1;
|
|
4150
|
+
}
|
|
3339
4151
|
return "";
|
|
3340
4152
|
}
|
|
3341
4153
|
/**
|
|
3342
4154
|
* Groups the control node into the correct parent e.g. if/else into an if group
|
|
3343
4155
|
* @param node The control node
|
|
3344
4156
|
* @param parentNode The parent node
|
|
4157
|
+
* @param status The parse status, for error reporting
|
|
3345
4158
|
*/
|
|
3346
|
-
function wrangleControlNode(node, parentNode) {
|
|
4159
|
+
function wrangleControlNode(node, parentNode, status) {
|
|
3347
4160
|
if (node.operation === "@if") {
|
|
3348
4161
|
const ifGroup = {
|
|
3349
4162
|
type: "control",
|
|
@@ -3392,14 +4205,41 @@ function wrangleControlNode(node, parentNode) {
|
|
|
3392
4205
|
}
|
|
3393
4206
|
};
|
|
3394
4207
|
parentNode.children.push(awaitGroup);
|
|
3395
|
-
} else if (node.operation === "@
|
|
4208
|
+
} else if (node.operation === "@with") for (let i = parentNode.children.length - 1; i >= 0; i--) {
|
|
3396
4209
|
const lastChild = parentNode.children[i];
|
|
3397
4210
|
if (isControlNode(lastChild) && lastChild.operation === "@await group") {
|
|
3398
4211
|
lastChild.children.push(node);
|
|
3399
4212
|
break;
|
|
3400
4213
|
}
|
|
3401
4214
|
}
|
|
3402
|
-
else if (node.operation === "@
|
|
4215
|
+
else if (node.operation === "@catch") {
|
|
4216
|
+
let attached = false;
|
|
4217
|
+
let duplicate = false;
|
|
4218
|
+
for (let i = parentNode.children.length - 1; i >= 0; i--) {
|
|
4219
|
+
const lastChild = parentNode.children[i];
|
|
4220
|
+
if (isControlNode(lastChild) && lastChild.operation === "@try group") {
|
|
4221
|
+
if (lastChild.children.some((child) => isControlNode(child) && child.operation === "@catch")) duplicate = true;
|
|
4222
|
+
else {
|
|
4223
|
+
lastChild.children.push(node);
|
|
4224
|
+
attached = true;
|
|
4225
|
+
}
|
|
4226
|
+
break;
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
if (!attached) addError(status, duplicate ? "`@try` cannot have more than one `@catch` block" : "`@catch` must follow a `@try` block", node.span.start, node.span.end);
|
|
4230
|
+
} else if (node.operation === "@try") {
|
|
4231
|
+
const tryGroup = {
|
|
4232
|
+
type: "control",
|
|
4233
|
+
operation: "@try group",
|
|
4234
|
+
statement: "",
|
|
4235
|
+
children: [node],
|
|
4236
|
+
span: {
|
|
4237
|
+
start: 0,
|
|
4238
|
+
end: 0
|
|
4239
|
+
}
|
|
4240
|
+
};
|
|
4241
|
+
parentNode.children.push(tryGroup);
|
|
4242
|
+
} else if (node.operation === "@replace") {
|
|
3403
4243
|
const replaceGroup = {
|
|
3404
4244
|
type: "control",
|
|
3405
4245
|
operation: "@replace group",
|
|
@@ -3436,61 +4276,93 @@ function parseControlBranches(status, parentNode) {
|
|
|
3436
4276
|
let start = status.i;
|
|
3437
4277
|
status.i += 1;
|
|
3438
4278
|
consumeSpace(status);
|
|
3439
|
-
if (accept("else", status, false) || accept("case", status, false) || accept("default", status, false) || accept("
|
|
4279
|
+
if (accept("else", status, false) || accept("case", status, false) || accept("default", status, false) || accept("catch", status, false) || accept("with", status, false)) parseControl(status, parentNode);
|
|
3440
4280
|
else {
|
|
3441
4281
|
gotAnotherBranch = false;
|
|
3442
4282
|
status.i = start;
|
|
3443
4283
|
}
|
|
3444
4284
|
}
|
|
3445
4285
|
}
|
|
3446
|
-
|
|
3447
4286
|
//#endregion
|
|
3448
4287
|
//#region src/compile/parse/parseMarkup.ts
|
|
3449
4288
|
function parseMarkup(status, source) {
|
|
3450
4289
|
const current = status.components.at(-1);
|
|
3451
4290
|
if (!current) return;
|
|
4291
|
+
parseMarkupInto(status, source, (root) => current.markup ??= root);
|
|
4292
|
+
}
|
|
4293
|
+
/**
|
|
4294
|
+
* Parses markup into a root node, letting callers choose where the resulting
|
|
4295
|
+
* RootNode is stored (e.g. a component's `markup` or its `error` block).
|
|
4296
|
+
*
|
|
4297
|
+
* @param status The parse status
|
|
4298
|
+
* @param source The full source code
|
|
4299
|
+
* @param setRoot Called with a fresh root node the first time content is
|
|
4300
|
+
* parsed; subsequent content is appended to the same node.
|
|
4301
|
+
*/
|
|
4302
|
+
function parseMarkupInto(status, source, setRoot) {
|
|
4303
|
+
let root = null;
|
|
4304
|
+
const getRoot = () => root ??= {
|
|
4305
|
+
type: "root",
|
|
4306
|
+
children: []
|
|
4307
|
+
};
|
|
3452
4308
|
while (status.i < source.length) if (accept("}", status, false)) return;
|
|
3453
|
-
else if (accept("<!--", status))
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
4309
|
+
else if (accept("<!--", status)) {
|
|
4310
|
+
const start = status.i;
|
|
4311
|
+
status.i = status.source.indexOf("-->", status.i) + 3;
|
|
4312
|
+
const text = {
|
|
4313
|
+
type: "comment",
|
|
4314
|
+
commentType: "html",
|
|
4315
|
+
content: status.source.substring(start, status.i - 3)
|
|
4316
|
+
};
|
|
4317
|
+
setRoot(getRoot());
|
|
4318
|
+
getRoot().children.push(text);
|
|
4319
|
+
} else if (accept("@//", status)) {
|
|
4320
|
+
const start = status.i;
|
|
4321
|
+
status.i = status.source.indexOf("\n", status.i) + 1;
|
|
4322
|
+
const text = {
|
|
4323
|
+
type: "comment",
|
|
4324
|
+
commentType: "line",
|
|
4325
|
+
content: status.source.substring(start, status.i - 1)
|
|
4326
|
+
};
|
|
4327
|
+
setRoot(getRoot());
|
|
4328
|
+
getRoot().children.push(text);
|
|
4329
|
+
} else if (accept("@/*", status)) {
|
|
4330
|
+
const start = status.i;
|
|
4331
|
+
status.i = status.source.indexOf("*/", status.i) + 2;
|
|
4332
|
+
const text = {
|
|
4333
|
+
type: "comment",
|
|
4334
|
+
commentType: "block",
|
|
4335
|
+
content: status.source.substring(start, status.i - 2)
|
|
3460
4336
|
};
|
|
4337
|
+
setRoot(getRoot());
|
|
4338
|
+
getRoot().children.push(text);
|
|
4339
|
+
} else if (isSpaceChar(status.source, status.i)) {
|
|
4340
|
+
setRoot(getRoot());
|
|
3461
4341
|
const text = {
|
|
3462
4342
|
type: "text",
|
|
3463
4343
|
content: consumeSpace(status),
|
|
3464
4344
|
spans: []
|
|
3465
4345
|
};
|
|
3466
|
-
|
|
4346
|
+
getRoot().children.push(text);
|
|
3467
4347
|
} else if (accept("<", status, false)) {
|
|
3468
|
-
|
|
3469
|
-
type: "root",
|
|
3470
|
-
children: []
|
|
3471
|
-
};
|
|
4348
|
+
setRoot(getRoot());
|
|
3472
4349
|
const element = parseElement(status);
|
|
3473
|
-
|
|
4350
|
+
getRoot().children.push(element);
|
|
3474
4351
|
} else if (accept("@", status, false)) {
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
children: []
|
|
3478
|
-
};
|
|
3479
|
-
parseControl(status, current.markup);
|
|
4352
|
+
setRoot(getRoot());
|
|
4353
|
+
parseControl(status, getRoot());
|
|
3480
4354
|
} else status.i += 1;
|
|
3481
4355
|
}
|
|
3482
|
-
|
|
3483
4356
|
//#endregion
|
|
3484
4357
|
//#region src/compile/utils/hash.ts
|
|
3485
4358
|
const returnRegex = /\r/g;
|
|
3486
4359
|
function hash(input) {
|
|
3487
4360
|
input = input.replace(returnRegex, "");
|
|
3488
|
-
let hash
|
|
4361
|
+
let hash = 5381;
|
|
3489
4362
|
let i = input.length;
|
|
3490
|
-
while (i--) hash
|
|
3491
|
-
return (hash
|
|
4363
|
+
while (i--) hash = (hash << 5) - hash ^ input.charCodeAt(i);
|
|
4364
|
+
return (hash >>> 0).toString(36);
|
|
3492
4365
|
}
|
|
3493
|
-
|
|
3494
4366
|
//#endregion
|
|
3495
4367
|
//#region src/compile/parse/utils/expect.ts
|
|
3496
4368
|
/**
|
|
@@ -3512,7 +4384,6 @@ function expect(value, status, advance = true) {
|
|
|
3512
4384
|
else addError(status, "Expected token");
|
|
3513
4385
|
return false;
|
|
3514
4386
|
}
|
|
3515
|
-
|
|
3516
4387
|
//#endregion
|
|
3517
4388
|
//#region src/compile/parse/parseStyles.ts
|
|
3518
4389
|
function parseStyles(source, status) {
|
|
@@ -3520,7 +4391,7 @@ function parseStyles(source, status) {
|
|
|
3520
4391
|
if (!current) return;
|
|
3521
4392
|
current.style = {
|
|
3522
4393
|
global: false,
|
|
3523
|
-
|
|
4394
|
+
children: [],
|
|
3524
4395
|
hash: hash(source)
|
|
3525
4396
|
};
|
|
3526
4397
|
const styleStatus = {
|
|
@@ -3528,6 +4399,7 @@ function parseStyles(source, status) {
|
|
|
3528
4399
|
i: 0,
|
|
3529
4400
|
marker: 0,
|
|
3530
4401
|
level: 0,
|
|
4402
|
+
braces: [],
|
|
3531
4403
|
imports: [],
|
|
3532
4404
|
script: [],
|
|
3533
4405
|
components: [],
|
|
@@ -3536,69 +4408,86 @@ function parseStyles(source, status) {
|
|
|
3536
4408
|
};
|
|
3537
4409
|
while (styleStatus.i < styleStatus.source.length) if (isSpaceChar(styleStatus.source, styleStatus.i)) consumeSpace(styleStatus);
|
|
3538
4410
|
else {
|
|
3539
|
-
const
|
|
3540
|
-
if (
|
|
4411
|
+
const node = parseStyleNode(styleStatus);
|
|
4412
|
+
if (node) current.style.children.push(node);
|
|
3541
4413
|
else break;
|
|
3542
4414
|
}
|
|
3543
4415
|
status.errors = status.errors.concat(styleStatus.errors);
|
|
3544
4416
|
}
|
|
3545
|
-
function
|
|
4417
|
+
function parseStyleNode(status) {
|
|
4418
|
+
const gapBefore = /\n\s*\n\s*$/.test(status.source.slice(0, status.i));
|
|
4419
|
+
if (accept("//", status, false) || accept("/*", status, false)) return parseComment(status, gapBefore);
|
|
3546
4420
|
const selector = consumeUntil("{", status).trim();
|
|
3547
4421
|
if (expect("{", status)) {
|
|
3548
4422
|
const block = {
|
|
4423
|
+
type: "block",
|
|
3549
4424
|
selector,
|
|
3550
|
-
|
|
3551
|
-
|
|
4425
|
+
children: [],
|
|
4426
|
+
gapBefore
|
|
3552
4427
|
};
|
|
3553
4428
|
consumeSpace(status);
|
|
3554
4429
|
while (status.source[status.i] !== "}") {
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
if (
|
|
3565
|
-
|
|
3566
|
-
|
|
4430
|
+
if (accept("//", status, false) || accept("/*", status, false)) block.children.push(parseComment(status, false));
|
|
4431
|
+
else {
|
|
4432
|
+
let nextColon = status.source.indexOf(":", status.i);
|
|
4433
|
+
if (nextColon === -1) nextColon = status.source.length;
|
|
4434
|
+
let nextOpenBrace = status.source.indexOf("{", status.i);
|
|
4435
|
+
if (nextOpenBrace === -1) nextOpenBrace = status.source.length;
|
|
4436
|
+
if (nextColon < nextOpenBrace) {
|
|
4437
|
+
const attribute = parseStyleAttribute(status);
|
|
4438
|
+
block.children.push(attribute);
|
|
4439
|
+
} else if (nextOpenBrace < nextColon) {
|
|
4440
|
+
const child = parseStyleNode(status);
|
|
4441
|
+
if (child) block.children.push(child);
|
|
4442
|
+
else break;
|
|
4443
|
+
} else return block;
|
|
4444
|
+
}
|
|
3567
4445
|
consumeSpace(status);
|
|
3568
4446
|
}
|
|
3569
4447
|
accept("}", status);
|
|
3570
4448
|
return block;
|
|
3571
4449
|
}
|
|
3572
4450
|
}
|
|
4451
|
+
function parseComment(status, gapBefore) {
|
|
4452
|
+
const start = status.i;
|
|
4453
|
+
if (accept("//", status)) {
|
|
4454
|
+
const end = status.source.indexOf("\n", status.i);
|
|
4455
|
+
status.i = end === -1 ? status.source.length : end;
|
|
4456
|
+
} else {
|
|
4457
|
+
accept("/*", status);
|
|
4458
|
+
const end = status.source.indexOf("*/", status.i);
|
|
4459
|
+
status.i = end === -1 ? status.source.length : end + 2;
|
|
4460
|
+
}
|
|
4461
|
+
return {
|
|
4462
|
+
type: "comment",
|
|
4463
|
+
content: status.source.substring(start, status.i),
|
|
4464
|
+
gapBefore
|
|
4465
|
+
};
|
|
4466
|
+
}
|
|
3573
4467
|
function parseStyleAttribute(status) {
|
|
4468
|
+
const gapBefore = /\n\s*\n\s*$/.test(status.source.slice(0, status.i));
|
|
3574
4469
|
const name = consumeUntil(":", status).trim();
|
|
3575
4470
|
accept(":", status);
|
|
3576
4471
|
const value = consumeUntil(";", status).trim();
|
|
3577
4472
|
accept(";", status);
|
|
3578
4473
|
return {
|
|
4474
|
+
type: "attribute",
|
|
3579
4475
|
name,
|
|
3580
|
-
value
|
|
4476
|
+
value,
|
|
4477
|
+
gapBefore
|
|
3581
4478
|
};
|
|
3582
4479
|
}
|
|
3583
|
-
|
|
3584
4480
|
//#endregion
|
|
3585
4481
|
//#region src/compile/utils/isComponentNode.ts
|
|
3586
4482
|
function isComponentNode(node) {
|
|
3587
4483
|
return node.type === "component";
|
|
3588
4484
|
}
|
|
3589
|
-
|
|
3590
|
-
//#endregion
|
|
3591
|
-
//#region src/compile/utils/isParentNode.ts
|
|
3592
|
-
function isParentNode(node) {
|
|
3593
|
-
return "children" in node;
|
|
3594
|
-
}
|
|
3595
|
-
|
|
3596
4485
|
//#endregion
|
|
3597
4486
|
//#region src/compile/parse/scopeStyles.ts
|
|
3598
4487
|
function scopeStyles(status) {
|
|
3599
4488
|
let selectors = [];
|
|
3600
4489
|
for (let component of status.components) if (component.style && component.markup) {
|
|
3601
|
-
for (let block of component.style.
|
|
4490
|
+
for (let block of component.style.children) if (block.type === "block") collectStyleSelectors(block, selectors);
|
|
3602
4491
|
scopeStylesOnNode(component.markup, selectors);
|
|
3603
4492
|
}
|
|
3604
4493
|
}
|
|
@@ -3616,7 +4505,6 @@ function scopeStylesOnNode(node, selectors) {
|
|
|
3616
4505
|
function collectStyleSelectors(block, selectors) {
|
|
3617
4506
|
for (let s of block.selector.split(/[\s*,>+~[\]]/)) selectors.push(s);
|
|
3618
4507
|
}
|
|
3619
|
-
|
|
3620
4508
|
//#endregion
|
|
3621
4509
|
//#region src/compile/parse/parseCode.ts
|
|
3622
4510
|
function parseCode(source) {
|
|
@@ -3625,16 +4513,24 @@ function parseCode(source) {
|
|
|
3625
4513
|
i: 0,
|
|
3626
4514
|
marker: 0,
|
|
3627
4515
|
level: 0,
|
|
4516
|
+
braces: [],
|
|
3628
4517
|
imports: [],
|
|
3629
4518
|
script: [],
|
|
3630
4519
|
components: [],
|
|
3631
4520
|
stack: [],
|
|
3632
4521
|
errors: []
|
|
3633
4522
|
};
|
|
3634
|
-
for (status.i; status.i < source.length; status.i++) if (consumeScriptComments(status)) {} else if (status.source[status.i] === "{")
|
|
3635
|
-
|
|
4523
|
+
for (status.i; status.i < source.length; status.i++) if (consumeScriptComments(status)) {} else if (status.source[status.i] === "{") {
|
|
4524
|
+
status.level += 1;
|
|
4525
|
+
status.braces.push(false);
|
|
4526
|
+
} else if (status.source[status.i] === "}") {
|
|
3636
4527
|
status.level -= 1;
|
|
3637
|
-
|
|
4528
|
+
const fromComponent = status.braces.pop();
|
|
4529
|
+
if (status.level === 0 && fromComponent) parseComponentEnd(status);
|
|
4530
|
+
else if (status.level < 0) {
|
|
4531
|
+
addError(status, `Unexpected closing brace`, status.i, status.i + 1);
|
|
4532
|
+
status.level = 0;
|
|
4533
|
+
}
|
|
3638
4534
|
} else if (accept("function", status)) {
|
|
3639
4535
|
if (consumeSpace(status)) if (/[A-Z]/.test(source[status.i])) parseComponentStart(status);
|
|
3640
4536
|
else status.i--;
|
|
@@ -3656,8 +4552,13 @@ function parseCode(source) {
|
|
|
3656
4552
|
if (consumeSpace(status) && accept("default", status) && consumeSpace(status) && accept("(", status, false)) parseComponentStart(status);
|
|
3657
4553
|
else status.i = start;
|
|
3658
4554
|
} else if (accept("@render", status, false)) parseComponentRender(status);
|
|
4555
|
+
else if (accept("@error", status, false)) parseComponentError(status);
|
|
3659
4556
|
else if (accept("@head", status, false)) parseComponentHead(status);
|
|
3660
4557
|
else if (accept("@style", status, false)) parseComponentStyle(status);
|
|
4558
|
+
if (status.level > 0) {
|
|
4559
|
+
const count = status.level;
|
|
4560
|
+
addError(status, `Unbalanced braces: missing ${count} closing brace${count === 1 ? "" : "s"}`, source.length, source.length);
|
|
4561
|
+
}
|
|
3661
4562
|
status.script.push({
|
|
3662
4563
|
script: source.substring(status.marker, source.length),
|
|
3663
4564
|
span: {
|
|
@@ -3684,13 +4585,33 @@ function parseComponentStart(status) {
|
|
|
3684
4585
|
let def = /export\s+default\s+(function\s+|const\s+)*$/.test(source);
|
|
3685
4586
|
let isConst = /const\s+$/.test(source);
|
|
3686
4587
|
let isAnon = /export default\s+$/.test(source);
|
|
4588
|
+
let componentStart = status.i;
|
|
4589
|
+
let name = consumeAlphaNumeric(status);
|
|
4590
|
+
if (def && status.components.some((c) => c.default)) addError(status, `Multiple default exports`, componentStart, componentStart + name.length);
|
|
3687
4591
|
const current = {
|
|
3688
|
-
start:
|
|
3689
|
-
name
|
|
4592
|
+
start: componentStart,
|
|
4593
|
+
name,
|
|
3690
4594
|
exported,
|
|
3691
|
-
default: def
|
|
4595
|
+
default: def,
|
|
4596
|
+
scriptStart: status.script.length
|
|
3692
4597
|
};
|
|
3693
4598
|
status.components.push(current);
|
|
4599
|
+
if (status.source[status.i] === "<") {
|
|
4600
|
+
const typeParamsStart = status.i;
|
|
4601
|
+
let level = 0;
|
|
4602
|
+
for (status.i; status.i < status.source.length; status.i++) {
|
|
4603
|
+
const char = status.source[status.i];
|
|
4604
|
+
if (char === "<") level += 1;
|
|
4605
|
+
else if (char === ">") {
|
|
4606
|
+
level -= 1;
|
|
4607
|
+
if (level === 0) {
|
|
4608
|
+
current.typeParams = status.source.substring(typeParamsStart, status.i + 1);
|
|
4609
|
+
status.i += 1;
|
|
4610
|
+
break;
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
3694
4615
|
let presource = source.substring(0, status.i).trimEnd();
|
|
3695
4616
|
let lastNewline = presource.lastIndexOf("\n");
|
|
3696
4617
|
if (lastNewline !== -1) {
|
|
@@ -3737,8 +4658,8 @@ function parseComponentStart(status) {
|
|
|
3737
4658
|
const paramsMatch = current.params?.match(/\$props:\s*([$A-Za-z0-9]+)/);
|
|
3738
4659
|
if (paramsMatch) {
|
|
3739
4660
|
let propsTypeName = paramsMatch[1];
|
|
3740
|
-
let propsTypeStart = status.source.search(
|
|
3741
|
-
if (propsTypeStart === -1) propsTypeStart = status.source.search(
|
|
4661
|
+
let propsTypeStart = status.source.search(new RegExp(`interface\\s+${propsTypeName}`));
|
|
4662
|
+
if (propsTypeStart === -1) propsTypeStart = status.source.search(new RegExp(`type\\s+${propsTypeName}`));
|
|
3742
4663
|
if (propsTypeStart !== -1) {
|
|
3743
4664
|
let bracesStart = status.source.indexOf("{", propsTypeStart);
|
|
3744
4665
|
if (bracesStart !== -1) {
|
|
@@ -3747,6 +4668,7 @@ function parseComponentStart(status) {
|
|
|
3747
4668
|
i: bracesStart + 1,
|
|
3748
4669
|
marker: 0,
|
|
3749
4670
|
level: 0,
|
|
4671
|
+
braces: [],
|
|
3750
4672
|
imports: [],
|
|
3751
4673
|
script: [],
|
|
3752
4674
|
components: [],
|
|
@@ -3767,6 +4689,7 @@ function parseComponentStart(status) {
|
|
|
3767
4689
|
}
|
|
3768
4690
|
accept("{", status);
|
|
3769
4691
|
status.level += 1;
|
|
4692
|
+
status.braces.push(true);
|
|
3770
4693
|
const postFunctionChunk = {
|
|
3771
4694
|
script: status.source.substring(status.marker, status.i),
|
|
3772
4695
|
span: {
|
|
@@ -3821,6 +4744,42 @@ function parseComponentRender(status) {
|
|
|
3821
4744
|
}
|
|
3822
4745
|
status.marker = status.i;
|
|
3823
4746
|
}
|
|
4747
|
+
function parseComponentError(status) {
|
|
4748
|
+
const current = status.components.at(-1);
|
|
4749
|
+
if (!current) return;
|
|
4750
|
+
if (current.error) addError(status, `Multiple @error sections`, status.i, status.i + 6);
|
|
4751
|
+
if (!current.markup) addError(status, `@error must come after @render`, status.i, status.i + 6);
|
|
4752
|
+
status.script.push({
|
|
4753
|
+
script: status.source.substring(status.marker, status.i),
|
|
4754
|
+
span: {
|
|
4755
|
+
start: status.marker,
|
|
4756
|
+
end: status.i
|
|
4757
|
+
}
|
|
4758
|
+
});
|
|
4759
|
+
status.script.push({
|
|
4760
|
+
script: "/* @error */",
|
|
4761
|
+
span: {
|
|
4762
|
+
start: 0,
|
|
4763
|
+
end: 0
|
|
4764
|
+
}
|
|
4765
|
+
});
|
|
4766
|
+
accept("@error", status);
|
|
4767
|
+
consumeSpace(status);
|
|
4768
|
+
let errorVar = "";
|
|
4769
|
+
if (accept("(", status)) {
|
|
4770
|
+
let start = status.i;
|
|
4771
|
+
while (status.i < status.source.length && status.source[status.i] !== ")") status.i += 1;
|
|
4772
|
+
errorVar = status.source.substring(start, status.i).trim();
|
|
4773
|
+
accept(")", status);
|
|
4774
|
+
consumeSpace(status);
|
|
4775
|
+
}
|
|
4776
|
+
current.errorVar = errorVar || "err";
|
|
4777
|
+
if (accept("{", status)) {
|
|
4778
|
+
parseMarkupInto(status, status.source, (root) => current.error ??= root);
|
|
4779
|
+
accept("}", status);
|
|
4780
|
+
}
|
|
4781
|
+
status.marker = status.i;
|
|
4782
|
+
}
|
|
3824
4783
|
function parseComponentHead(status) {
|
|
3825
4784
|
const current = status.components.at(-1);
|
|
3826
4785
|
if (!current) return;
|
|
@@ -3874,16 +4833,22 @@ function parseComponentStyle(status) {
|
|
|
3874
4833
|
for (; status.i < status.source.length; status.i++) {
|
|
3875
4834
|
const char = status.source[status.i];
|
|
3876
4835
|
const nextChar = status.source[status.i + 1];
|
|
3877
|
-
if (char === "/" && nextChar === "/")
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
4836
|
+
if (char === "/" && nextChar === "/") {
|
|
4837
|
+
let start = status.i;
|
|
4838
|
+
status.i = status.source.indexOf("\n", status.i);
|
|
4839
|
+
styleSource += status.source.substring(start, status.i + 1);
|
|
4840
|
+
} else if (char === "/" && nextChar === "*") {
|
|
4841
|
+
let start = status.i;
|
|
4842
|
+
status.i = status.source.indexOf("*/", status.i) + 1;
|
|
4843
|
+
styleSource += status.source.substring(start, status.i + 1);
|
|
4844
|
+
} else if (char === "\"" || char === "'") {
|
|
4845
|
+
let start = status.i;
|
|
4846
|
+
status.i = endOfString(char, status.source, start);
|
|
4847
|
+
styleSource += status.source.substring(start, status.i + 1);
|
|
3883
4848
|
} else if (char === "`") {
|
|
3884
|
-
let start
|
|
3885
|
-
status.i = endOfTemplateString(status.source, start
|
|
3886
|
-
styleSource += status.source.substring(start
|
|
4849
|
+
let start = status.i;
|
|
4850
|
+
status.i = endOfTemplateString(status.source, start);
|
|
4851
|
+
styleSource += status.source.substring(start, status.i + 1);
|
|
3887
4852
|
} else if (char === "{") {
|
|
3888
4853
|
level += 1;
|
|
3889
4854
|
if (start === -1) start = status.i + 1;
|
|
@@ -3918,49 +4883,80 @@ function parseComponentEnd(status) {
|
|
|
3918
4883
|
end: 0
|
|
3919
4884
|
}
|
|
3920
4885
|
});
|
|
3921
|
-
const
|
|
3922
|
-
|
|
3923
|
-
current.
|
|
4886
|
+
const scriptText = status.script.slice(current.scriptStart ?? status.script.length).map((chunk) => chunk.script).join("\n");
|
|
4887
|
+
const markupParts = [];
|
|
4888
|
+
if (current.markup) markupParts.push(collectMarkupExpressions(current.markup));
|
|
4889
|
+
if (current.error) markupParts.push(collectMarkupExpressions(current.error));
|
|
4890
|
+
if (current.head) markupParts.push(collectMarkupExpressions(current.head));
|
|
4891
|
+
const usageSource = scriptText + "\n" + markupParts.join("\n");
|
|
4892
|
+
const usageRanges = codeRanges(usageSource);
|
|
4893
|
+
current.props = getPropsUsage(usageSource, usageRanges);
|
|
4894
|
+
current.contextProps = getContextUsage(usageSource, usageRanges);
|
|
3924
4895
|
if (current.markup && isElementNode(current.markup.children[0]) && current.markup.children[0].tagName === "html" && isSpaceNode(current.markup.children[0].children[0])) current.markup.children.splice(1, 1);
|
|
3925
4896
|
status.marker = status.i;
|
|
3926
4897
|
}
|
|
3927
|
-
|
|
3928
|
-
|
|
4898
|
+
/**
|
|
4899
|
+
* Gets the `$props` names that the compiled output references. The input is
|
|
4900
|
+
* the component's code-only text (script plus markup expressions), with
|
|
4901
|
+
* `ranges` marking the positions that are code — matches that start inside a
|
|
4902
|
+
* string, comment or regex literal are ignored, so mentions inside
|
|
4903
|
+
* sample-code strings or prose don't count.
|
|
4904
|
+
*/
|
|
4905
|
+
function getPropsUsage(source, ranges) {
|
|
3929
4906
|
const props = [];
|
|
3930
|
-
for (let match of
|
|
4907
|
+
for (let match of source.matchAll(/\$props\??\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g)) {
|
|
4908
|
+
if (!isInCode(ranges, match.index)) continue;
|
|
3931
4909
|
const name = trimQuotes(match[1] || match[2]);
|
|
3932
4910
|
if (!props.includes(name)) props.push(name);
|
|
3933
4911
|
}
|
|
4912
|
+
if (!props.length) for (let match of source.matchAll(/(?<![\w$])\$props(?![\w])(?!\s*:)(?!<)/g)) {
|
|
4913
|
+
if (!isInCode(ranges, match.index)) continue;
|
|
4914
|
+
props.push("$props");
|
|
4915
|
+
break;
|
|
4916
|
+
}
|
|
3934
4917
|
return props.length ? props : void 0;
|
|
3935
4918
|
}
|
|
3936
|
-
|
|
3937
|
-
|
|
4919
|
+
/**
|
|
4920
|
+
* Gets the `$context` names that the compiled output references. The input is
|
|
4921
|
+
* the component's code-only text (script plus markup expressions), with
|
|
4922
|
+
* `ranges` marking the positions that are code — matches that start inside a
|
|
4923
|
+
* string, comment or regex literal are ignored, so mentions inside
|
|
4924
|
+
* sample-code strings or prose don't count.
|
|
4925
|
+
*/
|
|
4926
|
+
function getContextUsage(source, ranges) {
|
|
4927
|
+
const contextsMatches = source.matchAll(/\$context\??\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g);
|
|
3938
4928
|
const contexts = [];
|
|
3939
4929
|
for (let match of contextsMatches) {
|
|
4930
|
+
if (!isInCode(ranges, match.index)) continue;
|
|
3940
4931
|
const name = trimQuotes(match[1] || match[2]);
|
|
3941
4932
|
if (!contexts.includes(name)) contexts.push(name);
|
|
3942
4933
|
}
|
|
4934
|
+
if (!contexts.length) for (let match of source.matchAll(/(?<![\w$])\$context(?![\w])(?!\s*:)(?!<)/g)) {
|
|
4935
|
+
if (!isInCode(ranges, match.index)) continue;
|
|
4936
|
+
contexts.push("$context");
|
|
4937
|
+
break;
|
|
4938
|
+
}
|
|
3943
4939
|
return contexts.length ? contexts : void 0;
|
|
3944
4940
|
}
|
|
4941
|
+
/**
|
|
4942
|
+
* Returns true if `index` falls inside one of the (sorted, non-overlapping)
|
|
4943
|
+
* code ranges — i.e. not inside a string, comment or regex literal.
|
|
4944
|
+
*/
|
|
4945
|
+
function isInCode(ranges, index) {
|
|
4946
|
+
for (const [start, end] of ranges) {
|
|
4947
|
+
if (index >= start && index < end) return true;
|
|
4948
|
+
if (index < start) return false;
|
|
4949
|
+
}
|
|
4950
|
+
return false;
|
|
4951
|
+
}
|
|
3945
4952
|
function consumeScriptComments(status) {
|
|
3946
|
-
const
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
status.i = status.source.indexOf("\n", status.i);
|
|
3950
|
-
return true;
|
|
3951
|
-
} else if (char === "/" && nextChar === "*") {
|
|
3952
|
-
status.i = status.source.indexOf("*/", status.i) + 1;
|
|
3953
|
-
return true;
|
|
3954
|
-
} else if (char === "\"" || char === "'") {
|
|
3955
|
-
status.i = endOfString(char, status.source, status.i);
|
|
3956
|
-
return true;
|
|
3957
|
-
} else if (char === "`") {
|
|
3958
|
-
status.i = endOfTemplateString(status.source, status.i);
|
|
4953
|
+
const skipped = skipStringOrComment(status.source, status.i);
|
|
4954
|
+
if (skipped !== -1) {
|
|
4955
|
+
status.i = skipped;
|
|
3959
4956
|
return true;
|
|
3960
4957
|
}
|
|
3961
4958
|
return false;
|
|
3962
4959
|
}
|
|
3963
|
-
|
|
3964
4960
|
//#endregion
|
|
3965
4961
|
//#region src/compile/parse.ts
|
|
3966
4962
|
/**
|
|
@@ -3974,7 +4970,7 @@ function consumeScriptComments(status) {
|
|
|
3974
4970
|
function parse(source) {
|
|
3975
4971
|
return parseCode(source);
|
|
3976
4972
|
}
|
|
3977
|
-
|
|
3978
4973
|
//#endregion
|
|
3979
4974
|
export { build, buildType, parse };
|
|
4975
|
+
|
|
3980
4976
|
//# sourceMappingURL=compile.mjs.map
|