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