@torpor/view 0.4.17 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +1728 -740
- 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 +1731 -742
- 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 +2071 -508
- package/dist/index.d.cts +600 -112
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +600 -112
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2052 -505
- 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--9zsK5Nq.cjs +0 -59
- package/dist/formatText-B8u71S_K.mjs +0 -42
- package/dist/formatText-B8u71S_K.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/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,23 @@ 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
|
+
b.append(`if (${firstNodeVar} !== null && ${firstNodeVar}.parentNode !== ${htmlParentName}) {`);
|
|
1757
|
+
b.append(`${lastNodeVar} = ${htmlAnchorName}.previousSibling as ChildNode | null;`);
|
|
1758
|
+
b.append(`if (${lastNodeVar} !== null) {`);
|
|
1759
|
+
b.append(`${firstNodeVar} = ${lastNodeVar};`);
|
|
1760
|
+
b.append(`let t_scan: ChildNode | null = ${lastNodeVar};`);
|
|
1761
|
+
b.append(`while (t_scan !== null && t_scan.previousSibling !== null && t_scan.previousSibling !== ${htmlAnchorName} && (t_scan.previousSibling.nodeType !== 3 || (t_scan.previousSibling.textContent ?? "").trim() !== "")) {`);
|
|
1762
|
+
b.append(`t_scan = t_scan.previousSibling;`);
|
|
1763
|
+
b.append(`}`);
|
|
1764
|
+
b.append(`${firstNodeVar} = t_scan;`);
|
|
1765
|
+
b.append(`}`);
|
|
1766
|
+
b.append(`}`);
|
|
1767
|
+
b.append(`}${status.options.dev === true ? ", \"html\"" : ""});`);
|
|
1768
|
+
b.append("");
|
|
1235
1769
|
}
|
|
1236
|
-
|
|
1237
1770
|
//#endregion
|
|
1238
1771
|
//#region src/compile/build/client/buildIfNode.ts
|
|
1239
1772
|
function buildIfNode(node, status, b) {
|
|
@@ -1291,7 +1824,6 @@ function buildIfBranch(node, status, b, parentName, regionName, indexName, index
|
|
|
1291
1824
|
b.append(`${indexName} = ${index};`);
|
|
1292
1825
|
b.append("}");
|
|
1293
1826
|
}
|
|
1294
|
-
|
|
1295
1827
|
//#endregion
|
|
1296
1828
|
//#region src/compile/build/client/buildReplaceNode.ts
|
|
1297
1829
|
function buildReplaceNode(node, status, b) {
|
|
@@ -1331,7 +1863,6 @@ function buildReplaceBranch(node, status, b, parentName, regionName) {
|
|
|
1331
1863
|
buildAddFragment(node, status, b, parentName, "t_before");
|
|
1332
1864
|
b.append("t_pop_region(t_old_region);");
|
|
1333
1865
|
}
|
|
1334
|
-
|
|
1335
1866
|
//#endregion
|
|
1336
1867
|
//#region src/compile/build/client/buildScriptNode.ts
|
|
1337
1868
|
function buildScriptNode(node, status, b) {
|
|
@@ -1342,7 +1873,6 @@ function maybeAppend$1(text, end) {
|
|
|
1342
1873
|
if (!text.endsWith(end)) text += end;
|
|
1343
1874
|
return text;
|
|
1344
1875
|
}
|
|
1345
|
-
|
|
1346
1876
|
//#endregion
|
|
1347
1877
|
//#region src/compile/build/client/buildSwitchNode.ts
|
|
1348
1878
|
function buildSwitchNode(node, status, b) {
|
|
@@ -1354,7 +1884,7 @@ function buildSwitchNode(node, status, b) {
|
|
|
1354
1884
|
if (branches.findIndex((n) => n.operation === "@default") === -1) branches.push({
|
|
1355
1885
|
type: "control",
|
|
1356
1886
|
operation: "@default",
|
|
1357
|
-
statement: "default",
|
|
1887
|
+
statement: "default:",
|
|
1358
1888
|
children: [],
|
|
1359
1889
|
span: {
|
|
1360
1890
|
start: 0,
|
|
@@ -1405,7 +1935,45 @@ function buildSwitchBranch(node, status, b, parentName, regionName, indexName, i
|
|
|
1405
1935
|
break;
|
|
1406
1936
|
}`);
|
|
1407
1937
|
}
|
|
1408
|
-
|
|
1938
|
+
//#endregion
|
|
1939
|
+
//#region src/compile/build/client/buildTryNode.ts
|
|
1940
|
+
function buildTryNode(node, status, b) {
|
|
1941
|
+
const anchorName = node.varName ?? "null";
|
|
1942
|
+
const parentName = node.parentName || anchorName + ".parentNode";
|
|
1943
|
+
const regionName = nextVarName("try_region", status);
|
|
1944
|
+
const branches = node.children.filter((n) => isControlNode(n));
|
|
1945
|
+
const tryBranch = branches.find((n) => n.operation === "@try");
|
|
1946
|
+
const catchBranch = branches.find((n) => n.operation === "@catch");
|
|
1947
|
+
status.imports.add("t_region");
|
|
1948
|
+
status.imports.add("t_run_try");
|
|
1949
|
+
b.append("");
|
|
1950
|
+
b.append("/* @try */");
|
|
1951
|
+
addPushDevBoundary("control", "@try", status, b);
|
|
1952
|
+
b.append(`
|
|
1953
|
+
const ${regionName} = t_region(${status.options.dev === true ? `"try"` : ""});
|
|
1954
|
+
t_run_try(${regionName}, ${anchorName}, (${status.inHead ? "" : "t_before"}) => {`);
|
|
1955
|
+
buildTryBranchBody(tryBranch, status, b, parentName);
|
|
1956
|
+
const catchVar = catchBranch ? trimMatched(catchBranch.statement.substring(5).trim(), "(", ")") || "err" : null;
|
|
1957
|
+
if (catchBranch && catchVar) {
|
|
1958
|
+
addMappedText("},", `(${status.inHead ? "" : "t_before"}, ${replaceForVarNames(catchVar, status)}) => {`, "", catchBranch.span, status, b);
|
|
1959
|
+
buildTryBranchBody(catchBranch, status, b, parentName);
|
|
1960
|
+
b.append(`}${status.options.dev === true ? `, "runTry"` : ""});`);
|
|
1961
|
+
} else b.append(`}, null${status.options.dev === true ? `, "runTry"` : ""});`);
|
|
1962
|
+
addPopDevBoundary(status, b);
|
|
1963
|
+
b.append("");
|
|
1964
|
+
}
|
|
1965
|
+
function buildTryBranchBody(node, status, b, parentName) {
|
|
1966
|
+
if (node.children.length > 0) {
|
|
1967
|
+
buildFragment(node, status, b, parentName, "t_before");
|
|
1968
|
+
status.fragmentStack.push({
|
|
1969
|
+
fragment: node.fragment,
|
|
1970
|
+
path: ""
|
|
1971
|
+
});
|
|
1972
|
+
for (let child of node.children) buildNode(child, status, b, parentName, "t_before");
|
|
1973
|
+
status.fragmentStack.pop();
|
|
1974
|
+
buildAddFragment(node, status, b, parentName, "t_before");
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1409
1977
|
//#endregion
|
|
1410
1978
|
//#region src/compile/build/client/buildControlNode.ts
|
|
1411
1979
|
function buildControlNode(node, status, b) {
|
|
@@ -1430,8 +1998,12 @@ function buildControlNode(node, status, b) {
|
|
|
1430
1998
|
buildAwaitNode(node, status, b);
|
|
1431
1999
|
break;
|
|
1432
2000
|
case "@await":
|
|
1433
|
-
case "@
|
|
2001
|
+
case "@with":
|
|
2002
|
+
case "@try":
|
|
1434
2003
|
case "@catch": break;
|
|
2004
|
+
case "@try group":
|
|
2005
|
+
buildTryNode(node, status, b);
|
|
2006
|
+
break;
|
|
1435
2007
|
case "@replace group":
|
|
1436
2008
|
buildReplaceNode(node, status, b);
|
|
1437
2009
|
break;
|
|
@@ -1454,7 +2026,6 @@ function buildControlNode(node, status, b) {
|
|
|
1454
2026
|
default: throw new Error(`Invalid operation: ${node.operation}`);
|
|
1455
2027
|
}
|
|
1456
2028
|
}
|
|
1457
|
-
|
|
1458
2029
|
//#endregion
|
|
1459
2030
|
//#region src/compile/utils/trimEnd.ts
|
|
1460
2031
|
/**
|
|
@@ -1470,30 +2041,29 @@ function trimEnd(text, char) {
|
|
|
1470
2041
|
while (text[end - 1] === char) end -= 1;
|
|
1471
2042
|
return text.substring(0, end);
|
|
1472
2043
|
}
|
|
1473
|
-
|
|
1474
2044
|
//#endregion
|
|
1475
2045
|
//#region src/compile/build/client/buildMount.ts
|
|
1476
2046
|
function buildMount(_functionName, functionBody, status, b) {
|
|
1477
2047
|
functionBody = replaceForVarNames(functionBody, status);
|
|
1478
|
-
status.imports.add("$
|
|
1479
|
-
b.append("// @ts-ignore\n$
|
|
2048
|
+
status.imports.add("$onmount");
|
|
2049
|
+
b.append("// @ts-ignore\n$onmount(() => {");
|
|
1480
2050
|
b.append(functionBody);
|
|
1481
2051
|
b.append("});");
|
|
1482
2052
|
}
|
|
1483
|
-
|
|
1484
2053
|
//#endregion
|
|
1485
2054
|
//#region src/compile/build/client/stashRun.ts
|
|
1486
2055
|
function stashRun(fragment, functionStart, value, functionEnd, span, status) {
|
|
1487
2056
|
value = replaceForVarNames(value, status);
|
|
1488
2057
|
let functionBody = functionStart + value + functionEnd;
|
|
2058
|
+
let forVarMask = status.forVarNames.length > 0 ? forVarsReadIn(value, status.forVarNames) : void 0;
|
|
1489
2059
|
fragment.effects.push({
|
|
1490
2060
|
functionBody,
|
|
1491
2061
|
spans: [span],
|
|
1492
2062
|
offsets: [functionStart.length],
|
|
1493
|
-
lengths: [value.length]
|
|
2063
|
+
lengths: [value.length],
|
|
2064
|
+
forVarMask
|
|
1494
2065
|
});
|
|
1495
2066
|
}
|
|
1496
|
-
|
|
1497
2067
|
//#endregion
|
|
1498
2068
|
//#region src/compile/build/client/stashRunWithOffsets.ts
|
|
1499
2069
|
function stashRunWithOffsets(fragment, functionStart, value, functionEnd, spans, offsets, lengths, status) {
|
|
@@ -1506,17 +2076,18 @@ function stashRunWithOffsets(fragment, functionStart, value, functionEnd, spans,
|
|
|
1506
2076
|
lengths[i] += changedLength;
|
|
1507
2077
|
for (let j = i + 1; j < offsets.length; j++) offsets[j] += changedLength;
|
|
1508
2078
|
}
|
|
2079
|
+
let forVarMask = status.forVarNames.length > 0 ? forVarsReadIn(functionBody, status.forVarNames) : void 0;
|
|
1509
2080
|
fragment.effects.push({
|
|
1510
2081
|
functionBody,
|
|
1511
2082
|
spans,
|
|
1512
2083
|
offsets,
|
|
1513
|
-
lengths
|
|
2084
|
+
lengths,
|
|
2085
|
+
forVarMask
|
|
1514
2086
|
});
|
|
1515
2087
|
}
|
|
1516
|
-
|
|
1517
2088
|
//#endregion
|
|
1518
2089
|
//#region src/compile/build/client/buildElementNode.ts
|
|
1519
|
-
function buildElementNode(node, status, b, parentName
|
|
2090
|
+
function buildElementNode(node, status, b, parentName) {
|
|
1520
2091
|
if (status.inHead) {
|
|
1521
2092
|
if (node.tagName === "title") buildTitleNode(node, status, b);
|
|
1522
2093
|
else buildHeadNode(node, status, b);
|
|
@@ -1527,8 +2098,10 @@ function buildElementNode(node, status, b, parentName, root = false) {
|
|
|
1527
2098
|
if (svgElement) status.ns = true;
|
|
1528
2099
|
const varName = node.varName;
|
|
1529
2100
|
if (varName) {
|
|
1530
|
-
if (node.tagName === "@element")
|
|
1531
|
-
|
|
2101
|
+
if (node.tagName === "@element") {
|
|
2102
|
+
buildDynamicElementNode(node, status, b);
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
1532
2105
|
buildElementAttributes$1(node, varName, status, b);
|
|
1533
2106
|
}
|
|
1534
2107
|
const oldPreserveWhitespace = status.preserveWhitespace;
|
|
@@ -1539,23 +2112,18 @@ function buildElementNode(node, status, b, parentName, root = false) {
|
|
|
1539
2112
|
}
|
|
1540
2113
|
function buildDynamicElementNode(node, status, b) {
|
|
1541
2114
|
let selfAttribute = node.attributes.find((a) => a.name === "self");
|
|
1542
|
-
if (selfAttribute && selfAttribute.value
|
|
1543
|
-
status.imports.add("$run");
|
|
2115
|
+
if (selfAttribute && selfAttribute.value) {
|
|
1544
2116
|
status.imports.add("t_dynamic");
|
|
1545
2117
|
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"` : ""});`);
|
|
2118
|
+
if (selfAttribute.fullyReactive) {
|
|
2119
|
+
status.imports.add("$run");
|
|
2120
|
+
b.append("$run(() => {");
|
|
2121
|
+
b.append(`${node.varName} = t_dynamic(${node.varName}, ${selfValue});`);
|
|
2122
|
+
b.append(`}${status.options.dev === true ? `, "setDynamic"` : ""});`);
|
|
2123
|
+
} else b.append(`${node.varName} = t_dynamic(${node.varName}, ${selfValue});`);
|
|
1558
2124
|
}
|
|
2125
|
+
buildElementAttributes$1(node, node.varName, status, b);
|
|
2126
|
+
for (let child of node.children) buildNode(child, status, b, node.varName, "null");
|
|
1559
2127
|
}
|
|
1560
2128
|
function buildTitleNode(node, status, b) {
|
|
1561
2129
|
let content = node.children[0].content || "";
|
|
@@ -1598,11 +2166,6 @@ function buildHeadNode(node, status, b) {
|
|
|
1598
2166
|
function buildElementAttributes$1(node, varName, status, b) {
|
|
1599
2167
|
let fragment = status.fragmentStack.at(-1)?.fragment;
|
|
1600
2168
|
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
2169
|
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
2170
|
else if (name === "&value" || name === "&checked") buildBindAttribute(node, varName, name, value, status, b);
|
|
1608
2171
|
else if (name === "onmount") buildMount("elMount", `return (${trimEnd(value.trim(), ";")})(${varName});`, status, b);
|
|
@@ -1615,9 +2178,8 @@ function buildElementAttributes$1(node, varName, status, b) {
|
|
|
1615
2178
|
const propName = status.ns ? "className.baseVal" : "className";
|
|
1616
2179
|
stashRun(fragment, `${status.ns ? "// @ts-ignore\n" : ""}${varName}.${propName} = t_class(`, params.join(", "), ");", span, status);
|
|
1617
2180
|
} else if (name === "style") {
|
|
1618
|
-
status.imports.add("t_attribute");
|
|
1619
2181
|
status.imports.add("t_style");
|
|
1620
|
-
stashRun(fragment,
|
|
2182
|
+
stashRun(fragment, `${varName}.style.cssText += t_style(`, value, ");", span, status);
|
|
1621
2183
|
} else if (name.includes("-")) {
|
|
1622
2184
|
status.imports.add("t_attribute");
|
|
1623
2185
|
stashRun(fragment, `t_attribute(${varName}, "${name}", `, value, ");", span, status);
|
|
@@ -1636,7 +2198,7 @@ function buildElementAttributes$1(node, varName, status, b) {
|
|
|
1636
2198
|
stashRunWithOffsets(fragment, `${status.ns ? "// @ts-ignore\n" : ""}${varName}.${propName} = t_class(`, params.join(", "), ");", spans, offsets, lengths, status);
|
|
1637
2199
|
} else if (name === "style") {
|
|
1638
2200
|
status.imports.add("t_style");
|
|
1639
|
-
stashRunWithOffsets(fragment, `${varName}.
|
|
2201
|
+
stashRunWithOffsets(fragment, `${varName}.style.cssText += t_style(`, value, ");", spans, offsets, lengths, status);
|
|
1640
2202
|
} else if (name.includes("-")) {
|
|
1641
2203
|
status.imports.add("t_attribute");
|
|
1642
2204
|
stashRunWithOffsets(fragment, `t_attribute(${varName}, "${name}", `, value, ");", spans, offsets, lengths, status);
|
|
@@ -1679,9 +2241,18 @@ function buildBindAttribute(node, varName, name, value, status, b) {
|
|
|
1679
2241
|
inputValue = "e.target.value";
|
|
1680
2242
|
break;
|
|
1681
2243
|
}
|
|
1682
|
-
} else if (node.tagName === "select")
|
|
2244
|
+
} else if (node.tagName === "select") {
|
|
2245
|
+
eventName = "change";
|
|
2246
|
+
if (node.attributes.find((a) => a.name === "multiple")) {
|
|
2247
|
+
buildRun("setBinding", `Array.from(${varName}.options).forEach((opt) => opt.selected = Array.isArray(${value}) && ${value}.includes(opt.value));`, status, b);
|
|
2248
|
+
status.imports.add("t_event");
|
|
2249
|
+
b.append(`t_event(${varName}, "change", (e) => ${value} = Array.from(e.target.selectedOptions).map((opt) => opt.value));`);
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
1683
2253
|
let set = `${value} || ${defaultValue}`;
|
|
1684
|
-
|
|
2254
|
+
const propName = name.substring(1);
|
|
2255
|
+
buildRun("setBinding", `${propName === "value" ? `${varName}.${propName} = String(${set})` : `${varName}.${propName} = ${set}`};`, status, b);
|
|
1685
2256
|
status.imports.add("t_event");
|
|
1686
2257
|
b.append(`t_event(${varName}, "${eventName}", (e) => ${value} = ${inputValue});`);
|
|
1687
2258
|
}
|
|
@@ -1717,7 +2288,6 @@ function buildTransitionAttribute(node, varName, name, value, status, b) {
|
|
|
1717
2288
|
b.append(`t_animate(${varName}, null, ${entryVarName});`);
|
|
1718
2289
|
}
|
|
1719
2290
|
}
|
|
1720
|
-
|
|
1721
2291
|
//#endregion
|
|
1722
2292
|
//#region src/compile/build/client/buildRootNode.ts
|
|
1723
2293
|
function buildRootNode(node, status, b, parentName, anchorName) {
|
|
@@ -1730,13 +2300,12 @@ function buildRootNode(node, status, b, parentName, anchorName) {
|
|
|
1730
2300
|
status.fragmentStack.pop();
|
|
1731
2301
|
buildAddFragment(node, status, b, parentName, anchorName);
|
|
1732
2302
|
}
|
|
1733
|
-
|
|
1734
2303
|
//#endregion
|
|
1735
2304
|
//#region src/compile/build/client/buildSlotNode.ts
|
|
1736
2305
|
function buildSlotNode(node, status, b) {
|
|
1737
2306
|
let slotName = node.attributes.find((a) => a.name === "name")?.value;
|
|
1738
2307
|
slotName = slotName ? trimQuotes(slotName) : "_";
|
|
1739
|
-
const propsName = nextVarName("
|
|
2308
|
+
const propsName = nextVarName("slot_props", status);
|
|
1740
2309
|
const slotAttributes = node.attributes.filter((a) => a.name !== "name");
|
|
1741
2310
|
const slotHasProps = slotAttributes.length;
|
|
1742
2311
|
if (slotHasProps) {
|
|
@@ -1773,7 +2342,7 @@ function buildSlotNode(node, status, b) {
|
|
|
1773
2342
|
"$context"
|
|
1774
2343
|
];
|
|
1775
2344
|
b.append(`$slots["${slotName}"](${params.join(", ")})`);
|
|
1776
|
-
const fill = node.children.find((c) => isSpecialNode(c) && c.tagName === "fill");
|
|
2345
|
+
const fill = node.children.find((c) => isSpecialNode(c) && (c.tagName === "fill" || c.tagName === "filldef"));
|
|
1777
2346
|
if (fill && isSpecialNode(fill) && fill.children.length) {
|
|
1778
2347
|
b.append(`} else {`);
|
|
1779
2348
|
buildFragment(fill, status, b, slotParentName, slotAnchorName);
|
|
@@ -1787,7 +2356,6 @@ function buildSlotNode(node, status, b) {
|
|
|
1787
2356
|
}
|
|
1788
2357
|
b.append(`}`);
|
|
1789
2358
|
}
|
|
1790
|
-
|
|
1791
2359
|
//#endregion
|
|
1792
2360
|
//#region src/compile/build/client/buildSpecialNode.ts
|
|
1793
2361
|
function buildSpecialNode(node, status, b, parentName, root = false) {
|
|
@@ -1795,9 +2363,10 @@ function buildSpecialNode(node, status, b, parentName, root = false) {
|
|
|
1795
2363
|
case "slot":
|
|
1796
2364
|
buildSlotNode(node, status, b);
|
|
1797
2365
|
break;
|
|
1798
|
-
case "fill":
|
|
2366
|
+
case "fill":
|
|
2367
|
+
case "filldef": break;
|
|
1799
2368
|
case "@element":
|
|
1800
|
-
buildElementNode(node, status, b, parentName
|
|
2369
|
+
buildElementNode(node, status, b, parentName);
|
|
1801
2370
|
break;
|
|
1802
2371
|
case "@component":
|
|
1803
2372
|
buildComponentNode(node, status, b, root);
|
|
@@ -1806,7 +2375,6 @@ function buildSpecialNode(node, status, b, parentName, root = false) {
|
|
|
1806
2375
|
}
|
|
1807
2376
|
return "";
|
|
1808
2377
|
}
|
|
1809
|
-
|
|
1810
2378
|
//#endregion
|
|
1811
2379
|
//#region src/compile/build/client/buildTextNode.ts
|
|
1812
2380
|
function buildTextNode(node, status) {
|
|
@@ -1823,7 +2391,6 @@ function buildTextNode(node, status) {
|
|
|
1823
2391
|
let maxLevel = 0;
|
|
1824
2392
|
for (let i = 0; i < content.length; i++) {
|
|
1825
2393
|
const char = content[i];
|
|
1826
|
-
const nextChar = content[i + 1];
|
|
1827
2394
|
if (char === "{") {
|
|
1828
2395
|
level++;
|
|
1829
2396
|
maxLevel = Math.max(level, maxLevel);
|
|
@@ -1842,25 +2409,10 @@ function buildTextNode(node, status) {
|
|
|
1842
2409
|
}
|
|
1843
2410
|
} else if (char === "`" && level === 0) textContent += "\\";
|
|
1844
2411
|
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);
|
|
2412
|
+
const skipped = skipStringOrComment(content, i);
|
|
2413
|
+
if (skipped !== -1) {
|
|
2414
|
+
textContent += content.substring(i, skipped);
|
|
2415
|
+
i = skipped - 1;
|
|
1864
2416
|
continue;
|
|
1865
2417
|
}
|
|
1866
2418
|
}
|
|
@@ -1880,7 +2432,6 @@ function buildTextNode(node, status) {
|
|
|
1880
2432
|
stashRunWithOffsets(fragment, `${node.varName}.textContent = `, textContent, ";", node.spans, offsets, lengths, status);
|
|
1881
2433
|
}
|
|
1882
2434
|
}
|
|
1883
|
-
|
|
1884
2435
|
//#endregion
|
|
1885
2436
|
//#region src/compile/build/client/buildNode.ts
|
|
1886
2437
|
function buildNode(node, status, b, parentName, anchorName, root = false) {
|
|
@@ -1899,7 +2450,7 @@ function buildNode(node, status, b, parentName, anchorName, root = false) {
|
|
|
1899
2450
|
buildComponentNode(node, status, b, root);
|
|
1900
2451
|
break;
|
|
1901
2452
|
case "element":
|
|
1902
|
-
buildElementNode(node, status, b, parentName
|
|
2453
|
+
buildElementNode(node, status, b, parentName);
|
|
1903
2454
|
break;
|
|
1904
2455
|
case "text":
|
|
1905
2456
|
buildTextNode(node, status);
|
|
@@ -1907,28 +2458,40 @@ function buildNode(node, status, b, parentName, anchorName, root = false) {
|
|
|
1907
2458
|
case "special":
|
|
1908
2459
|
buildSpecialNode(node, status, b, parentName, root);
|
|
1909
2460
|
break;
|
|
2461
|
+
case "comment": break;
|
|
1910
2462
|
default: throw new Error(`Invalid node type: ${node.type}`);
|
|
1911
2463
|
}
|
|
1912
2464
|
}
|
|
1913
|
-
|
|
1914
2465
|
//#endregion
|
|
1915
2466
|
//#region src/compile/build/client/buildCode.ts
|
|
1916
2467
|
const importsMap$1 = {
|
|
1917
2468
|
$watch: "import { $watch } from \"${folder}\";",
|
|
2469
|
+
$bind: "import { $bind } from \"${folder}\";",
|
|
1918
2470
|
$cache: "import { $cache } from \"${folder}\";",
|
|
2471
|
+
$async: "import { $async } from \"${folder}\";",
|
|
2472
|
+
$pending: "import { $pending } from \"${folder}\";",
|
|
2473
|
+
$refresh: "import { $refresh } from \"${folder}\";",
|
|
1919
2474
|
$run: "import { $run } from \"${folder}\";",
|
|
1920
|
-
$
|
|
2475
|
+
$onmount: "import { $onmount } from \"${folder}\";",
|
|
2476
|
+
$stream: "import { $stream } from \"${folder}\";",
|
|
2477
|
+
fromElement: "import { fromElement } from \"${folder}\";",
|
|
2478
|
+
fromServer: "import { fromServer } from \"${folder}\";",
|
|
2479
|
+
fromWebSocket: "import { fromWebSocket } from \"${folder}\";",
|
|
1921
2480
|
$unwrap: "import { $unwrap } from \"${folder}\";",
|
|
1922
2481
|
$peek: "import { $peek } from \"${folder}\";",
|
|
1923
2482
|
$batch: "import { $batch } from \"${folder}\";",
|
|
1924
2483
|
t_region: "import { t_region } from \"${folder}\";",
|
|
1925
2484
|
t_push_region: "import { t_push_region } from \"${folder}\";",
|
|
1926
2485
|
t_pop_region: "import { t_pop_region } from \"${folder}\";",
|
|
2486
|
+
t_rerun_region_effects: "import { t_rerun_region_effects } from \"${folder}\";",
|
|
1927
2487
|
t_run_control: "import { t_run_control } from \"${folder}\";",
|
|
1928
2488
|
t_run_branch: "import { t_run_branch } from \"${folder}\";",
|
|
1929
2489
|
t_list_item: "import { t_list_item } from \"${folder}\";",
|
|
1930
2490
|
t_run_list: "import { t_run_list } from \"${folder}\";",
|
|
2491
|
+
t_run_await: "import { t_run_await } from \"${folder}\";",
|
|
2492
|
+
t_run_try: "import { t_run_try } from \"${folder}\";",
|
|
1931
2493
|
t_add_fragment: "import { t_add_fragment } from \"${folder}\";",
|
|
2494
|
+
t_add_element: "import { t_add_element } from \"${folder}\";",
|
|
1932
2495
|
t_apply_props: "import { t_apply_props } from \"${folder}\";",
|
|
1933
2496
|
t_class: "import { t_class } from \"${folder}\";",
|
|
1934
2497
|
t_style: "import { t_style } from \"${folder}\";",
|
|
@@ -1936,19 +2499,24 @@ const importsMap$1 = {
|
|
|
1936
2499
|
t_dynamic: "import { t_dynamic } from \"${folder}\";",
|
|
1937
2500
|
t_fmt: "import { t_fmt } from \"${folder}\";",
|
|
1938
2501
|
t_fragment: "import { t_fragment } from \"${folder}\";",
|
|
2502
|
+
t_fragment_el: "import { t_fragment_el } from \"${folder}\";",
|
|
1939
2503
|
t_event: "import { t_event } from \"${folder}\";",
|
|
1940
2504
|
t_animate: "import { t_animate } from \"${folder}\";",
|
|
1941
2505
|
t_root: "import { t_root } from \"${folder}\";",
|
|
2506
|
+
t_root_el: "import { t_root_el } from \"${folder}\";",
|
|
1942
2507
|
t_anchor: "import { t_anchor } from \"${folder}\";",
|
|
1943
2508
|
t_child: "import { t_child } from \"${folder}\";",
|
|
1944
2509
|
t_next: "import { t_next } from \"${folder}\";",
|
|
1945
2510
|
t_skip: "import { t_skip } from \"${folder}\";",
|
|
1946
2511
|
t_frg: "import { t_frg } from \"${folder}\";",
|
|
2512
|
+
t_save_hydration: "import { t_save_hydration } from \"${folder}\";",
|
|
2513
|
+
t_restore_hydration: "import { t_restore_hydration } from \"${folder}\";",
|
|
1947
2514
|
t_elm: "import { t_elm } from \"${folder}\";",
|
|
1948
2515
|
t_txt: "import { t_txt } from \"${folder}\";",
|
|
1949
2516
|
t_cmt: "import { t_cmt } from \"${folder}\";",
|
|
1950
2517
|
t_print: "import { t_print } from \"${folder}\";",
|
|
1951
2518
|
ListItem: "import { type ListItem } from \"${folder}\";",
|
|
2519
|
+
ListItemSpec: "import { type ListItemSpec } from \"${folder}\";",
|
|
1952
2520
|
SlotRender: "import { type SlotRender } from \"${folder}\";",
|
|
1953
2521
|
devContext: "import { devContext } from \"${folder}/dev\";",
|
|
1954
2522
|
t_push_dev_bound: "import { t_push_dev_bound } from \"${folder}/dev\";",
|
|
@@ -1961,13 +2529,10 @@ function buildCode(template, map, options) {
|
|
|
1961
2529
|
buildTemplate(template, map, imports, b, options);
|
|
1962
2530
|
let startSize = b.toString().length;
|
|
1963
2531
|
if (imports.size) {
|
|
2532
|
+
const folder = options?.renderFolder ?? "@torpor/view";
|
|
2533
|
+
const sortedImports = Array.from(imports).map((imp) => (importsMap$1[imp] ?? imp).replace("${folder}", folder)).sort().reverse();
|
|
1964
2534
|
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
|
-
}
|
|
2535
|
+
for (let imp of sortedImports) b.prepend(imp);
|
|
1971
2536
|
}
|
|
1972
2537
|
if (options?.mapped) {
|
|
1973
2538
|
let diff = b.toString().length - startSize + 1;
|
|
@@ -1980,13 +2545,27 @@ function buildCode(template, map, options) {
|
|
|
1980
2545
|
}
|
|
1981
2546
|
function buildTemplate(template, map, imports, b, options) {
|
|
1982
2547
|
let script = template.script.map((s) => s.script).join("\n");
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
if (/\$
|
|
2548
|
+
for (const component of template.components) {
|
|
2549
|
+
if (component.markup) script += "\n" + collectMarkupExpressions(component.markup);
|
|
2550
|
+
if (component.error) script += "\n" + collectMarkupExpressions(component.error);
|
|
2551
|
+
if (component.head) script += "\n" + collectMarkupExpressions(component.head);
|
|
2552
|
+
}
|
|
2553
|
+
let scriptCode = codeRanges(script).map(([start, end]) => script.substring(start, end)).join("");
|
|
2554
|
+
if (/\$watch\b/.test(scriptCode)) imports.add("$watch");
|
|
2555
|
+
if (/\$bind\b/.test(scriptCode)) imports.add("$bind");
|
|
2556
|
+
if (/\$cache\b/.test(scriptCode)) imports.add("$cache");
|
|
2557
|
+
if (/\$async\b/.test(scriptCode)) imports.add("$async");
|
|
2558
|
+
if (/\$pending\b/.test(scriptCode)) imports.add("$pending");
|
|
2559
|
+
if (/\$refresh\b/.test(scriptCode)) imports.add("$refresh");
|
|
2560
|
+
if (/\$run\b/.test(scriptCode)) imports.add("$run");
|
|
2561
|
+
if (/\$onmount\b/.test(scriptCode)) imports.add("$onmount");
|
|
2562
|
+
if (/\$stream\b/.test(scriptCode)) imports.add("$stream");
|
|
2563
|
+
if (/\bfromElement\b/.test(scriptCode)) imports.add("fromElement");
|
|
2564
|
+
if (/\bfromServer\b/.test(scriptCode)) imports.add("fromServer");
|
|
2565
|
+
if (/\bfromWebSocket\b/.test(scriptCode)) imports.add("fromWebSocket");
|
|
2566
|
+
if (/\$unwrap\b/.test(scriptCode)) imports.add("$unwrap");
|
|
2567
|
+
if (/\$peek\b/.test(scriptCode)) imports.add("$peek");
|
|
2568
|
+
if (/\$batch\b/.test(scriptCode)) imports.add("$batch");
|
|
1990
2569
|
let currentIndex = 0;
|
|
1991
2570
|
let current = template.components[0];
|
|
1992
2571
|
let status = makeStatus(imports, map, current, options);
|
|
@@ -1998,10 +2577,10 @@ function buildTemplate(template, map, imports, b, options) {
|
|
|
1998
2577
|
${current.markup ? "$parent" : "_$parent"}: ParentNode,
|
|
1999
2578
|
${current.markup ? "$anchor" : "_$anchor"}: Node | null,`);
|
|
2000
2579
|
if (current.params !== void 0) addMappedText("", current.params, ",", chunk.span, status, b);
|
|
2001
|
-
else b.append(`${current.props?.length ? "$props: Record<PropertyKey, any>" : "
|
|
2580
|
+
else b.append(`${current.props?.length ? "$props: Record<PropertyKey, any>" : "_$props?: Record<PropertyKey, any>"},`);
|
|
2002
2581
|
b.append(`
|
|
2003
|
-
|
|
2004
|
-
|
|
2582
|
+
${current.contextProps?.length || current.markup && markupRendersComponent(current.markup) || current.error && markupRendersComponent(current.error) ? "$context" : "_$context"}?: Record<PropertyKey, any>,
|
|
2583
|
+
${current.slotProps?.length ? "$slots" : "_$slots"}?: Record<string, SlotRender>,`);
|
|
2005
2584
|
} else if (chunk.script === ") /* @return_type */ {") b.append("): void {");
|
|
2006
2585
|
else if (chunk.script === "/* @start */") {
|
|
2007
2586
|
addPushDevBoundary("component", current.name || "Anon Component", status, b);
|
|
@@ -2010,10 +2589,26 @@ function buildTemplate(template, map, imports, b, options) {
|
|
|
2010
2589
|
if (current.markup) {
|
|
2011
2590
|
b.append("");
|
|
2012
2591
|
b.append("/* User interface */");
|
|
2592
|
+
if (current.error) {
|
|
2593
|
+
status.imports.add("t_region");
|
|
2594
|
+
status.imports.add("t_run_try");
|
|
2595
|
+
b.append(`const t_error_region = t_region();`);
|
|
2596
|
+
b.append(`t_run_try(t_error_region, $anchor, () => {`);
|
|
2597
|
+
}
|
|
2013
2598
|
buildFragmentText(current.markup, status, b);
|
|
2014
2599
|
b.append("");
|
|
2015
2600
|
buildNode(current.markup, status, b, "$parent", "$anchor", true);
|
|
2016
2601
|
}
|
|
2602
|
+
} else if (chunk.script === "/* @error */") {
|
|
2603
|
+
if (current.markup && current.error) {
|
|
2604
|
+
b.append(`}, (_, ${current.errorVar ?? "err"}) => {`);
|
|
2605
|
+
b.append("");
|
|
2606
|
+
b.append("/* User interface error */");
|
|
2607
|
+
buildFragmentText(current.error, status, b);
|
|
2608
|
+
b.append("");
|
|
2609
|
+
buildNode(current.error, status, b, "$parent", "$anchor", true);
|
|
2610
|
+
b.append(`}${status.options.dev === true ? ", \"runError\"" : ""});`);
|
|
2611
|
+
}
|
|
2017
2612
|
} else if (chunk.script === "/* @head */") {
|
|
2018
2613
|
if (current.head) {
|
|
2019
2614
|
b.append("");
|
|
@@ -2045,15 +2640,26 @@ function makeStatus(imports, map, current, options) {
|
|
|
2045
2640
|
options: options ?? {}
|
|
2046
2641
|
};
|
|
2047
2642
|
}
|
|
2048
|
-
|
|
2049
2643
|
//#endregion
|
|
2050
2644
|
//#region src/compile/build/client/buildStyles.ts
|
|
2051
2645
|
function buildStyles(style, styleHash) {
|
|
2052
2646
|
const b = new Builder();
|
|
2053
|
-
for (let block of style.
|
|
2647
|
+
for (let block of style.children) buildStyleNode(block, b, styleHash);
|
|
2054
2648
|
return b.toString();
|
|
2055
2649
|
}
|
|
2056
|
-
function
|
|
2650
|
+
function buildStyleNode(node, b, styleHash) {
|
|
2651
|
+
switch (node.type) {
|
|
2652
|
+
case "block":
|
|
2653
|
+
buildBlockNode(node, b, styleHash);
|
|
2654
|
+
break;
|
|
2655
|
+
case "attribute":
|
|
2656
|
+
buildAttributeNode(node, b);
|
|
2657
|
+
break;
|
|
2658
|
+
case "comment": break;
|
|
2659
|
+
default: throw new Error(`Invalid style node type: ${node.type}`);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
function buildBlockNode(block, b, styleHash) {
|
|
2057
2663
|
if (block.selector.startsWith("@")) b.append(`${block.selector} {`);
|
|
2058
2664
|
else {
|
|
2059
2665
|
block.selector = block.selector.replaceAll(/\s+/g, " ");
|
|
@@ -2079,8 +2685,7 @@ function buildStyleBlock(block, b, styleHash) {
|
|
|
2079
2685
|
}
|
|
2080
2686
|
b.append(`${selectors.join(" ").replaceAll(" ,", ",")} {`);
|
|
2081
2687
|
}
|
|
2082
|
-
for (let
|
|
2083
|
-
for (let child of block.children) buildStyleBlock(child, b, styleHash);
|
|
2688
|
+
for (let child of block.children) buildStyleNode(child, b, styleHash);
|
|
2084
2689
|
b.append(`}`);
|
|
2085
2690
|
}
|
|
2086
2691
|
function addSelector(selectors, selector, styleHash, start, end, global) {
|
|
@@ -2099,10 +2704,9 @@ function addSelector(selectors, selector, styleHash, start, end, global) {
|
|
|
2099
2704
|
selectors.push(s);
|
|
2100
2705
|
}
|
|
2101
2706
|
}
|
|
2102
|
-
function
|
|
2707
|
+
function buildAttributeNode(attribute, b) {
|
|
2103
2708
|
b.append(`${attribute.name}: ${attribute.value};`);
|
|
2104
2709
|
}
|
|
2105
|
-
|
|
2106
2710
|
//#endregion
|
|
2107
2711
|
//#region src/compile/build/server/buildServerComponentNode.ts
|
|
2108
2712
|
function buildServerComponentNode(node, status, b) {
|
|
@@ -2118,20 +2722,16 @@ function buildServerComponentNode(node, status, b) {
|
|
|
2118
2722
|
for (let { name, value } of node.attributes) {
|
|
2119
2723
|
if (name.startsWith("&")) name = name.substring(1);
|
|
2120
2724
|
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");
|
|
2725
|
+
if (node.scopeStyles) value = `[${value}, "torp-${status.styleHash}"]`;
|
|
2130
2726
|
props.push({
|
|
2131
2727
|
name,
|
|
2132
|
-
value
|
|
2728
|
+
value
|
|
2133
2729
|
});
|
|
2134
|
-
} else if (value != null) props.push({
|
|
2730
|
+
} else if (name === "style" && value != null) props.push({
|
|
2731
|
+
name,
|
|
2732
|
+
value
|
|
2733
|
+
});
|
|
2734
|
+
else if (value != null) props.push({
|
|
2135
2735
|
name,
|
|
2136
2736
|
value
|
|
2137
2737
|
});
|
|
@@ -2141,17 +2741,17 @@ function buildServerComponentNode(node, status, b) {
|
|
|
2141
2741
|
});
|
|
2142
2742
|
}
|
|
2143
2743
|
b.append(`const ${propsName} = {`);
|
|
2144
|
-
for (let p of props) b.append(`${p.name}: ${p.value},`);
|
|
2744
|
+
for (let p of props) b.append(p.name ? `${p.name}: ${p.value},` : `${p.value},`);
|
|
2145
2745
|
b.append("};");
|
|
2146
2746
|
}
|
|
2147
2747
|
const componentHasSlots = node.children.length;
|
|
2148
2748
|
const slotsName = componentHasSlots ? nextVarName("slots", status) : "undefined";
|
|
2149
2749
|
if (componentHasSlots) {
|
|
2150
2750
|
b.append(`const ${slotsName}: Record<string, ServerSlotRender> = {};`);
|
|
2151
|
-
for (let slot of node.children) if (isSpecialNode(slot) && slot.tagName === "fill") {
|
|
2751
|
+
for (let slot of node.children) if (isSpecialNode(slot) && (slot.tagName === "fill" || slot.tagName === "filldef")) {
|
|
2152
2752
|
const nameAttribute = slot.attributes.find((a) => a.name === "name");
|
|
2153
2753
|
const slotName = nameAttribute?.value ? trimQuotes(nameAttribute.value) : "_";
|
|
2154
|
-
const slotParams = [slot.hasSlotProps ? "$
|
|
2754
|
+
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
2755
|
b.append(`${slot.hasSlotProps ? "// @ts-ignore\n" : ""}${slotsName}["${slotName}"] = (\n${slotParams.join(",\n")}\n) => {`);
|
|
2156
2756
|
b.append(`let t_body = "";`);
|
|
2157
2757
|
for (let child of slot.children) buildServerNode(child, status, b);
|
|
@@ -2175,9 +2775,8 @@ function buildServerComponentNode(node, status, b) {
|
|
|
2175
2775
|
b.append(`t_body += ${componentResult}.body;`);
|
|
2176
2776
|
b.append(`t_head += ${componentResult}.head;`);
|
|
2177
2777
|
status.output += HYDRATION_END_COMMENT;
|
|
2178
|
-
status.output +=
|
|
2778
|
+
status.output += "<!>";
|
|
2179
2779
|
}
|
|
2180
|
-
|
|
2181
2780
|
//#endregion
|
|
2182
2781
|
//#region src/compile/build/server/buildServerAwaitNode.ts
|
|
2183
2782
|
function buildServerAwaitNode(node, status, b) {
|
|
@@ -2186,9 +2785,9 @@ function buildServerAwaitNode(node, status, b) {
|
|
|
2186
2785
|
b.append(`t_body += \`${status.output}\`;`);
|
|
2187
2786
|
status.output = "";
|
|
2188
2787
|
}
|
|
2189
|
-
for (let branch of node.children) if (isControlNode(branch) && branch.operation === "@
|
|
2788
|
+
for (let branch of node.children) if (isControlNode(branch) && branch.operation === "@with") buildServerAwaitBranch(branch, status, b);
|
|
2190
2789
|
status.output += HYDRATION_END_COMMENT;
|
|
2191
|
-
status.output +=
|
|
2790
|
+
status.output += "<!>";
|
|
2192
2791
|
}
|
|
2193
2792
|
function buildServerAwaitBranch(node, status, b) {
|
|
2194
2793
|
for (let child of node.children) buildServerNode(child, status, b);
|
|
@@ -2197,7 +2796,6 @@ function buildServerAwaitBranch(node, status, b) {
|
|
|
2197
2796
|
status.output = "";
|
|
2198
2797
|
}
|
|
2199
2798
|
}
|
|
2200
|
-
|
|
2201
2799
|
//#endregion
|
|
2202
2800
|
//#region src/compile/build/server/buildServerForNode.ts
|
|
2203
2801
|
function buildServerForNode(node, status, b) {
|
|
@@ -2208,7 +2806,7 @@ function buildServerForNode(node, status, b) {
|
|
|
2208
2806
|
}
|
|
2209
2807
|
for (let branch of node.children) if (isControlNode(branch)) buildServerForBranch(branch, status, b);
|
|
2210
2808
|
status.output += HYDRATION_END_COMMENT;
|
|
2211
|
-
status.output +=
|
|
2809
|
+
status.output += "<!>";
|
|
2212
2810
|
}
|
|
2213
2811
|
function buildServerForBranch(node, status, b) {
|
|
2214
2812
|
b.append(`${node.statement} {`);
|
|
@@ -2220,16 +2818,14 @@ function buildServerForBranch(node, status, b) {
|
|
|
2220
2818
|
}
|
|
2221
2819
|
b.append("}");
|
|
2222
2820
|
}
|
|
2223
|
-
|
|
2224
2821
|
//#endregion
|
|
2225
2822
|
//#region src/compile/build/server/buildServerHtmlNode.ts
|
|
2226
2823
|
function buildServerHtmlNode(node, status) {
|
|
2227
2824
|
status.output += HYDRATION_START_COMMENT;
|
|
2228
2825
|
for (let branch of node.children) if (isControlNode(branch)) status.output += `\${${branch.statement}}`;
|
|
2229
2826
|
status.output += HYDRATION_END_COMMENT;
|
|
2230
|
-
status.output +=
|
|
2827
|
+
status.output += "<!>";
|
|
2231
2828
|
}
|
|
2232
|
-
|
|
2233
2829
|
//#endregion
|
|
2234
2830
|
//#region src/compile/build/server/buildServerIfNode.ts
|
|
2235
2831
|
function buildServerIfNode(node, status, b) {
|
|
@@ -2240,7 +2836,7 @@ function buildServerIfNode(node, status, b) {
|
|
|
2240
2836
|
}
|
|
2241
2837
|
for (let branch of node.children) if (isControlNode(branch)) buildServerIfBranch(branch, status, b);
|
|
2242
2838
|
status.output += HYDRATION_END_COMMENT;
|
|
2243
|
-
status.output +=
|
|
2839
|
+
status.output += "<!>";
|
|
2244
2840
|
}
|
|
2245
2841
|
function buildServerIfBranch(node, status, b) {
|
|
2246
2842
|
b.append(`${node.statement} {`);
|
|
@@ -2252,7 +2848,6 @@ function buildServerIfBranch(node, status, b) {
|
|
|
2252
2848
|
}
|
|
2253
2849
|
b.append("}");
|
|
2254
2850
|
}
|
|
2255
|
-
|
|
2256
2851
|
//#endregion
|
|
2257
2852
|
//#region src/compile/build/server/buildServerReplaceNode.ts
|
|
2258
2853
|
function buildServerReplaceNode(node, status, b) {
|
|
@@ -2263,7 +2858,7 @@ function buildServerReplaceNode(node, status, b) {
|
|
|
2263
2858
|
}
|
|
2264
2859
|
for (let branch of node.children) if (isControlNode(branch)) buildServerReplaceBranch(branch, status, b);
|
|
2265
2860
|
status.output += HYDRATION_END_COMMENT;
|
|
2266
|
-
status.output +=
|
|
2861
|
+
status.output += "<!>";
|
|
2267
2862
|
}
|
|
2268
2863
|
function buildServerReplaceBranch(node, status, b) {
|
|
2269
2864
|
b.append(`${node.statement};`);
|
|
@@ -2273,7 +2868,6 @@ function buildServerReplaceBranch(node, status, b) {
|
|
|
2273
2868
|
status.output = "";
|
|
2274
2869
|
}
|
|
2275
2870
|
}
|
|
2276
|
-
|
|
2277
2871
|
//#endregion
|
|
2278
2872
|
//#region src/compile/build/server/buildServerScriptNode.ts
|
|
2279
2873
|
function buildServerScriptNode(node, b) {
|
|
@@ -2283,7 +2877,6 @@ function maybeAppend(text, end) {
|
|
|
2283
2877
|
if (!text.endsWith(end)) text += end;
|
|
2284
2878
|
return text;
|
|
2285
2879
|
}
|
|
2286
|
-
|
|
2287
2880
|
//#endregion
|
|
2288
2881
|
//#region src/compile/build/server/buildServerSwitchNode.ts
|
|
2289
2882
|
function buildServerSwitchNode(node, status, b) {
|
|
@@ -2296,7 +2889,7 @@ function buildServerSwitchNode(node, status, b) {
|
|
|
2296
2889
|
for (let branch of node.children.filter((c) => isControlNode(c))) if (isControlNode(branch)) buildServerSwitchBranch(branch, status, b);
|
|
2297
2890
|
b.append("}");
|
|
2298
2891
|
status.output += HYDRATION_END_COMMENT;
|
|
2299
|
-
status.output +=
|
|
2892
|
+
status.output += "<!>";
|
|
2300
2893
|
}
|
|
2301
2894
|
function buildServerSwitchBranch(node, status, b) {
|
|
2302
2895
|
b.append(`${node.statement} {`);
|
|
@@ -2309,7 +2902,39 @@ function buildServerSwitchBranch(node, status, b) {
|
|
|
2309
2902
|
b.append("break;");
|
|
2310
2903
|
b.append("}");
|
|
2311
2904
|
}
|
|
2312
|
-
|
|
2905
|
+
//#endregion
|
|
2906
|
+
//#region src/compile/build/server/buildServerTryNode.ts
|
|
2907
|
+
function buildServerTryNode(node, status, b) {
|
|
2908
|
+
status.output += HYDRATION_START_COMMENT;
|
|
2909
|
+
if (status.output) {
|
|
2910
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2911
|
+
status.output = "";
|
|
2912
|
+
}
|
|
2913
|
+
const branches = node.children.filter((n) => isControlNode(n));
|
|
2914
|
+
let tryBranch = branches.find((n) => n.operation === "@try");
|
|
2915
|
+
let catchBranch = branches.find((n) => n.operation === "@catch");
|
|
2916
|
+
if (catchBranch) {
|
|
2917
|
+
b.append(`const t_try_body = t_body;`);
|
|
2918
|
+
b.append(`const t_try_head = t_head;`);
|
|
2919
|
+
b.append("try {");
|
|
2920
|
+
if (tryBranch) buildServerTryBranch(tryBranch, status, b);
|
|
2921
|
+
const catchVar = trimMatched(catchBranch.statement.substring(5).trim(), "(", ")");
|
|
2922
|
+
b.append(`} catch (${catchVar || "err"}) {`);
|
|
2923
|
+
b.append("t_body = t_try_body;");
|
|
2924
|
+
b.append("t_head = t_try_head;");
|
|
2925
|
+
buildServerTryBranch(catchBranch, status, b);
|
|
2926
|
+
b.append("}");
|
|
2927
|
+
} else if (tryBranch) buildServerTryBranch(tryBranch, status, b);
|
|
2928
|
+
status.output += HYDRATION_END_COMMENT;
|
|
2929
|
+
status.output += "<!>";
|
|
2930
|
+
}
|
|
2931
|
+
function buildServerTryBranch(node, status, b) {
|
|
2932
|
+
for (let child of node.children) buildServerNode(child, status, b);
|
|
2933
|
+
if (status.output) {
|
|
2934
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2935
|
+
status.output = "";
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2313
2938
|
//#endregion
|
|
2314
2939
|
//#region src/compile/build/server/buildServerControlNode.ts
|
|
2315
2940
|
function buildServerControlNode(node, status, b) {
|
|
@@ -2334,8 +2959,12 @@ function buildServerControlNode(node, status, b) {
|
|
|
2334
2959
|
buildServerAwaitNode(node, status, b);
|
|
2335
2960
|
break;
|
|
2336
2961
|
case "@await":
|
|
2337
|
-
case "@
|
|
2962
|
+
case "@with":
|
|
2963
|
+
case "@try":
|
|
2338
2964
|
case "@catch": break;
|
|
2965
|
+
case "@try group":
|
|
2966
|
+
buildServerTryNode(node, status, b);
|
|
2967
|
+
break;
|
|
2339
2968
|
case "@replace group":
|
|
2340
2969
|
buildServerReplaceNode(node, status, b);
|
|
2341
2970
|
break;
|
|
@@ -2366,27 +2995,6 @@ function buildServerControlNode(node, status, b) {
|
|
|
2366
2995
|
default: throw new Error(`Invalid operation: ${node.operation}`);
|
|
2367
2996
|
}
|
|
2368
2997
|
}
|
|
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
2998
|
//#endregion
|
|
2391
2999
|
//#region src/compile/build/server/buildServerElementNode.ts
|
|
2392
3000
|
function buildServerElementNode(node, status, b) {
|
|
@@ -2398,7 +3006,7 @@ function buildServerElementNode(node, status, b) {
|
|
|
2398
3006
|
if (selfAttribute) tagName = `$\{${selfAttribute.value}}`;
|
|
2399
3007
|
}
|
|
2400
3008
|
status.output += `<${tagName}${attributes}>`;
|
|
2401
|
-
if (!
|
|
3009
|
+
if (!voidTags.includes(tagName)) {
|
|
2402
3010
|
const oldPreserveWhitespace = status.preserveWhitespace;
|
|
2403
3011
|
status.preserveWhitespace = ["pre", "code"].includes(tagName);
|
|
2404
3012
|
for (let child of node.children) buildServerNode(child, status, b);
|
|
@@ -2410,6 +3018,7 @@ function buildElementAttributes(node, status) {
|
|
|
2410
3018
|
let needsClass = node.scopeStyles;
|
|
2411
3019
|
let attributes = [];
|
|
2412
3020
|
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") {
|
|
3021
|
+
if (name === "&value" && node.tagName === "select" && node.attributes.some((a) => a.name === "multiple")) continue;
|
|
2413
3022
|
let defaultValue = "";
|
|
2414
3023
|
let typeAttribute = node.attributes.find((a) => a.name === "type");
|
|
2415
3024
|
if (typeAttribute && typeAttribute.value) switch (trimQuotes(typeAttribute.value)) {
|
|
@@ -2456,13 +3065,11 @@ function buildElementAttributes(node, status) {
|
|
|
2456
3065
|
if (needsClass) attributes.push(`class="torp-${status.styleHash}"`);
|
|
2457
3066
|
return attributes.join(" ");
|
|
2458
3067
|
}
|
|
2459
|
-
|
|
2460
3068
|
//#endregion
|
|
2461
3069
|
//#region src/compile/build/server/buildServerRootNode.ts
|
|
2462
3070
|
function buildServerRootNode(node, status, b) {
|
|
2463
3071
|
for (let child of node.children) buildServerNode(child, status, b);
|
|
2464
3072
|
}
|
|
2465
|
-
|
|
2466
3073
|
//#endregion
|
|
2467
3074
|
//#region src/compile/build/server/buildServerSlotNode.ts
|
|
2468
3075
|
function buildServerSlotNode(node, status, b) {
|
|
@@ -2473,7 +3080,7 @@ function buildServerSlotNode(node, status, b) {
|
|
|
2473
3080
|
}
|
|
2474
3081
|
let slotName = node.attributes.find((a) => a.name === "name")?.value;
|
|
2475
3082
|
slotName = slotName ? trimQuotes(slotName) : "_";
|
|
2476
|
-
const propsName = nextVarName("
|
|
3083
|
+
const propsName = nextVarName("slot_props", status);
|
|
2477
3084
|
const slotAttributes = node.attributes.filter((a) => a.name !== "name");
|
|
2478
3085
|
const slotHasProps = slotAttributes.length;
|
|
2479
3086
|
if (slotHasProps) {
|
|
@@ -2483,7 +3090,7 @@ function buildServerSlotNode(node, status, b) {
|
|
|
2483
3090
|
}
|
|
2484
3091
|
b.append(`if ($slots && $slots["${slotName}"]) {`);
|
|
2485
3092
|
b.append(`t_body += $slots["${slotName}"](${slotHasProps ? propsName : "undefined"}, $context);`);
|
|
2486
|
-
const fill = node.children.find((c) => isSpecialNode(c) && c.tagName === "fill");
|
|
3093
|
+
const fill = node.children.find((c) => isSpecialNode(c) && (c.tagName === "fill" || c.tagName === "filldef"));
|
|
2487
3094
|
if (fill && isSpecialNode(fill) && fill.children.length) {
|
|
2488
3095
|
b.append(`} else {`);
|
|
2489
3096
|
for (let child of fill.children) buildServerNode(child, status, b);
|
|
@@ -2494,9 +3101,8 @@ function buildServerSlotNode(node, status, b) {
|
|
|
2494
3101
|
}
|
|
2495
3102
|
b.append(`}`);
|
|
2496
3103
|
status.output += HYDRATION_END_COMMENT;
|
|
2497
|
-
status.output +=
|
|
3104
|
+
status.output += "<!>";
|
|
2498
3105
|
}
|
|
2499
|
-
|
|
2500
3106
|
//#endregion
|
|
2501
3107
|
//#region src/compile/build/server/buildServerSpecialNode.ts
|
|
2502
3108
|
function buildServerSpecialNode(node, status, b) {
|
|
@@ -2504,7 +3110,8 @@ function buildServerSpecialNode(node, status, b) {
|
|
|
2504
3110
|
case "slot":
|
|
2505
3111
|
buildServerSlotNode(node, status, b);
|
|
2506
3112
|
break;
|
|
2507
|
-
case "fill":
|
|
3113
|
+
case "fill":
|
|
3114
|
+
case "filldef": break;
|
|
2508
3115
|
case "@element":
|
|
2509
3116
|
buildServerElementNode(node, status, b);
|
|
2510
3117
|
break;
|
|
@@ -2514,7 +3121,6 @@ function buildServerSpecialNode(node, status, b) {
|
|
|
2514
3121
|
default: throw new Error(`Invalid special node: ${node.tagName}`);
|
|
2515
3122
|
}
|
|
2516
3123
|
}
|
|
2517
|
-
|
|
2518
3124
|
//#endregion
|
|
2519
3125
|
//#region src/compile/build/server/buildServerTextNode.ts
|
|
2520
3126
|
function buildServerTextNode(node, status) {
|
|
@@ -2523,7 +3129,6 @@ function buildServerTextNode(node, status) {
|
|
|
2523
3129
|
let level = 0;
|
|
2524
3130
|
for (let i = 0; i < content.length; i++) {
|
|
2525
3131
|
const char = content[i];
|
|
2526
|
-
const nextChar = content[i + 1];
|
|
2527
3132
|
if (char === "{") {
|
|
2528
3133
|
level++;
|
|
2529
3134
|
if (level === 1) {
|
|
@@ -2536,35 +3141,16 @@ function buildServerTextNode(node, status) {
|
|
|
2536
3141
|
if (level === 0) status.output += ")";
|
|
2537
3142
|
} else if (char === "`" && level === 0) status.output += "\\";
|
|
2538
3143
|
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));
|
|
3144
|
+
const skipped = skipStringOrComment(content, i);
|
|
3145
|
+
if (skipped !== -1) {
|
|
3146
|
+
status.output += content.substring(i, skipped);
|
|
3147
|
+
i = skipped - 1;
|
|
2558
3148
|
continue;
|
|
2559
3149
|
}
|
|
2560
3150
|
}
|
|
2561
3151
|
status.output += char;
|
|
2562
3152
|
}
|
|
2563
3153
|
}
|
|
2564
|
-
function escapeHtml(text) {
|
|
2565
|
-
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
3154
|
//#endregion
|
|
2569
3155
|
//#region src/compile/build/server/buildServerNode.ts
|
|
2570
3156
|
function buildServerNode(node, status, b) {
|
|
@@ -2587,17 +3173,25 @@ function buildServerNode(node, status, b) {
|
|
|
2587
3173
|
case "special":
|
|
2588
3174
|
buildServerSpecialNode(node, status, b);
|
|
2589
3175
|
break;
|
|
3176
|
+
case "comment": break;
|
|
2590
3177
|
default: throw new Error(`Invalid node type: ${node.type}`);
|
|
2591
3178
|
}
|
|
2592
3179
|
}
|
|
2593
|
-
|
|
2594
3180
|
//#endregion
|
|
2595
3181
|
//#region src/compile/build/server/buildServerCode.ts
|
|
2596
3182
|
const importsMap = {
|
|
2597
3183
|
$watch: "import { $watch } from \"${folder}\";",
|
|
3184
|
+
$bind: "import { $bind } from \"${folder}\";",
|
|
2598
3185
|
$cache: "import { $cache } from \"${folder}\";",
|
|
3186
|
+
$async: "import { $async } from \"${folder}\";",
|
|
3187
|
+
$pending: "import { $pending } from \"${folder}\";",
|
|
3188
|
+
$refresh: "import { $refresh } from \"${folder}\";",
|
|
2599
3189
|
$run: "import { $run } from \"${folder}\";",
|
|
2600
|
-
$
|
|
3190
|
+
$onmount: "import { $onmount } from \"${folder}\";",
|
|
3191
|
+
$stream: "import { $stream } from \"${folder}\";",
|
|
3192
|
+
fromElement: "import { fromElement } from \"${folder}\";",
|
|
3193
|
+
fromServer: "import { fromServer } from \"${folder}\";",
|
|
3194
|
+
fromWebSocket: "import { fromWebSocket } from \"${folder}\";",
|
|
2601
3195
|
$unwrap: "import { $unwrap } from \"${folder}\";",
|
|
2602
3196
|
$peek: "import { $peek } from \"${folder}\";",
|
|
2603
3197
|
$batch: "import { $batch } from \"${folder}\";",
|
|
@@ -2613,33 +3207,46 @@ function buildServerCode(template, options) {
|
|
|
2613
3207
|
imports.add("ServerSlotRender");
|
|
2614
3208
|
buildServerTemplate(template, imports, b, options);
|
|
2615
3209
|
if (imports.size) {
|
|
3210
|
+
const folder = options?.renderFolder ?? "@torpor/view/ssr";
|
|
3211
|
+
const sortedImports = Array.from(imports).map((imp) => (importsMap[imp] ?? imp).replace("${folder}", folder)).sort().reverse();
|
|
2616
3212
|
b.prepend("");
|
|
2617
|
-
for (let imp of
|
|
2618
|
-
imp = importsMap[imp] || imp;
|
|
2619
|
-
b.prepend(imp.replace("${folder}", options?.renderFolder || "@torpor/view/ssr"));
|
|
2620
|
-
}
|
|
3213
|
+
for (let imp of sortedImports) b.prepend(imp);
|
|
2621
3214
|
}
|
|
2622
3215
|
return b.toString();
|
|
2623
3216
|
}
|
|
2624
3217
|
function buildServerTemplate(template, imports, b, options) {
|
|
2625
3218
|
let script = template.script.map((s) => s.script).join("\n");
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
if (/\$
|
|
3219
|
+
for (const component of template.components) {
|
|
3220
|
+
if (component.markup) script += "\n" + collectMarkupExpressions(component.markup);
|
|
3221
|
+
if (component.error) script += "\n" + collectMarkupExpressions(component.error);
|
|
3222
|
+
if (component.head) script += "\n" + collectMarkupExpressions(component.head);
|
|
3223
|
+
}
|
|
3224
|
+
let scriptCode = codeRanges(script).map(([start, end]) => script.substring(start, end)).join("");
|
|
3225
|
+
if (/\$watch\b/.test(scriptCode)) imports.add("$watch");
|
|
3226
|
+
if (/\$bind\b/.test(scriptCode)) imports.add("$bind");
|
|
3227
|
+
if (/\$cache\b/.test(scriptCode)) imports.add("$cache");
|
|
3228
|
+
if (/\$async\b/.test(scriptCode)) imports.add("$async");
|
|
3229
|
+
if (/\$pending\b/.test(scriptCode)) imports.add("$pending");
|
|
3230
|
+
if (/\$refresh\b/.test(scriptCode)) imports.add("$refresh");
|
|
3231
|
+
if (/\$run\b/.test(scriptCode)) imports.add("$run");
|
|
3232
|
+
if (/\$onmount\b/.test(scriptCode)) imports.add("$onmount");
|
|
3233
|
+
if (/\$stream\b/.test(scriptCode)) imports.add("$stream");
|
|
3234
|
+
if (/\bfromElement\b/.test(scriptCode)) imports.add("fromElement");
|
|
3235
|
+
if (/\bfromServer\b/.test(scriptCode)) imports.add("fromServer");
|
|
3236
|
+
if (/\bfromWebSocket\b/.test(scriptCode)) imports.add("fromWebSocket");
|
|
3237
|
+
if (/\$unwrap\b/.test(scriptCode)) imports.add("$unwrap");
|
|
3238
|
+
if (/\$peek\b/.test(scriptCode)) imports.add("$peek");
|
|
3239
|
+
if (/\$batch\b/.test(scriptCode)) imports.add("$batch");
|
|
2633
3240
|
let currentIndex = 0;
|
|
2634
3241
|
let current = template.components[0];
|
|
2635
3242
|
for (let chunk of template.script) if (chunk.script === "/* @params */") {
|
|
2636
3243
|
let params = [
|
|
2637
|
-
current.params ?? `${current.props?.length ? "$props: Record<PropertyKey, any>" : "
|
|
2638
|
-
`${current.contextProps?.length ? "$context" : "
|
|
2639
|
-
`${current.slotProps?.length ? "$slots" : "
|
|
3244
|
+
current.params ?? `${current.props?.length ? "$props: Record<PropertyKey, any>" : "_$props?: Record<PropertyKey, any>"}`,
|
|
3245
|
+
`${current.contextProps?.length || current.markup && markupRendersComponent(current.markup) || current.error && markupRendersComponent(current.error) ? "$context" : "_$context"}?: Record<PropertyKey, any>`,
|
|
3246
|
+
`${current.slotProps?.length ? "$slots" : "_$slots"}?: Record<string, ServerSlotRender>`
|
|
2640
3247
|
];
|
|
2641
|
-
b.append(params.join(",\n"));
|
|
2642
|
-
} else if (chunk.script === ") /* @return_type */ {") b.append("): { body: string
|
|
3248
|
+
b.append(params.join(",\n") + ",");
|
|
3249
|
+
} else if (chunk.script === ") /* @return_type */ {") b.append("): { body: string; head: string } {");
|
|
2643
3250
|
else if (chunk.script === "/* @start */") {
|
|
2644
3251
|
if (current.contextProps?.length) b.append(`$context = Object.assign({}, $context);`);
|
|
2645
3252
|
b.append(`let t_body = "";`);
|
|
@@ -2656,12 +3263,38 @@ function buildServerTemplate(template, imports, b, options) {
|
|
|
2656
3263
|
};
|
|
2657
3264
|
b.append("");
|
|
2658
3265
|
b.append("/* User interface */");
|
|
3266
|
+
if (current.error) {
|
|
3267
|
+
b.append(`const t_try_body = t_body;`);
|
|
3268
|
+
b.append(`const t_try_head = t_head;`);
|
|
3269
|
+
b.append("try {");
|
|
3270
|
+
}
|
|
2659
3271
|
buildServerNode(current.markup, status, b);
|
|
2660
3272
|
if (status.output) {
|
|
2661
3273
|
b.append(`t_body += \`${status.output}\`;`);
|
|
2662
3274
|
status.output = "";
|
|
2663
3275
|
}
|
|
2664
3276
|
}
|
|
3277
|
+
} else if (chunk.script === "/* @error */") {
|
|
3278
|
+
if (current.markup && current.error) {
|
|
3279
|
+
const status = {
|
|
3280
|
+
imports,
|
|
3281
|
+
output: "",
|
|
3282
|
+
styleHash: current.style?.hash || "",
|
|
3283
|
+
varNames: {},
|
|
3284
|
+
preserveWhitespace: false,
|
|
3285
|
+
options
|
|
3286
|
+
};
|
|
3287
|
+
b.append(`} catch (${current.errorVar}) {`);
|
|
3288
|
+
b.append("t_body = t_try_body;");
|
|
3289
|
+
b.append("t_head = t_try_head;");
|
|
3290
|
+
b.append("/* User interface error */");
|
|
3291
|
+
buildServerNode(current.error, status, b);
|
|
3292
|
+
if (status.output) {
|
|
3293
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
3294
|
+
status.output = "";
|
|
3295
|
+
}
|
|
3296
|
+
b.append("}");
|
|
3297
|
+
}
|
|
2665
3298
|
} else if (chunk.script === "/* @head */") {} else if (chunk.script === "/* @style */") {
|
|
2666
3299
|
if (current.style) {
|
|
2667
3300
|
b.append("");
|
|
@@ -2669,13 +3302,115 @@ function buildServerTemplate(template, imports, b, options) {
|
|
|
2669
3302
|
const styles = buildStyles(current.style, current.style.hash).replaceAll("\"", "\\\"").replaceAll(/\s+/g, " ");
|
|
2670
3303
|
b.append(`t_head += "<style id='${current.style.hash}'>${styles}</style>";\n`);
|
|
2671
3304
|
}
|
|
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);
|
|
3305
|
+
} else if (chunk.script === "/* @end */") {
|
|
3306
|
+
b.append(`return { body: t_body, head: t_head };`);
|
|
3307
|
+
currentIndex += 1;
|
|
3308
|
+
current = template.components[currentIndex];
|
|
3309
|
+
} else b.append(chunk.script);
|
|
3310
|
+
}
|
|
3311
|
+
//#endregion
|
|
3312
|
+
//#region src/compile/utils/trimWhitespace.ts
|
|
3313
|
+
/**
|
|
3314
|
+
* Tag names whose contents should keep all whitespace untouched, matching
|
|
3315
|
+
* native HTML rendering of `pre`/`textarea` and the existing build-time
|
|
3316
|
+
* `preserveWhitespace` behavior for `code`.
|
|
3317
|
+
*/
|
|
3318
|
+
const PRESERVE_TAGS = /* @__PURE__ */ new Set([
|
|
3319
|
+
"pre",
|
|
3320
|
+
"textarea",
|
|
3321
|
+
"code"
|
|
3322
|
+
]);
|
|
3323
|
+
/**
|
|
3324
|
+
* Container tags whose child layout ignores inter-child whitespace entirely
|
|
3325
|
+
* (table layout collapses it). Inside these, whitespace-only text nodes
|
|
3326
|
+
* between children are removed rather than collapsed to a single space.
|
|
3327
|
+
*/
|
|
3328
|
+
const WHITESPACE_INSIGNIFICANT_TAGS = /* @__PURE__ */ new Set([
|
|
3329
|
+
"tr",
|
|
3330
|
+
"tbody",
|
|
3331
|
+
"thead",
|
|
3332
|
+
"tfoot",
|
|
3333
|
+
"colgroup",
|
|
3334
|
+
"table",
|
|
3335
|
+
"ul",
|
|
3336
|
+
"ol",
|
|
3337
|
+
"select"
|
|
3338
|
+
]);
|
|
3339
|
+
/**
|
|
3340
|
+
* Trims and collapses whitespace text nodes in a parsed markup tree,
|
|
3341
|
+
* Svelte 5-style:
|
|
3342
|
+
*
|
|
3343
|
+
* - pure-whitespace text nodes at the start/end of a container are removed
|
|
3344
|
+
* - pure-whitespace text nodes between siblings are collapsed to a single space
|
|
3345
|
+
* (or removed entirely inside table/list containers where whitespace is
|
|
3346
|
+
* insignificant — see WHITESPACE_INSIGNIFICANT_TAGS)
|
|
3347
|
+
* - whitespace inside `pre`, `textarea`, and `code` is preserved
|
|
3348
|
+
*
|
|
3349
|
+
* Whitespace adjacent to non-rendering control nodes (e.g. `@key`, `@const`)
|
|
3350
|
+
* is treated as leading/trailing whitespace of the container and removed,
|
|
3351
|
+
* because those nodes produce no DOM output — without this rule, every row
|
|
3352
|
+
* of `@for { @key = ...; <tr>...</tr> }` would emit a phantom leading
|
|
3353
|
+
* whitespace text node.
|
|
3354
|
+
*
|
|
3355
|
+
* Mixed text nodes (containing non-whitespace content) are left untouched;
|
|
3356
|
+
* their internal whitespace continues to be collapsed at build time. The
|
|
3357
|
+
* walk mutates the tree in place and is idempotent.
|
|
3358
|
+
*
|
|
3359
|
+
* @param node The root node of the markup to trim (a component's markup or head)
|
|
3360
|
+
*/
|
|
3361
|
+
function trimWhitespace(node) {
|
|
3362
|
+
trimChildren(node.children, false, void 0);
|
|
3363
|
+
}
|
|
3364
|
+
function trimChildren(children, preserveAll, parentTag) {
|
|
3365
|
+
for (const child of children) {
|
|
3366
|
+
const grandChildren = getChildren(child);
|
|
3367
|
+
if (grandChildren) {
|
|
3368
|
+
const childTag = getTagName(child);
|
|
3369
|
+
trimChildren(grandChildren, preserveAll || PRESERVE_TAGS.has(childTag ?? ""), childTag);
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
if (preserveAll) return;
|
|
3373
|
+
if (parentTag !== void 0 && WHITESPACE_INSIGNIFICANT_TAGS.has(parentTag)) {
|
|
3374
|
+
for (let i = children.length - 1; i >= 0; i--) if (isWhitespaceText(children[i])) children.splice(i, 1);
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
const length = children.length;
|
|
3378
|
+
const hasRenderingBefore = Array.from({ length });
|
|
3379
|
+
const hasRenderingAfter = Array.from({ length });
|
|
3380
|
+
let seenBefore = false;
|
|
3381
|
+
let seenAfter = false;
|
|
3382
|
+
for (let i = 0; i < length; i++) {
|
|
3383
|
+
hasRenderingBefore[i] = seenBefore;
|
|
3384
|
+
if (isRenderingSibling(children[i])) seenBefore = true;
|
|
3385
|
+
const j = length - 1 - i;
|
|
3386
|
+
hasRenderingAfter[j] = seenAfter;
|
|
3387
|
+
if (isRenderingSibling(children[j])) seenAfter = true;
|
|
3388
|
+
}
|
|
3389
|
+
for (let i = length - 1; i >= 0; i--) if (isWhitespaceText(children[i]) && (!hasRenderingBefore[i] || !hasRenderingAfter[i])) children.splice(i, 1);
|
|
3390
|
+
for (const child of children) if (isTextNode(child) && child.content !== "" && isSpace(child.content)) child.content = " ";
|
|
3391
|
+
}
|
|
3392
|
+
/**
|
|
3393
|
+
* Returns true if `node` is a "rendering sibling" — i.e. a node that produces
|
|
3394
|
+
* visible DOM output and therefore acts as a boundary between leading,
|
|
3395
|
+
* inter-sibling, and trailing whitespace. Whitespace text nodes themselves
|
|
3396
|
+
* (the subject of the trim) and non-rendering control nodes (e.g. @key,
|
|
3397
|
+
* @const) are not rendering siblings.
|
|
3398
|
+
*/
|
|
3399
|
+
function isRenderingSibling(node) {
|
|
3400
|
+
if (isWhitespaceText(node)) return false;
|
|
3401
|
+
if (isControlNode(node) && NON_RENDERING_OPERATIONS.has(node.operation)) return false;
|
|
3402
|
+
return true;
|
|
3403
|
+
}
|
|
3404
|
+
function getChildren(node) {
|
|
3405
|
+
if (node.type === "text" || node.type === "comment") return void 0;
|
|
3406
|
+
return node.children;
|
|
3407
|
+
}
|
|
3408
|
+
function getTagName(node) {
|
|
3409
|
+
if (node.type === "element" || node.type === "component" || node.type === "special") return node.tagName;
|
|
3410
|
+
}
|
|
3411
|
+
function isWhitespaceText(node) {
|
|
3412
|
+
return isTextNode(node) && node.content !== "" && isSpace(node.content);
|
|
2677
3413
|
}
|
|
2678
|
-
|
|
2679
3414
|
//#endregion
|
|
2680
3415
|
//#region src/compile/build.ts
|
|
2681
3416
|
/**
|
|
@@ -2686,6 +3421,11 @@ function buildServerTemplate(template, imports, b, options) {
|
|
|
2686
3421
|
* @returns The component's code and styles
|
|
2687
3422
|
*/
|
|
2688
3423
|
function build(template, options) {
|
|
3424
|
+
if (!options?.preserveWhitespace) for (const component of template.components) {
|
|
3425
|
+
if (component.markup) trimWhitespace(component.markup);
|
|
3426
|
+
if (component.error) trimWhitespace(component.error);
|
|
3427
|
+
if (component.head) trimWhitespace(component.head);
|
|
3428
|
+
}
|
|
2689
3429
|
let map = [];
|
|
2690
3430
|
return {
|
|
2691
3431
|
code: options?.server ? buildServerCode(template, options) : buildCode(template, map, options),
|
|
@@ -2696,7 +3436,6 @@ function build(template, options) {
|
|
|
2696
3436
|
map
|
|
2697
3437
|
};
|
|
2698
3438
|
}
|
|
2699
|
-
|
|
2700
3439
|
//#endregion
|
|
2701
3440
|
//#region src/compile/buildType.ts
|
|
2702
3441
|
/**
|
|
@@ -2718,7 +3457,7 @@ function buildType(template, options) {
|
|
|
2718
3457
|
}
|
|
2719
3458
|
b.append(`
|
|
2720
3459
|
${component.documentation ?? ""}
|
|
2721
|
-
${component.default ? "declare" : "export declare"} function ${component.name}(
|
|
3460
|
+
${component.default ? "declare" : "export declare"} function ${component.name}${component.typeParams ?? ""}(
|
|
2722
3461
|
$parent: ParentNode,
|
|
2723
3462
|
$anchor: Node | null,
|
|
2724
3463
|
${component.params || "$props?: Record<PropertyKey, any>"},
|
|
@@ -2730,13 +3469,29 @@ function buildType(template, options) {
|
|
|
2730
3469
|
}
|
|
2731
3470
|
return b.toString();
|
|
2732
3471
|
}
|
|
2733
|
-
|
|
3472
|
+
//#endregion
|
|
3473
|
+
//#region src/compile/utils/endOfTemplateString.ts
|
|
3474
|
+
/**
|
|
3475
|
+
* Returns the index of the closing backtick of the template string that
|
|
3476
|
+
* starts at `start`.
|
|
3477
|
+
*
|
|
3478
|
+
* NOTE: For scanning JavaScript expression text, use `codeScanner.ts` instead
|
|
3479
|
+
* -- this variant doesn't handle escaped characters or nested templates in
|
|
3480
|
+
* interpolations, and is kept only for non-JS contexts (CSS in `@style`
|
|
3481
|
+
* blocks) where a simpler scan is wanted.
|
|
3482
|
+
*/
|
|
3483
|
+
function endOfTemplateString(content, start) {
|
|
3484
|
+
let level = 0;
|
|
3485
|
+
for (let j = start + 1; j < content.length; j++) if (content[j] === "`" && content[j - 1] !== "\\" && level === 0) return j;
|
|
3486
|
+
else if (content[j] === "{" && (level > 0 || content[j - 1] === "$")) level += 1;
|
|
3487
|
+
else if (content[j] === "}" && level > 0) level -= 1;
|
|
3488
|
+
return content.length;
|
|
3489
|
+
}
|
|
2734
3490
|
//#endregion
|
|
2735
3491
|
//#region src/compile/utils/isSpaceNode.ts
|
|
2736
3492
|
function isSpaceNode(node) {
|
|
2737
3493
|
return isTextNode(node) && isSpace(node.content);
|
|
2738
3494
|
}
|
|
2739
|
-
|
|
2740
3495
|
//#endregion
|
|
2741
3496
|
//#region src/compile/parse/utils/accept.ts
|
|
2742
3497
|
/**
|
|
@@ -2757,25 +3512,24 @@ function accept(value, status, advance = true) {
|
|
|
2757
3512
|
}
|
|
2758
3513
|
return false;
|
|
2759
3514
|
}
|
|
2760
|
-
|
|
2761
3515
|
//#endregion
|
|
2762
3516
|
//#region src/compile/parse/parseInlineScript.ts
|
|
2763
3517
|
function parseInlineScript(status) {
|
|
2764
3518
|
const start = status.i;
|
|
2765
3519
|
let braceCount = 0;
|
|
2766
|
-
while (status.i < status.source.length)
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
3520
|
+
while (status.i < status.source.length) {
|
|
3521
|
+
const skipped = skipStringOrComment(status.source, status.i);
|
|
3522
|
+
if (skipped !== -1) {
|
|
3523
|
+
status.i = skipped;
|
|
3524
|
+
continue;
|
|
3525
|
+
}
|
|
3526
|
+
if (accept("{", status)) braceCount += 1;
|
|
3527
|
+
else if (accept("}", status)) if (braceCount > 0) braceCount -= 1;
|
|
3528
|
+
else return status.source.substring(start, status.i - 1);
|
|
3529
|
+
else status.i += 1;
|
|
3530
|
+
}
|
|
2776
3531
|
return "";
|
|
2777
3532
|
}
|
|
2778
|
-
|
|
2779
3533
|
//#endregion
|
|
2780
3534
|
//#region src/compile/utils/trimStart.ts
|
|
2781
3535
|
/**
|
|
@@ -2791,7 +3545,6 @@ function trimStart(text, char) {
|
|
|
2791
3545
|
while (text[start] === char) start += 1;
|
|
2792
3546
|
return text.substring(start);
|
|
2793
3547
|
}
|
|
2794
|
-
|
|
2795
3548
|
//#endregion
|
|
2796
3549
|
//#region src/compile/parse/utils/consumeSpace.ts
|
|
2797
3550
|
/**
|
|
@@ -2808,7 +3561,6 @@ function consumeSpace(status) {
|
|
|
2808
3561
|
for (status.i; status.i < status.source.length; status.i++) if (!isSpaceChar(status.source, status.i)) return status.source.substring(start, status.i);
|
|
2809
3562
|
return "";
|
|
2810
3563
|
}
|
|
2811
|
-
|
|
2812
3564
|
//#endregion
|
|
2813
3565
|
//#region src/compile/parse/addSpaceElement.ts
|
|
2814
3566
|
function addSpaceElement(parent, status) {
|
|
@@ -2826,7 +3578,6 @@ function addSpaceElement(parent, status) {
|
|
|
2826
3578
|
}
|
|
2827
3579
|
}
|
|
2828
3580
|
}
|
|
2829
|
-
|
|
2830
3581
|
//#endregion
|
|
2831
3582
|
//#region src/compile/utils/isFullyReactive.ts
|
|
2832
3583
|
/**
|
|
@@ -2839,7 +3590,41 @@ function addSpaceElement(parent, status) {
|
|
|
2839
3590
|
function isFullyReactive(text) {
|
|
2840
3591
|
return text.trim().startsWith("{") && text.trim().endsWith("}");
|
|
2841
3592
|
}
|
|
2842
|
-
|
|
3593
|
+
//#endregion
|
|
3594
|
+
//#region src/compile/parse/utils/addError.ts
|
|
3595
|
+
/**
|
|
3596
|
+
* Adds an error to the parse status
|
|
3597
|
+
*
|
|
3598
|
+
* @param status The parse status
|
|
3599
|
+
* @param message The error message to add
|
|
3600
|
+
* @param start (Optional) The start position of the error (defaults to the
|
|
3601
|
+
* current parse position)
|
|
3602
|
+
*/
|
|
3603
|
+
function addError(status, message, start = status.i, end = status.i) {
|
|
3604
|
+
let startLine = 0;
|
|
3605
|
+
let lastLineStart = 0;
|
|
3606
|
+
let i = 0;
|
|
3607
|
+
for (; i < start; i++) if (status.source[i] === "\n") {
|
|
3608
|
+
startLine += 1;
|
|
3609
|
+
lastLineStart = i + 1;
|
|
3610
|
+
}
|
|
3611
|
+
let startChar = start - lastLineStart;
|
|
3612
|
+
let endLine = startLine;
|
|
3613
|
+
for (; i < end; i++) if (status.source[i] === "\n") {
|
|
3614
|
+
endLine += 1;
|
|
3615
|
+
lastLineStart = i + 1;
|
|
3616
|
+
}
|
|
3617
|
+
let endChar = end - lastLineStart;
|
|
3618
|
+
status.errors.push({
|
|
3619
|
+
message,
|
|
3620
|
+
startIndex: start,
|
|
3621
|
+
startLine,
|
|
3622
|
+
startChar,
|
|
3623
|
+
endIndex: end,
|
|
3624
|
+
endLine,
|
|
3625
|
+
endChar
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
2843
3628
|
//#endregion
|
|
2844
3629
|
//#region src/compile/parse/utils/isAlphaNumericChar.ts
|
|
2845
3630
|
/**
|
|
@@ -2854,7 +3639,6 @@ function isAlphaNumericChar(text, index) {
|
|
|
2854
3639
|
let code = text.charCodeAt(index);
|
|
2855
3640
|
return code > 47 && code < 58 || code > 64 && code < 91 || code > 96 && code < 123;
|
|
2856
3641
|
}
|
|
2857
|
-
|
|
2858
3642
|
//#endregion
|
|
2859
3643
|
//#region src/compile/parse/utils/consumeAlphaNumeric.ts
|
|
2860
3644
|
/**
|
|
@@ -2871,7 +3655,6 @@ function consumeAlphaNumeric(status) {
|
|
|
2871
3655
|
for (status.i; status.i < status.source.length; status.i++) if (!isAlphaNumericChar(status.source, status.i)) return status.source.substring(start, status.i);
|
|
2872
3656
|
return "";
|
|
2873
3657
|
}
|
|
2874
|
-
|
|
2875
3658
|
//#endregion
|
|
2876
3659
|
//#region src/compile/parse/utils/consumeUntil.ts
|
|
2877
3660
|
/**
|
|
@@ -2889,7 +3672,6 @@ function consumeUntil(chars, status) {
|
|
|
2889
3672
|
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
3673
|
return "";
|
|
2891
3674
|
}
|
|
2892
|
-
|
|
2893
3675
|
//#endregion
|
|
2894
3676
|
//#region src/compile/parse/parseTag.ts
|
|
2895
3677
|
function parseTag(status) {
|
|
@@ -2912,6 +3694,7 @@ function parseTag(status) {
|
|
|
2912
3694
|
type: [
|
|
2913
3695
|
"slot",
|
|
2914
3696
|
"fill",
|
|
3697
|
+
"filldef",
|
|
2915
3698
|
"@component",
|
|
2916
3699
|
"@element"
|
|
2917
3700
|
].includes(tagName) ? "special" : "element",
|
|
@@ -2965,10 +3748,12 @@ function parseAttribute(status) {
|
|
|
2965
3748
|
value = name.substring(1, name.length - 1);
|
|
2966
3749
|
valueStart++;
|
|
2967
3750
|
valueEnd = valueStart + name.length;
|
|
2968
|
-
|
|
3751
|
+
if (value.startsWith("...")) name = value;
|
|
3752
|
+
else name = value.split(".").at(-1);
|
|
2969
3753
|
if (name.endsWith("!")) name = name.substring(0, name.length - 1);
|
|
2970
3754
|
reactive = fullyReactive = true;
|
|
2971
3755
|
}
|
|
3756
|
+
if (value != null && reactive && value.startsWith("...")) addError(status, `Spread attributes are not supported`, valueStart, valueEnd);
|
|
2972
3757
|
return {
|
|
2973
3758
|
name,
|
|
2974
3759
|
value,
|
|
@@ -2999,7 +3784,6 @@ function parseAttributeValue(status) {
|
|
|
2999
3784
|
return value;
|
|
3000
3785
|
}
|
|
3001
3786
|
}
|
|
3002
|
-
|
|
3003
3787
|
//#endregion
|
|
3004
3788
|
//#region src/compile/parse/parseText.ts
|
|
3005
3789
|
function parseText(status, element) {
|
|
@@ -3013,18 +3797,16 @@ function parseText(status, element) {
|
|
|
3013
3797
|
let level = 0;
|
|
3014
3798
|
for (let k = j; k < status.source.length; k++) {
|
|
3015
3799
|
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;
|
|
3800
|
+
if (char === "{") level += 1;
|
|
3022
3801
|
else if (char === "}") {
|
|
3023
3802
|
level -= 1;
|
|
3024
3803
|
if (level === 0) {
|
|
3025
3804
|
j = k;
|
|
3026
3805
|
break;
|
|
3027
3806
|
}
|
|
3807
|
+
} else {
|
|
3808
|
+
const skipped = skipStringOrComment(status.source, k);
|
|
3809
|
+
if (skipped !== -1) k = skipped - 1;
|
|
3028
3810
|
}
|
|
3029
3811
|
}
|
|
3030
3812
|
spans.push({
|
|
@@ -3060,7 +3842,38 @@ function parseText(status, element) {
|
|
|
3060
3842
|
status.i = end;
|
|
3061
3843
|
} else status.i += 1;
|
|
3062
3844
|
}
|
|
3063
|
-
|
|
3845
|
+
//#endregion
|
|
3846
|
+
//#region src/compile/parse/utils/usesSlot.ts
|
|
3847
|
+
const slotRegex = /\$slot\b/;
|
|
3848
|
+
/**
|
|
3849
|
+
* Checks whether the $slot variable is read in the nodes' own scope
|
|
3850
|
+
*
|
|
3851
|
+
* Note that the children of a component node are excluded, because they are
|
|
3852
|
+
* passed to the component as fills, which get their own $slot parameter.
|
|
3853
|
+
* The component's attributes do belong to this scope.
|
|
3854
|
+
*
|
|
3855
|
+
* @param nodes The nodes to check
|
|
3856
|
+
*
|
|
3857
|
+
* @returns True if a $slot variable is read in the nodes' own scope
|
|
3858
|
+
*/
|
|
3859
|
+
function usesSlot(nodes) {
|
|
3860
|
+
for (let node of nodes) if (node.type === "text") {
|
|
3861
|
+
const text = node;
|
|
3862
|
+
if (slotRegex.test(text.content)) return true;
|
|
3863
|
+
} else if (node.type === "control") {
|
|
3864
|
+
const control = node;
|
|
3865
|
+
if (slotRegex.test(control.statement)) return true;
|
|
3866
|
+
if (usesSlot(control.children)) return true;
|
|
3867
|
+
} else if (node.type === "element" || node.type === "special" || node.type === "component") {
|
|
3868
|
+
const element = node;
|
|
3869
|
+
for (let attribute of element.attributes) {
|
|
3870
|
+
if (slotRegex.test(attribute.name)) return true;
|
|
3871
|
+
if (attribute.value && slotRegex.test(attribute.value)) return true;
|
|
3872
|
+
}
|
|
3873
|
+
if (element.type !== "component" && usesSlot(element.children)) return true;
|
|
3874
|
+
}
|
|
3875
|
+
return false;
|
|
3876
|
+
}
|
|
3064
3877
|
//#endregion
|
|
3065
3878
|
//#region src/compile/parse/slottifyChildNodes.ts
|
|
3066
3879
|
/**
|
|
@@ -3068,65 +3881,33 @@ function parseText(status, element) {
|
|
|
3068
3881
|
* <fill> node
|
|
3069
3882
|
*/
|
|
3070
3883
|
function slottifyChildNodes(node) {
|
|
3071
|
-
|
|
3884
|
+
let nonFillNodes = [];
|
|
3885
|
+
for (let child of node.children) if (isFillNode(child)) if (nonFillNodes.find((n) => !isSpaceNode(n)) === void 0) nonFillNodes = [];
|
|
3886
|
+
else break;
|
|
3887
|
+
else nonFillNodes.push(child);
|
|
3888
|
+
if (nonFillNodes.find((n) => !isSpaceNode(n)) === void 0) nonFillNodes = [];
|
|
3072
3889
|
if (nonFillNodes.length) {
|
|
3073
3890
|
let defaultFillNode = node.children.find((n) => isFillNode(n) && !n.attributes.find((a) => a.name === "name"));
|
|
3074
3891
|
if (!defaultFillNode) {
|
|
3075
3892
|
defaultFillNode = {
|
|
3076
3893
|
type: "special",
|
|
3077
|
-
tagName: "
|
|
3894
|
+
tagName: "filldef",
|
|
3078
3895
|
attributes: [],
|
|
3079
3896
|
children: nonFillNodes,
|
|
3080
3897
|
span: {
|
|
3081
3898
|
start: 0,
|
|
3082
3899
|
end: 0
|
|
3083
|
-
}
|
|
3900
|
+
},
|
|
3901
|
+
hasSlotProps: usesSlot(nonFillNodes)
|
|
3084
3902
|
};
|
|
3085
3903
|
node.children.unshift(defaultFillNode);
|
|
3086
3904
|
}
|
|
3905
|
+
node.children = node.children.filter((c) => isFillNode(c) || !nonFillNodes.includes(c));
|
|
3087
3906
|
}
|
|
3088
|
-
node.children = node.children.filter((c) => isFillNode(c));
|
|
3089
3907
|
}
|
|
3090
3908
|
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
|
-
});
|
|
3909
|
+
return isSpecialNode(n) && (n.tagName === "fill" || n.tagName === "filldef");
|
|
3128
3910
|
}
|
|
3129
|
-
|
|
3130
3911
|
//#endregion
|
|
3131
3912
|
//#region src/compile/parse/parseElement.ts
|
|
3132
3913
|
function parseElement(status) {
|
|
@@ -3138,16 +3919,40 @@ function parseElement(status) {
|
|
|
3138
3919
|
addError(status, `Non-matching close tag: ${element.tagName.substring(1)}`, start, start + element.tagName.length);
|
|
3139
3920
|
return element;
|
|
3140
3921
|
}
|
|
3141
|
-
if (
|
|
3142
|
-
if (!element.selfClosed && !
|
|
3922
|
+
if (voidTags.includes(element.tagName)) checkVoidElement(status, element);
|
|
3923
|
+
if (!element.selfClosed && !voidTags.includes(element.tagName)) {
|
|
3143
3924
|
status.stack.push(element);
|
|
3144
3925
|
while (status.i < status.source.length) {
|
|
3145
3926
|
addSpaceElement(element, status);
|
|
3146
3927
|
if (accept("}", status, false)) break;
|
|
3147
|
-
else if (accept("<!--", status))
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3928
|
+
else if (accept("<!--", status)) {
|
|
3929
|
+
const start = status.i;
|
|
3930
|
+
status.i = status.source.indexOf("-->", status.i) + 3;
|
|
3931
|
+
const text = {
|
|
3932
|
+
type: "comment",
|
|
3933
|
+
commentType: "html",
|
|
3934
|
+
content: status.source.substring(start, status.i - 3)
|
|
3935
|
+
};
|
|
3936
|
+
element.children.push(text);
|
|
3937
|
+
} else if (accept("@//", status)) {
|
|
3938
|
+
const start = status.i;
|
|
3939
|
+
status.i = status.source.indexOf("\n", status.i) + 1;
|
|
3940
|
+
const text = {
|
|
3941
|
+
type: "comment",
|
|
3942
|
+
commentType: "line",
|
|
3943
|
+
content: status.source.substring(start, status.i - 1)
|
|
3944
|
+
};
|
|
3945
|
+
element.children.push(text);
|
|
3946
|
+
} else if (accept("@/*", status)) {
|
|
3947
|
+
const start = status.i;
|
|
3948
|
+
status.i = status.source.indexOf("*/", status.i) + 2;
|
|
3949
|
+
const text = {
|
|
3950
|
+
type: "comment",
|
|
3951
|
+
commentType: "block",
|
|
3952
|
+
content: status.source.substring(start, status.i - 2)
|
|
3953
|
+
};
|
|
3954
|
+
element.children.push(text);
|
|
3955
|
+
} else if (accept("</", status, false)) {
|
|
3151
3956
|
if (checkCloseTag(status, element)) break;
|
|
3152
3957
|
} else if (accept("<", status, false)) {
|
|
3153
3958
|
const child = parseElement(status);
|
|
@@ -3166,10 +3971,8 @@ function parseElement(status) {
|
|
|
3166
3971
|
slottifyChildNodes(element);
|
|
3167
3972
|
current.slotProps ??= [];
|
|
3168
3973
|
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") {
|
|
3974
|
+
} else if (element.tagName === "fill" || element.tagName === "filldef") element.hasSlotProps = usesSlot(element.children);
|
|
3975
|
+
else if (element.tagName === "@component") {
|
|
3173
3976
|
const selfAttribute = element.attributes.find((a) => a.name === "self");
|
|
3174
3977
|
if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) {
|
|
3175
3978
|
element.type = "component";
|
|
@@ -3197,7 +4000,7 @@ function parseElement(status) {
|
|
|
3197
4000
|
} else addError(status, "@component element must have a self attribute", start, start + element.tagName.length + 1);
|
|
3198
4001
|
}
|
|
3199
4002
|
}
|
|
3200
|
-
if (!element.closed && !
|
|
4003
|
+
if (!element.closed && !voidTags.includes(element.tagName)) addError(status, `Unclosed non-void element: ${element.tagName}`, start, start + element.tagName.length + 1);
|
|
3201
4004
|
return element;
|
|
3202
4005
|
}
|
|
3203
4006
|
function checkVoidElement(status, element) {
|
|
@@ -3232,7 +4035,6 @@ function checkCloseTag(status, element) {
|
|
|
3232
4035
|
if (!found) addError(status, `Non-matching close tag: ${closingTagName} (expected ${element.tagName})`, start, start + element.tagName.length + 1);
|
|
3233
4036
|
return found;
|
|
3234
4037
|
}
|
|
3235
|
-
|
|
3236
4038
|
//#endregion
|
|
3237
4039
|
//#region src/compile/parse/parseControl.ts
|
|
3238
4040
|
const controlOperations = [
|
|
@@ -3244,7 +4046,8 @@ const controlOperations = [
|
|
|
3244
4046
|
"@case",
|
|
3245
4047
|
"@default",
|
|
3246
4048
|
"@await",
|
|
3247
|
-
"@
|
|
4049
|
+
"@with",
|
|
4050
|
+
"@try",
|
|
3248
4051
|
"@catch",
|
|
3249
4052
|
"@replace",
|
|
3250
4053
|
"@html",
|
|
@@ -3267,7 +4070,7 @@ function parseControl(status, parentNode) {
|
|
|
3267
4070
|
const node = parseControlOpen(status);
|
|
3268
4071
|
if (!node) return;
|
|
3269
4072
|
if (standaloneOperations.includes(node.operation)) {
|
|
3270
|
-
wrangleControlNode(node, parentNode);
|
|
4073
|
+
wrangleControlNode(node, parentNode, status);
|
|
3271
4074
|
return;
|
|
3272
4075
|
}
|
|
3273
4076
|
while (status.i < status.source.length) {
|
|
@@ -3286,7 +4089,7 @@ function parseControl(status, parentNode) {
|
|
|
3286
4089
|
break;
|
|
3287
4090
|
}
|
|
3288
4091
|
}
|
|
3289
|
-
wrangleControlNode(node, parentNode);
|
|
4092
|
+
wrangleControlNode(node, parentNode, status);
|
|
3290
4093
|
parseControlBranches(status, parentNode);
|
|
3291
4094
|
}
|
|
3292
4095
|
function parseControlOpen(status) {
|
|
@@ -3308,6 +4111,7 @@ function parseControlOpen(status) {
|
|
|
3308
4111
|
accept("}", status);
|
|
3309
4112
|
}
|
|
3310
4113
|
if (operation === "@replace" || operation === "@html") statement = trimMatched(statement.substring(statement.indexOf("(")).trim(), "(", ")");
|
|
4114
|
+
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
4115
|
} else {
|
|
3312
4116
|
addError(status, `Unknown operation: ${operation}`, status.i, status.i + operation.length);
|
|
3313
4117
|
return null;
|
|
@@ -3325,25 +4129,27 @@ function parseControlOpen(status) {
|
|
|
3325
4129
|
}
|
|
3326
4130
|
function parseControlStatement(start, operation, status) {
|
|
3327
4131
|
let parenCount = 0;
|
|
3328
|
-
while (status.i < status.source.length)
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
4132
|
+
while (status.i < status.source.length) {
|
|
4133
|
+
const skipped = skipStringOrComment(status.source, status.i);
|
|
4134
|
+
if (skipped !== -1) {
|
|
4135
|
+
status.i = skipped;
|
|
4136
|
+
continue;
|
|
4137
|
+
}
|
|
4138
|
+
if (accept("(", status)) parenCount += 1;
|
|
4139
|
+
else if (accept(")", status)) parenCount -= 1;
|
|
4140
|
+
else if (accept("{", status) || accept("\n", status) && standaloneOperations.includes(operation)) {
|
|
4141
|
+
if (parenCount === 0) return trimStart(status.source.substring(start, status.i - 1).trim(), "@");
|
|
4142
|
+
} else status.i += 1;
|
|
4143
|
+
}
|
|
3339
4144
|
return "";
|
|
3340
4145
|
}
|
|
3341
4146
|
/**
|
|
3342
4147
|
* Groups the control node into the correct parent e.g. if/else into an if group
|
|
3343
4148
|
* @param node The control node
|
|
3344
4149
|
* @param parentNode The parent node
|
|
4150
|
+
* @param status The parse status, for error reporting
|
|
3345
4151
|
*/
|
|
3346
|
-
function wrangleControlNode(node, parentNode) {
|
|
4152
|
+
function wrangleControlNode(node, parentNode, status) {
|
|
3347
4153
|
if (node.operation === "@if") {
|
|
3348
4154
|
const ifGroup = {
|
|
3349
4155
|
type: "control",
|
|
@@ -3392,14 +4198,41 @@ function wrangleControlNode(node, parentNode) {
|
|
|
3392
4198
|
}
|
|
3393
4199
|
};
|
|
3394
4200
|
parentNode.children.push(awaitGroup);
|
|
3395
|
-
} else if (node.operation === "@
|
|
4201
|
+
} else if (node.operation === "@with") for (let i = parentNode.children.length - 1; i >= 0; i--) {
|
|
3396
4202
|
const lastChild = parentNode.children[i];
|
|
3397
4203
|
if (isControlNode(lastChild) && lastChild.operation === "@await group") {
|
|
3398
4204
|
lastChild.children.push(node);
|
|
3399
4205
|
break;
|
|
3400
4206
|
}
|
|
3401
4207
|
}
|
|
3402
|
-
else if (node.operation === "@
|
|
4208
|
+
else if (node.operation === "@catch") {
|
|
4209
|
+
let attached = false;
|
|
4210
|
+
let duplicate = false;
|
|
4211
|
+
for (let i = parentNode.children.length - 1; i >= 0; i--) {
|
|
4212
|
+
const lastChild = parentNode.children[i];
|
|
4213
|
+
if (isControlNode(lastChild) && lastChild.operation === "@try group") {
|
|
4214
|
+
if (lastChild.children.some((child) => isControlNode(child) && child.operation === "@catch")) duplicate = true;
|
|
4215
|
+
else {
|
|
4216
|
+
lastChild.children.push(node);
|
|
4217
|
+
attached = true;
|
|
4218
|
+
}
|
|
4219
|
+
break;
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
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);
|
|
4223
|
+
} else if (node.operation === "@try") {
|
|
4224
|
+
const tryGroup = {
|
|
4225
|
+
type: "control",
|
|
4226
|
+
operation: "@try group",
|
|
4227
|
+
statement: "",
|
|
4228
|
+
children: [node],
|
|
4229
|
+
span: {
|
|
4230
|
+
start: 0,
|
|
4231
|
+
end: 0
|
|
4232
|
+
}
|
|
4233
|
+
};
|
|
4234
|
+
parentNode.children.push(tryGroup);
|
|
4235
|
+
} else if (node.operation === "@replace") {
|
|
3403
4236
|
const replaceGroup = {
|
|
3404
4237
|
type: "control",
|
|
3405
4238
|
operation: "@replace group",
|
|
@@ -3436,61 +4269,93 @@ function parseControlBranches(status, parentNode) {
|
|
|
3436
4269
|
let start = status.i;
|
|
3437
4270
|
status.i += 1;
|
|
3438
4271
|
consumeSpace(status);
|
|
3439
|
-
if (accept("else", status, false) || accept("case", status, false) || accept("default", status, false) || accept("
|
|
4272
|
+
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
4273
|
else {
|
|
3441
4274
|
gotAnotherBranch = false;
|
|
3442
4275
|
status.i = start;
|
|
3443
4276
|
}
|
|
3444
4277
|
}
|
|
3445
4278
|
}
|
|
3446
|
-
|
|
3447
4279
|
//#endregion
|
|
3448
4280
|
//#region src/compile/parse/parseMarkup.ts
|
|
3449
4281
|
function parseMarkup(status, source) {
|
|
3450
4282
|
const current = status.components.at(-1);
|
|
3451
4283
|
if (!current) return;
|
|
4284
|
+
parseMarkupInto(status, source, (root) => current.markup ??= root);
|
|
4285
|
+
}
|
|
4286
|
+
/**
|
|
4287
|
+
* Parses markup into a root node, letting callers choose where the resulting
|
|
4288
|
+
* RootNode is stored (e.g. a component's `markup` or its `error` block).
|
|
4289
|
+
*
|
|
4290
|
+
* @param status The parse status
|
|
4291
|
+
* @param source The full source code
|
|
4292
|
+
* @param setRoot Called with a fresh root node the first time content is
|
|
4293
|
+
* parsed; subsequent content is appended to the same node.
|
|
4294
|
+
*/
|
|
4295
|
+
function parseMarkupInto(status, source, setRoot) {
|
|
4296
|
+
let root = null;
|
|
4297
|
+
const getRoot = () => root ??= {
|
|
4298
|
+
type: "root",
|
|
4299
|
+
children: []
|
|
4300
|
+
};
|
|
3452
4301
|
while (status.i < source.length) if (accept("}", status, false)) return;
|
|
3453
|
-
else if (accept("<!--", status))
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
4302
|
+
else if (accept("<!--", status)) {
|
|
4303
|
+
const start = status.i;
|
|
4304
|
+
status.i = status.source.indexOf("-->", status.i) + 3;
|
|
4305
|
+
const text = {
|
|
4306
|
+
type: "comment",
|
|
4307
|
+
commentType: "html",
|
|
4308
|
+
content: status.source.substring(start, status.i - 3)
|
|
4309
|
+
};
|
|
4310
|
+
setRoot(getRoot());
|
|
4311
|
+
getRoot().children.push(text);
|
|
4312
|
+
} else if (accept("@//", status)) {
|
|
4313
|
+
const start = status.i;
|
|
4314
|
+
status.i = status.source.indexOf("\n", status.i) + 1;
|
|
4315
|
+
const text = {
|
|
4316
|
+
type: "comment",
|
|
4317
|
+
commentType: "line",
|
|
4318
|
+
content: status.source.substring(start, status.i - 1)
|
|
4319
|
+
};
|
|
4320
|
+
setRoot(getRoot());
|
|
4321
|
+
getRoot().children.push(text);
|
|
4322
|
+
} else if (accept("@/*", status)) {
|
|
4323
|
+
const start = status.i;
|
|
4324
|
+
status.i = status.source.indexOf("*/", status.i) + 2;
|
|
4325
|
+
const text = {
|
|
4326
|
+
type: "comment",
|
|
4327
|
+
commentType: "block",
|
|
4328
|
+
content: status.source.substring(start, status.i - 2)
|
|
3460
4329
|
};
|
|
4330
|
+
setRoot(getRoot());
|
|
4331
|
+
getRoot().children.push(text);
|
|
4332
|
+
} else if (isSpaceChar(status.source, status.i)) {
|
|
4333
|
+
setRoot(getRoot());
|
|
3461
4334
|
const text = {
|
|
3462
4335
|
type: "text",
|
|
3463
4336
|
content: consumeSpace(status),
|
|
3464
4337
|
spans: []
|
|
3465
4338
|
};
|
|
3466
|
-
|
|
4339
|
+
getRoot().children.push(text);
|
|
3467
4340
|
} else if (accept("<", status, false)) {
|
|
3468
|
-
|
|
3469
|
-
type: "root",
|
|
3470
|
-
children: []
|
|
3471
|
-
};
|
|
4341
|
+
setRoot(getRoot());
|
|
3472
4342
|
const element = parseElement(status);
|
|
3473
|
-
|
|
4343
|
+
getRoot().children.push(element);
|
|
3474
4344
|
} else if (accept("@", status, false)) {
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
children: []
|
|
3478
|
-
};
|
|
3479
|
-
parseControl(status, current.markup);
|
|
4345
|
+
setRoot(getRoot());
|
|
4346
|
+
parseControl(status, getRoot());
|
|
3480
4347
|
} else status.i += 1;
|
|
3481
4348
|
}
|
|
3482
|
-
|
|
3483
4349
|
//#endregion
|
|
3484
4350
|
//#region src/compile/utils/hash.ts
|
|
3485
4351
|
const returnRegex = /\r/g;
|
|
3486
4352
|
function hash(input) {
|
|
3487
4353
|
input = input.replace(returnRegex, "");
|
|
3488
|
-
let hash
|
|
4354
|
+
let hash = 5381;
|
|
3489
4355
|
let i = input.length;
|
|
3490
|
-
while (i--) hash
|
|
3491
|
-
return (hash
|
|
4356
|
+
while (i--) hash = (hash << 5) - hash ^ input.charCodeAt(i);
|
|
4357
|
+
return (hash >>> 0).toString(36);
|
|
3492
4358
|
}
|
|
3493
|
-
|
|
3494
4359
|
//#endregion
|
|
3495
4360
|
//#region src/compile/parse/utils/expect.ts
|
|
3496
4361
|
/**
|
|
@@ -3512,7 +4377,6 @@ function expect(value, status, advance = true) {
|
|
|
3512
4377
|
else addError(status, "Expected token");
|
|
3513
4378
|
return false;
|
|
3514
4379
|
}
|
|
3515
|
-
|
|
3516
4380
|
//#endregion
|
|
3517
4381
|
//#region src/compile/parse/parseStyles.ts
|
|
3518
4382
|
function parseStyles(source, status) {
|
|
@@ -3520,7 +4384,7 @@ function parseStyles(source, status) {
|
|
|
3520
4384
|
if (!current) return;
|
|
3521
4385
|
current.style = {
|
|
3522
4386
|
global: false,
|
|
3523
|
-
|
|
4387
|
+
children: [],
|
|
3524
4388
|
hash: hash(source)
|
|
3525
4389
|
};
|
|
3526
4390
|
const styleStatus = {
|
|
@@ -3528,6 +4392,7 @@ function parseStyles(source, status) {
|
|
|
3528
4392
|
i: 0,
|
|
3529
4393
|
marker: 0,
|
|
3530
4394
|
level: 0,
|
|
4395
|
+
braces: [],
|
|
3531
4396
|
imports: [],
|
|
3532
4397
|
script: [],
|
|
3533
4398
|
components: [],
|
|
@@ -3536,69 +4401,86 @@ function parseStyles(source, status) {
|
|
|
3536
4401
|
};
|
|
3537
4402
|
while (styleStatus.i < styleStatus.source.length) if (isSpaceChar(styleStatus.source, styleStatus.i)) consumeSpace(styleStatus);
|
|
3538
4403
|
else {
|
|
3539
|
-
const
|
|
3540
|
-
if (
|
|
4404
|
+
const node = parseStyleNode(styleStatus);
|
|
4405
|
+
if (node) current.style.children.push(node);
|
|
3541
4406
|
else break;
|
|
3542
4407
|
}
|
|
3543
4408
|
status.errors = status.errors.concat(styleStatus.errors);
|
|
3544
4409
|
}
|
|
3545
|
-
function
|
|
4410
|
+
function parseStyleNode(status) {
|
|
4411
|
+
const gapBefore = /\n\s*\n\s*$/.test(status.source.slice(0, status.i));
|
|
4412
|
+
if (accept("//", status, false) || accept("/*", status, false)) return parseComment(status, gapBefore);
|
|
3546
4413
|
const selector = consumeUntil("{", status).trim();
|
|
3547
4414
|
if (expect("{", status)) {
|
|
3548
4415
|
const block = {
|
|
4416
|
+
type: "block",
|
|
3549
4417
|
selector,
|
|
3550
|
-
|
|
3551
|
-
|
|
4418
|
+
children: [],
|
|
4419
|
+
gapBefore
|
|
3552
4420
|
};
|
|
3553
4421
|
consumeSpace(status);
|
|
3554
4422
|
while (status.source[status.i] !== "}") {
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
if (
|
|
3565
|
-
|
|
3566
|
-
|
|
4423
|
+
if (accept("//", status, false) || accept("/*", status, false)) block.children.push(parseComment(status, false));
|
|
4424
|
+
else {
|
|
4425
|
+
let nextColon = status.source.indexOf(":", status.i);
|
|
4426
|
+
if (nextColon === -1) nextColon = status.source.length;
|
|
4427
|
+
let nextOpenBrace = status.source.indexOf("{", status.i);
|
|
4428
|
+
if (nextOpenBrace === -1) nextOpenBrace = status.source.length;
|
|
4429
|
+
if (nextColon < nextOpenBrace) {
|
|
4430
|
+
const attribute = parseStyleAttribute(status);
|
|
4431
|
+
block.children.push(attribute);
|
|
4432
|
+
} else if (nextOpenBrace < nextColon) {
|
|
4433
|
+
const child = parseStyleNode(status);
|
|
4434
|
+
if (child) block.children.push(child);
|
|
4435
|
+
else break;
|
|
4436
|
+
} else return block;
|
|
4437
|
+
}
|
|
3567
4438
|
consumeSpace(status);
|
|
3568
4439
|
}
|
|
3569
4440
|
accept("}", status);
|
|
3570
4441
|
return block;
|
|
3571
4442
|
}
|
|
3572
4443
|
}
|
|
4444
|
+
function parseComment(status, gapBefore) {
|
|
4445
|
+
const start = status.i;
|
|
4446
|
+
if (accept("//", status)) {
|
|
4447
|
+
const end = status.source.indexOf("\n", status.i);
|
|
4448
|
+
status.i = end === -1 ? status.source.length : end;
|
|
4449
|
+
} else {
|
|
4450
|
+
accept("/*", status);
|
|
4451
|
+
const end = status.source.indexOf("*/", status.i);
|
|
4452
|
+
status.i = end === -1 ? status.source.length : end + 2;
|
|
4453
|
+
}
|
|
4454
|
+
return {
|
|
4455
|
+
type: "comment",
|
|
4456
|
+
content: status.source.substring(start, status.i),
|
|
4457
|
+
gapBefore
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
3573
4460
|
function parseStyleAttribute(status) {
|
|
4461
|
+
const gapBefore = /\n\s*\n\s*$/.test(status.source.slice(0, status.i));
|
|
3574
4462
|
const name = consumeUntil(":", status).trim();
|
|
3575
4463
|
accept(":", status);
|
|
3576
4464
|
const value = consumeUntil(";", status).trim();
|
|
3577
4465
|
accept(";", status);
|
|
3578
4466
|
return {
|
|
4467
|
+
type: "attribute",
|
|
3579
4468
|
name,
|
|
3580
|
-
value
|
|
4469
|
+
value,
|
|
4470
|
+
gapBefore
|
|
3581
4471
|
};
|
|
3582
4472
|
}
|
|
3583
|
-
|
|
3584
4473
|
//#endregion
|
|
3585
4474
|
//#region src/compile/utils/isComponentNode.ts
|
|
3586
4475
|
function isComponentNode(node) {
|
|
3587
4476
|
return node.type === "component";
|
|
3588
4477
|
}
|
|
3589
|
-
|
|
3590
|
-
//#endregion
|
|
3591
|
-
//#region src/compile/utils/isParentNode.ts
|
|
3592
|
-
function isParentNode(node) {
|
|
3593
|
-
return "children" in node;
|
|
3594
|
-
}
|
|
3595
|
-
|
|
3596
4478
|
//#endregion
|
|
3597
4479
|
//#region src/compile/parse/scopeStyles.ts
|
|
3598
4480
|
function scopeStyles(status) {
|
|
3599
4481
|
let selectors = [];
|
|
3600
4482
|
for (let component of status.components) if (component.style && component.markup) {
|
|
3601
|
-
for (let block of component.style.
|
|
4483
|
+
for (let block of component.style.children) if (block.type === "block") collectStyleSelectors(block, selectors);
|
|
3602
4484
|
scopeStylesOnNode(component.markup, selectors);
|
|
3603
4485
|
}
|
|
3604
4486
|
}
|
|
@@ -3614,9 +4496,8 @@ function scopeStylesOnNode(node, selectors) {
|
|
|
3614
4496
|
if (isParentNode(node)) for (let child of node.children) scopeStylesOnNode(child, selectors);
|
|
3615
4497
|
}
|
|
3616
4498
|
function collectStyleSelectors(block, selectors) {
|
|
3617
|
-
for (let s of block.selector.split(/[\s*,>+~]/)) selectors.push(s);
|
|
4499
|
+
for (let s of block.selector.split(/[\s*,>+~[\]]/)) selectors.push(s);
|
|
3618
4500
|
}
|
|
3619
|
-
|
|
3620
4501
|
//#endregion
|
|
3621
4502
|
//#region src/compile/parse/parseCode.ts
|
|
3622
4503
|
function parseCode(source) {
|
|
@@ -3625,16 +4506,24 @@ function parseCode(source) {
|
|
|
3625
4506
|
i: 0,
|
|
3626
4507
|
marker: 0,
|
|
3627
4508
|
level: 0,
|
|
4509
|
+
braces: [],
|
|
3628
4510
|
imports: [],
|
|
3629
4511
|
script: [],
|
|
3630
4512
|
components: [],
|
|
3631
4513
|
stack: [],
|
|
3632
4514
|
errors: []
|
|
3633
4515
|
};
|
|
3634
|
-
for (status.i; status.i < source.length; status.i++) if (consumeScriptComments(status)) {} else if (status.source[status.i] === "{")
|
|
3635
|
-
|
|
4516
|
+
for (status.i; status.i < source.length; status.i++) if (consumeScriptComments(status)) {} else if (status.source[status.i] === "{") {
|
|
4517
|
+
status.level += 1;
|
|
4518
|
+
status.braces.push(false);
|
|
4519
|
+
} else if (status.source[status.i] === "}") {
|
|
3636
4520
|
status.level -= 1;
|
|
3637
|
-
|
|
4521
|
+
const fromComponent = status.braces.pop();
|
|
4522
|
+
if (status.level === 0 && fromComponent) parseComponentEnd(status);
|
|
4523
|
+
else if (status.level < 0) {
|
|
4524
|
+
addError(status, `Unexpected closing brace`, status.i, status.i + 1);
|
|
4525
|
+
status.level = 0;
|
|
4526
|
+
}
|
|
3638
4527
|
} else if (accept("function", status)) {
|
|
3639
4528
|
if (consumeSpace(status)) if (/[A-Z]/.test(source[status.i])) parseComponentStart(status);
|
|
3640
4529
|
else status.i--;
|
|
@@ -3656,8 +4545,13 @@ function parseCode(source) {
|
|
|
3656
4545
|
if (consumeSpace(status) && accept("default", status) && consumeSpace(status) && accept("(", status, false)) parseComponentStart(status);
|
|
3657
4546
|
else status.i = start;
|
|
3658
4547
|
} else if (accept("@render", status, false)) parseComponentRender(status);
|
|
4548
|
+
else if (accept("@error", status, false)) parseComponentError(status);
|
|
3659
4549
|
else if (accept("@head", status, false)) parseComponentHead(status);
|
|
3660
4550
|
else if (accept("@style", status, false)) parseComponentStyle(status);
|
|
4551
|
+
if (status.level > 0) {
|
|
4552
|
+
const count = status.level;
|
|
4553
|
+
addError(status, `Unbalanced braces: missing ${count} closing brace${count === 1 ? "" : "s"}`, source.length, source.length);
|
|
4554
|
+
}
|
|
3661
4555
|
status.script.push({
|
|
3662
4556
|
script: source.substring(status.marker, source.length),
|
|
3663
4557
|
span: {
|
|
@@ -3684,13 +4578,33 @@ function parseComponentStart(status) {
|
|
|
3684
4578
|
let def = /export\s+default\s+(function\s+|const\s+)*$/.test(source);
|
|
3685
4579
|
let isConst = /const\s+$/.test(source);
|
|
3686
4580
|
let isAnon = /export default\s+$/.test(source);
|
|
4581
|
+
let componentStart = status.i;
|
|
4582
|
+
let name = consumeAlphaNumeric(status);
|
|
4583
|
+
if (def && status.components.some((c) => c.default)) addError(status, `Multiple default exports`, componentStart, componentStart + name.length);
|
|
3687
4584
|
const current = {
|
|
3688
|
-
start:
|
|
3689
|
-
name
|
|
4585
|
+
start: componentStart,
|
|
4586
|
+
name,
|
|
3690
4587
|
exported,
|
|
3691
|
-
default: def
|
|
4588
|
+
default: def,
|
|
4589
|
+
scriptStart: status.script.length
|
|
3692
4590
|
};
|
|
3693
4591
|
status.components.push(current);
|
|
4592
|
+
if (status.source[status.i] === "<") {
|
|
4593
|
+
const typeParamsStart = status.i;
|
|
4594
|
+
let level = 0;
|
|
4595
|
+
for (status.i; status.i < status.source.length; status.i++) {
|
|
4596
|
+
const char = status.source[status.i];
|
|
4597
|
+
if (char === "<") level += 1;
|
|
4598
|
+
else if (char === ">") {
|
|
4599
|
+
level -= 1;
|
|
4600
|
+
if (level === 0) {
|
|
4601
|
+
current.typeParams = status.source.substring(typeParamsStart, status.i + 1);
|
|
4602
|
+
status.i += 1;
|
|
4603
|
+
break;
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
3694
4608
|
let presource = source.substring(0, status.i).trimEnd();
|
|
3695
4609
|
let lastNewline = presource.lastIndexOf("\n");
|
|
3696
4610
|
if (lastNewline !== -1) {
|
|
@@ -3737,8 +4651,8 @@ function parseComponentStart(status) {
|
|
|
3737
4651
|
const paramsMatch = current.params?.match(/\$props:\s*([$A-Za-z0-9]+)/);
|
|
3738
4652
|
if (paramsMatch) {
|
|
3739
4653
|
let propsTypeName = paramsMatch[1];
|
|
3740
|
-
let propsTypeStart = status.source.search(
|
|
3741
|
-
if (propsTypeStart === -1) propsTypeStart = status.source.search(
|
|
4654
|
+
let propsTypeStart = status.source.search(new RegExp(`interface\\s+${propsTypeName}`));
|
|
4655
|
+
if (propsTypeStart === -1) propsTypeStart = status.source.search(new RegExp(`type\\s+${propsTypeName}`));
|
|
3742
4656
|
if (propsTypeStart !== -1) {
|
|
3743
4657
|
let bracesStart = status.source.indexOf("{", propsTypeStart);
|
|
3744
4658
|
if (bracesStart !== -1) {
|
|
@@ -3747,6 +4661,7 @@ function parseComponentStart(status) {
|
|
|
3747
4661
|
i: bracesStart + 1,
|
|
3748
4662
|
marker: 0,
|
|
3749
4663
|
level: 0,
|
|
4664
|
+
braces: [],
|
|
3750
4665
|
imports: [],
|
|
3751
4666
|
script: [],
|
|
3752
4667
|
components: [],
|
|
@@ -3767,6 +4682,7 @@ function parseComponentStart(status) {
|
|
|
3767
4682
|
}
|
|
3768
4683
|
accept("{", status);
|
|
3769
4684
|
status.level += 1;
|
|
4685
|
+
status.braces.push(true);
|
|
3770
4686
|
const postFunctionChunk = {
|
|
3771
4687
|
script: status.source.substring(status.marker, status.i),
|
|
3772
4688
|
span: {
|
|
@@ -3821,6 +4737,42 @@ function parseComponentRender(status) {
|
|
|
3821
4737
|
}
|
|
3822
4738
|
status.marker = status.i;
|
|
3823
4739
|
}
|
|
4740
|
+
function parseComponentError(status) {
|
|
4741
|
+
const current = status.components.at(-1);
|
|
4742
|
+
if (!current) return;
|
|
4743
|
+
if (current.error) addError(status, `Multiple @error sections`, status.i, status.i + 6);
|
|
4744
|
+
if (!current.markup) addError(status, `@error must come after @render`, status.i, status.i + 6);
|
|
4745
|
+
status.script.push({
|
|
4746
|
+
script: status.source.substring(status.marker, status.i),
|
|
4747
|
+
span: {
|
|
4748
|
+
start: status.marker,
|
|
4749
|
+
end: status.i
|
|
4750
|
+
}
|
|
4751
|
+
});
|
|
4752
|
+
status.script.push({
|
|
4753
|
+
script: "/* @error */",
|
|
4754
|
+
span: {
|
|
4755
|
+
start: 0,
|
|
4756
|
+
end: 0
|
|
4757
|
+
}
|
|
4758
|
+
});
|
|
4759
|
+
accept("@error", status);
|
|
4760
|
+
consumeSpace(status);
|
|
4761
|
+
let errorVar = "";
|
|
4762
|
+
if (accept("(", status)) {
|
|
4763
|
+
let start = status.i;
|
|
4764
|
+
while (status.i < status.source.length && status.source[status.i] !== ")") status.i += 1;
|
|
4765
|
+
errorVar = status.source.substring(start, status.i).trim();
|
|
4766
|
+
accept(")", status);
|
|
4767
|
+
consumeSpace(status);
|
|
4768
|
+
}
|
|
4769
|
+
current.errorVar = errorVar || "err";
|
|
4770
|
+
if (accept("{", status)) {
|
|
4771
|
+
parseMarkupInto(status, status.source, (root) => current.error ??= root);
|
|
4772
|
+
accept("}", status);
|
|
4773
|
+
}
|
|
4774
|
+
status.marker = status.i;
|
|
4775
|
+
}
|
|
3824
4776
|
function parseComponentHead(status) {
|
|
3825
4777
|
const current = status.components.at(-1);
|
|
3826
4778
|
if (!current) return;
|
|
@@ -3874,16 +4826,22 @@ function parseComponentStyle(status) {
|
|
|
3874
4826
|
for (; status.i < status.source.length; status.i++) {
|
|
3875
4827
|
const char = status.source[status.i];
|
|
3876
4828
|
const nextChar = status.source[status.i + 1];
|
|
3877
|
-
if (char === "/" && nextChar === "/")
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
4829
|
+
if (char === "/" && nextChar === "/") {
|
|
4830
|
+
let start = status.i;
|
|
4831
|
+
status.i = status.source.indexOf("\n", status.i);
|
|
4832
|
+
styleSource += status.source.substring(start, status.i + 1);
|
|
4833
|
+
} else if (char === "/" && nextChar === "*") {
|
|
4834
|
+
let start = status.i;
|
|
4835
|
+
status.i = status.source.indexOf("*/", status.i) + 1;
|
|
4836
|
+
styleSource += status.source.substring(start, status.i + 1);
|
|
4837
|
+
} else if (char === "\"" || char === "'") {
|
|
4838
|
+
let start = status.i;
|
|
4839
|
+
status.i = endOfString(char, status.source, start);
|
|
4840
|
+
styleSource += status.source.substring(start, status.i + 1);
|
|
3883
4841
|
} else if (char === "`") {
|
|
3884
|
-
let start
|
|
3885
|
-
status.i = endOfTemplateString(status.source, start
|
|
3886
|
-
styleSource += status.source.substring(start
|
|
4842
|
+
let start = status.i;
|
|
4843
|
+
status.i = endOfTemplateString(status.source, start);
|
|
4844
|
+
styleSource += status.source.substring(start, status.i + 1);
|
|
3887
4845
|
} else if (char === "{") {
|
|
3888
4846
|
level += 1;
|
|
3889
4847
|
if (start === -1) start = status.i + 1;
|
|
@@ -3918,49 +4876,80 @@ function parseComponentEnd(status) {
|
|
|
3918
4876
|
end: 0
|
|
3919
4877
|
}
|
|
3920
4878
|
});
|
|
3921
|
-
const
|
|
3922
|
-
|
|
3923
|
-
current.
|
|
4879
|
+
const scriptText = status.script.slice(current.scriptStart ?? status.script.length).map((chunk) => chunk.script).join("\n");
|
|
4880
|
+
const markupParts = [];
|
|
4881
|
+
if (current.markup) markupParts.push(collectMarkupExpressions(current.markup));
|
|
4882
|
+
if (current.error) markupParts.push(collectMarkupExpressions(current.error));
|
|
4883
|
+
if (current.head) markupParts.push(collectMarkupExpressions(current.head));
|
|
4884
|
+
const usageSource = scriptText + "\n" + markupParts.join("\n");
|
|
4885
|
+
const usageRanges = codeRanges(usageSource);
|
|
4886
|
+
current.props = getPropsUsage(usageSource, usageRanges);
|
|
4887
|
+
current.contextProps = getContextUsage(usageSource, usageRanges);
|
|
3924
4888
|
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
4889
|
status.marker = status.i;
|
|
3926
4890
|
}
|
|
3927
|
-
|
|
3928
|
-
|
|
4891
|
+
/**
|
|
4892
|
+
* Gets the `$props` names that the compiled output references. The input is
|
|
4893
|
+
* the component's code-only text (script plus markup expressions), with
|
|
4894
|
+
* `ranges` marking the positions that are code — matches that start inside a
|
|
4895
|
+
* string, comment or regex literal are ignored, so mentions inside
|
|
4896
|
+
* sample-code strings or prose don't count.
|
|
4897
|
+
*/
|
|
4898
|
+
function getPropsUsage(source, ranges) {
|
|
3929
4899
|
const props = [];
|
|
3930
|
-
for (let match of
|
|
4900
|
+
for (let match of source.matchAll(/\$props\??\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g)) {
|
|
4901
|
+
if (!isInCode(ranges, match.index)) continue;
|
|
3931
4902
|
const name = trimQuotes(match[1] || match[2]);
|
|
3932
4903
|
if (!props.includes(name)) props.push(name);
|
|
3933
4904
|
}
|
|
4905
|
+
if (!props.length) for (let match of source.matchAll(/(?<![\w$])\$props(?![\w])(?!\s*:)(?!<)/g)) {
|
|
4906
|
+
if (!isInCode(ranges, match.index)) continue;
|
|
4907
|
+
props.push("$props");
|
|
4908
|
+
break;
|
|
4909
|
+
}
|
|
3934
4910
|
return props.length ? props : void 0;
|
|
3935
4911
|
}
|
|
3936
|
-
|
|
3937
|
-
|
|
4912
|
+
/**
|
|
4913
|
+
* Gets the `$context` names that the compiled output references. The input is
|
|
4914
|
+
* the component's code-only text (script plus markup expressions), with
|
|
4915
|
+
* `ranges` marking the positions that are code — matches that start inside a
|
|
4916
|
+
* string, comment or regex literal are ignored, so mentions inside
|
|
4917
|
+
* sample-code strings or prose don't count.
|
|
4918
|
+
*/
|
|
4919
|
+
function getContextUsage(source, ranges) {
|
|
4920
|
+
const contextsMatches = source.matchAll(/\$context\??\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g);
|
|
3938
4921
|
const contexts = [];
|
|
3939
4922
|
for (let match of contextsMatches) {
|
|
4923
|
+
if (!isInCode(ranges, match.index)) continue;
|
|
3940
4924
|
const name = trimQuotes(match[1] || match[2]);
|
|
3941
4925
|
if (!contexts.includes(name)) contexts.push(name);
|
|
3942
4926
|
}
|
|
4927
|
+
if (!contexts.length) for (let match of source.matchAll(/(?<![\w$])\$context(?![\w])(?!\s*:)(?!<)/g)) {
|
|
4928
|
+
if (!isInCode(ranges, match.index)) continue;
|
|
4929
|
+
contexts.push("$context");
|
|
4930
|
+
break;
|
|
4931
|
+
}
|
|
3943
4932
|
return contexts.length ? contexts : void 0;
|
|
3944
4933
|
}
|
|
4934
|
+
/**
|
|
4935
|
+
* Returns true if `index` falls inside one of the (sorted, non-overlapping)
|
|
4936
|
+
* code ranges — i.e. not inside a string, comment or regex literal.
|
|
4937
|
+
*/
|
|
4938
|
+
function isInCode(ranges, index) {
|
|
4939
|
+
for (const [start, end] of ranges) {
|
|
4940
|
+
if (index >= start && index < end) return true;
|
|
4941
|
+
if (index < start) return false;
|
|
4942
|
+
}
|
|
4943
|
+
return false;
|
|
4944
|
+
}
|
|
3945
4945
|
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);
|
|
4946
|
+
const skipped = skipStringOrComment(status.source, status.i);
|
|
4947
|
+
if (skipped !== -1) {
|
|
4948
|
+
status.i = skipped;
|
|
3959
4949
|
return true;
|
|
3960
4950
|
}
|
|
3961
4951
|
return false;
|
|
3962
4952
|
}
|
|
3963
|
-
|
|
3964
4953
|
//#endregion
|
|
3965
4954
|
//#region src/compile/parse.ts
|
|
3966
4955
|
/**
|
|
@@ -3974,7 +4963,7 @@ function consumeScriptComments(status) {
|
|
|
3974
4963
|
function parse(source) {
|
|
3975
4964
|
return parseCode(source);
|
|
3976
4965
|
}
|
|
3977
|
-
|
|
3978
4966
|
//#endregion
|
|
3979
4967
|
export { build, buildType, parse };
|
|
4968
|
+
|
|
3980
4969
|
//# sourceMappingURL=compile.mjs.map
|