@torpor/view 0.3.0 → 0.3.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/CHANGELOG.md +6 -0
- package/dist/Component-C6Y3UwGs.d.cts +11 -0
- package/dist/Component-C6Y3UwGs.d.cts.map +1 -0
- package/dist/ServerSlotRender-COS6H8Yk.d.cts +41 -0
- package/dist/ServerSlotRender-COS6H8Yk.d.cts.map +1 -0
- package/dist/compile.cjs +3683 -0
- package/dist/compile.cjs.map +1 -0
- package/dist/compile.d.cts +210 -0
- package/dist/compile.d.cts.map +1 -0
- package/dist/formatText-DufG5l83.cjs +500 -0
- package/dist/formatText-DufG5l83.cjs.map +1 -0
- package/dist/index.cjs +906 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +411 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/ssr.cjs +40 -0
- package/dist/ssr.cjs.map +1 -0
- package/dist/ssr.d.cts +19 -0
- package/dist/ssr.d.cts.map +1 -0
- package/package.json +7 -4
- package/tsdown.config.ts +1 -1
package/dist/compile.cjs
ADDED
|
@@ -0,0 +1,3683 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/compile/utils/endOfTemplateString.ts
|
|
3
|
+
function endOfTemplateString(content, start) {
|
|
4
|
+
let level = 0;
|
|
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;
|
|
8
|
+
return content.length;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/compile/utils/Builder.ts
|
|
13
|
+
var Builder = class {
|
|
14
|
+
#text = "";
|
|
15
|
+
#space = 0;
|
|
16
|
+
#mapped = false;
|
|
17
|
+
lineMap = [];
|
|
18
|
+
constructor(mapped) {
|
|
19
|
+
this.#mapped = mapped === true;
|
|
20
|
+
}
|
|
21
|
+
prepend(text) {
|
|
22
|
+
this.#text = text + "\n" + this.#text;
|
|
23
|
+
}
|
|
24
|
+
append(text) {
|
|
25
|
+
if (this.#mapped) {
|
|
26
|
+
this.lineMap.push(this.#text.length);
|
|
27
|
+
for (let i$1 = 0; i$1 < text.length; i$1++) if (text[i$1] === "\n") this.lineMap.push(this.#text.length + i$1);
|
|
28
|
+
this.#text += "\n" + text;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const start = this.#text.length;
|
|
32
|
+
text = text.trim();
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (i < text.length) {
|
|
35
|
+
while (text[i] === " " || text[i] === " ") i++;
|
|
36
|
+
if (text[i] === ";") i++;
|
|
37
|
+
else if (text[i] === "\n") {
|
|
38
|
+
this.lineMap.push(start + i);
|
|
39
|
+
this.#text += "\n";
|
|
40
|
+
} else {
|
|
41
|
+
if (text[i] === "}" || text[i] === ")" || text[i] === "]") this.#space -= 1;
|
|
42
|
+
this.#text += " ".repeat(Math.max(0, this.#space));
|
|
43
|
+
const start$1 = i;
|
|
44
|
+
while (i < text.length && text[i] !== "\n") {
|
|
45
|
+
if (text[i] === "`") i = endOfTemplateString(text, i);
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
const line = text.substring(start$1, i + 1);
|
|
49
|
+
if (line.startsWith("*")) this.#text += " ";
|
|
50
|
+
this.#text += line;
|
|
51
|
+
if (text[i - 1] === "{" || text[i - 1] === "(" || text[i - 1] === "[") this.#space += 1;
|
|
52
|
+
}
|
|
53
|
+
i++;
|
|
54
|
+
}
|
|
55
|
+
if (!this.#text.endsWith("\n\n")) this.#text += "\n";
|
|
56
|
+
}
|
|
57
|
+
toString() {
|
|
58
|
+
return this.#text;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/compile/build/client/addMappedText.ts
|
|
64
|
+
function addMappedText(preText, text, postText, range, status, b) {
|
|
65
|
+
if (status.options?.mapped) {
|
|
66
|
+
let start = b.toString().length + preText.length;
|
|
67
|
+
b.append(preText + text + postText);
|
|
68
|
+
let end = start + text.length - postText.length;
|
|
69
|
+
status.map.push({
|
|
70
|
+
script: text,
|
|
71
|
+
source: range,
|
|
72
|
+
compiled: {
|
|
73
|
+
start,
|
|
74
|
+
end
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
} else b.append(preText + text + postText);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/compile/types/comments.ts
|
|
82
|
+
const ANCHOR_COMMENT = "<!>";
|
|
83
|
+
const HYDRATION_START_COMMENT = "<![>";
|
|
84
|
+
const HYDRATION_END_COMMENT = "<!]>";
|
|
85
|
+
const HYDRATION_BREAK_COMMENT = "<!^>";
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/compile/utils/isReactive.ts
|
|
89
|
+
/**
|
|
90
|
+
* Checks whether the supplied string is fully or partially reactive (i.e. contains braces)
|
|
91
|
+
*
|
|
92
|
+
* @param text The string to check
|
|
93
|
+
*
|
|
94
|
+
* @returns True if the supplied string is fully or partially reactive
|
|
95
|
+
*/
|
|
96
|
+
function isReactive(text) {
|
|
97
|
+
return text.includes("{") && text.includes("}");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/compile/utils/trimQuotes.ts
|
|
102
|
+
/**
|
|
103
|
+
* Trims matching quotes from the start and end of the supplied text
|
|
104
|
+
*
|
|
105
|
+
* @param text The text to trim
|
|
106
|
+
*
|
|
107
|
+
* @returns The text without the quotes at the start or the end
|
|
108
|
+
*/
|
|
109
|
+
function trimQuotes(text) {
|
|
110
|
+
let start = 0;
|
|
111
|
+
let end = text.length;
|
|
112
|
+
while (text[start] === "'" && text[end - 1] === "'" || text[start] === "\"" && text[end - 1] === "\"" || text[start] === "`" && text[end - 1] === "`") {
|
|
113
|
+
start += 1;
|
|
114
|
+
end -= 1;
|
|
115
|
+
}
|
|
116
|
+
return text.substring(start, end);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/compile/build/client/buildFragmentText.ts
|
|
121
|
+
/**
|
|
122
|
+
* Builds the HTML template text for all of the fragments in a component
|
|
123
|
+
*/
|
|
124
|
+
function buildFragmentText(node, status, b) {
|
|
125
|
+
const fragments = [];
|
|
126
|
+
buildNodeFragmentText(node, status, fragments);
|
|
127
|
+
if (fragments.length) b.append(`const t_fragments: DocumentFragment[] = [];`);
|
|
128
|
+
}
|
|
129
|
+
function buildNodeFragmentText(node, status, fragments, currentFragment) {
|
|
130
|
+
switch (node.type) {
|
|
131
|
+
case "root":
|
|
132
|
+
buildRootFragmentText(node, status, fragments);
|
|
133
|
+
break;
|
|
134
|
+
case "control":
|
|
135
|
+
buildControlFragmentText(node, status, fragments, currentFragment);
|
|
136
|
+
break;
|
|
137
|
+
case "component":
|
|
138
|
+
buildComponentFragmentText(node, status, fragments, currentFragment);
|
|
139
|
+
break;
|
|
140
|
+
case "element": {
|
|
141
|
+
let ns = node.tagName === "svg";
|
|
142
|
+
if (ns) status.ns = true;
|
|
143
|
+
buildElementFragmentText(node, status, fragments, currentFragment);
|
|
144
|
+
if (ns) status.ns = false;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "text": {
|
|
148
|
+
const content = node.content;
|
|
149
|
+
if (currentFragment) currentFragment.text += isReactive(content) ? "#" : content;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "special":
|
|
153
|
+
buildSpecialFragmentText(node, status, fragments, currentFragment);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function buildRootFragmentText(node, status, fragments) {
|
|
158
|
+
node.fragment = {
|
|
159
|
+
number: fragments.length,
|
|
160
|
+
text: "",
|
|
161
|
+
ns: status.ns,
|
|
162
|
+
effects: [],
|
|
163
|
+
events: [],
|
|
164
|
+
animations: []
|
|
165
|
+
};
|
|
166
|
+
fragments.push(node.fragment);
|
|
167
|
+
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
168
|
+
}
|
|
169
|
+
function buildControlFragmentText(node, status, fragments, currentFragment) {
|
|
170
|
+
switch (node.operation) {
|
|
171
|
+
case "@if group":
|
|
172
|
+
case "@switch group":
|
|
173
|
+
case "@for group":
|
|
174
|
+
case "@await group":
|
|
175
|
+
case "@replace group":
|
|
176
|
+
case "@html group":
|
|
177
|
+
currentFragment.text += ANCHOR_COMMENT;
|
|
178
|
+
for (let child of node.children) buildNodeFragmentText(child, status, fragments, currentFragment);
|
|
179
|
+
break;
|
|
180
|
+
default:
|
|
181
|
+
node.fragment = {
|
|
182
|
+
number: fragments.length,
|
|
183
|
+
text: "",
|
|
184
|
+
ns: status.ns,
|
|
185
|
+
effects: [],
|
|
186
|
+
events: [],
|
|
187
|
+
animations: []
|
|
188
|
+
};
|
|
189
|
+
fragments.push(node.fragment);
|
|
190
|
+
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function buildComponentFragmentText(node, status, fragments, currentFragment) {
|
|
195
|
+
currentFragment.text += ANCHOR_COMMENT;
|
|
196
|
+
if (node.children.length) {
|
|
197
|
+
node.fragment = {
|
|
198
|
+
number: fragments.length,
|
|
199
|
+
text: "",
|
|
200
|
+
ns: status.ns,
|
|
201
|
+
effects: [],
|
|
202
|
+
events: [],
|
|
203
|
+
animations: []
|
|
204
|
+
};
|
|
205
|
+
fragments.push(node.fragment);
|
|
206
|
+
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function buildElementFragmentText(node, status, fragments, currentFragment) {
|
|
210
|
+
let needsClass = node.scopeStyles;
|
|
211
|
+
currentFragment.text += `<${node.tagName}`;
|
|
212
|
+
let attributesText = node.attributes.filter((a) => !a.name.startsWith("on")).map((a) => {
|
|
213
|
+
if (a.value && a.reactive) {
|
|
214
|
+
if (a.name.startsWith("data-")) return `${a.name}=""`;
|
|
215
|
+
} else if (a.name === "class" && node.scopeStyles) {
|
|
216
|
+
needsClass = false;
|
|
217
|
+
let value = a.value ? `"${trimQuotes(a.value)} torp-${status.styleHash}"` : `"torp-${status.styleHash}"`;
|
|
218
|
+
return `${a.name}=${value}`;
|
|
219
|
+
} else return `${a.name}${a.value != null ? `=${a.value}` : ""}`;
|
|
220
|
+
}).filter(Boolean).join(" ");
|
|
221
|
+
if (attributesText) currentFragment.text += " " + attributesText;
|
|
222
|
+
if (needsClass) currentFragment.text += ` class="torp-${status.styleHash}"`;
|
|
223
|
+
currentFragment.text += ">";
|
|
224
|
+
for (let child of node.children) buildNodeFragmentText(child, status, fragments, currentFragment);
|
|
225
|
+
currentFragment.text += `</${node.tagName}>`;
|
|
226
|
+
}
|
|
227
|
+
function buildSpecialFragmentText(node, status, fragments, currentFragment) {
|
|
228
|
+
switch (node.tagName) {
|
|
229
|
+
case "slot":
|
|
230
|
+
currentFragment.text += ANCHOR_COMMENT;
|
|
231
|
+
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
232
|
+
break;
|
|
233
|
+
case "fill":
|
|
234
|
+
node.fragment = {
|
|
235
|
+
number: fragments.length,
|
|
236
|
+
text: "",
|
|
237
|
+
ns: status.ns,
|
|
238
|
+
effects: [],
|
|
239
|
+
events: [],
|
|
240
|
+
animations: []
|
|
241
|
+
};
|
|
242
|
+
fragments.push(node.fragment);
|
|
243
|
+
for (let child of node.children) buildNodeFragmentText(child, status, fragments, node.fragment);
|
|
244
|
+
break;
|
|
245
|
+
case "@element":
|
|
246
|
+
buildComponentFragmentText(node, status, fragments, currentFragment);
|
|
247
|
+
break;
|
|
248
|
+
case "@component":
|
|
249
|
+
buildComponentFragmentText(node, status, fragments, currentFragment);
|
|
250
|
+
break;
|
|
251
|
+
default: throw new Error(`Invalid special node: ${node.tagName}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/compile/utils/isSpecialNode.ts
|
|
257
|
+
function isSpecialNode(node) {
|
|
258
|
+
return node.type === "special";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/compile/build/utils/nextVarName.ts
|
|
263
|
+
/**
|
|
264
|
+
* Makes a supplied variable name (hopefully!) unique by prepending `t_` and
|
|
265
|
+
* appending a sequence number
|
|
266
|
+
*
|
|
267
|
+
* @param name The variable name
|
|
268
|
+
* @param status The build status
|
|
269
|
+
*
|
|
270
|
+
* @returns A unique variable name
|
|
271
|
+
*/
|
|
272
|
+
function nextVarName(name, status) {
|
|
273
|
+
if (!status.varNames[name]) status.varNames[name] = 1;
|
|
274
|
+
let varName = `t_${name}_${status.varNames[name]}`;
|
|
275
|
+
status.varNames[name] += 1;
|
|
276
|
+
return varName;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/compile/build/client/buildAddFragment.ts
|
|
281
|
+
function buildAddFragment(node, status, b, parentName, anchorName) {
|
|
282
|
+
if (node.fragment) {
|
|
283
|
+
const fragment = node.fragment;
|
|
284
|
+
const fragmentName = `t_fragment_${fragment.number}`;
|
|
285
|
+
status.imports.add("t_add_fragment");
|
|
286
|
+
if (fragment.effects.length > 0) {
|
|
287
|
+
status.imports.add("$run");
|
|
288
|
+
b.append(`$run(function setAttributes() {`);
|
|
289
|
+
if (status.options?.mapped) for (let effect of fragment.effects) for (let i = 0; i < effect.ranges.length; i++) {
|
|
290
|
+
let start = b.toString().length + effect.offsets[i];
|
|
291
|
+
b.append(effect.functionBody);
|
|
292
|
+
let end = start + effect.lengths[i];
|
|
293
|
+
status.map.push({
|
|
294
|
+
script: effect.functionBody,
|
|
295
|
+
source: effect.ranges[i],
|
|
296
|
+
compiled: {
|
|
297
|
+
start,
|
|
298
|
+
end
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
else b.append(fragment.effects.map((e) => e.functionBody).join("\n"));
|
|
303
|
+
b.append("});");
|
|
304
|
+
}
|
|
305
|
+
let params = [
|
|
306
|
+
fragmentName,
|
|
307
|
+
parentName,
|
|
308
|
+
anchorName
|
|
309
|
+
];
|
|
310
|
+
if (fragment.endVarName) params.push(fragment.endVarName);
|
|
311
|
+
b.append(`t_add_fragment(${params.join(", ")});`);
|
|
312
|
+
if (fragment.endVarName) {
|
|
313
|
+
status.imports.add("t_next");
|
|
314
|
+
b.append(`t_next(${fragment.endVarName});`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/compile/parse/utils/isSpaceChar.ts
|
|
321
|
+
/**
|
|
322
|
+
* Checks whether the supplied character is a space
|
|
323
|
+
*
|
|
324
|
+
* @param text The string to check
|
|
325
|
+
* @param index The index of the character in the string
|
|
326
|
+
*
|
|
327
|
+
* @returns True if the supplied character is a space
|
|
328
|
+
*/
|
|
329
|
+
function isSpaceChar(text, index) {
|
|
330
|
+
let code = text.charCodeAt(index);
|
|
331
|
+
return code === 32 || code >= 9 && code <= 13;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/compile/parse/utils/isSpace.ts
|
|
336
|
+
/**
|
|
337
|
+
* Checks whether the supplied string contains only space characters
|
|
338
|
+
*
|
|
339
|
+
* @param text The string to check
|
|
340
|
+
*
|
|
341
|
+
* @returns True if the supplied string contains only space characters
|
|
342
|
+
*/
|
|
343
|
+
function isSpace(text) {
|
|
344
|
+
for (let i = 0; i < text.length; i++) if (!isSpaceChar(text, i)) return false;
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region src/compile/utils/isControlNode.ts
|
|
350
|
+
function isControlNode(node) {
|
|
351
|
+
return node.type === "control";
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/compile/utils/isElementNode.ts
|
|
356
|
+
function isElementNode(node) {
|
|
357
|
+
return node.type === "element";
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
//#endregion
|
|
361
|
+
//#region src/compile/utils/isTextNode.ts
|
|
362
|
+
function isTextNode(node) {
|
|
363
|
+
return node.type === "text";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/compile/build/client/buildFragment.ts
|
|
368
|
+
/**
|
|
369
|
+
* Builds the variables and code in a fragment
|
|
370
|
+
*/
|
|
371
|
+
function buildFragment(node, status, b, parentName, anchorName) {
|
|
372
|
+
if (node.fragment) {
|
|
373
|
+
const fragment = node.fragment;
|
|
374
|
+
const fragmentName = `t_fragment_${fragment.number}`;
|
|
375
|
+
if (status.options?.useCreateElement) {
|
|
376
|
+
let fragmentPath = {
|
|
377
|
+
parent: null,
|
|
378
|
+
type: "fragment",
|
|
379
|
+
children: []
|
|
380
|
+
};
|
|
381
|
+
let varPaths = /* @__PURE__ */ new Map();
|
|
382
|
+
declareFragmentVars(node.fragment, node, fragmentPath, status, b, parentName, anchorName, varPaths, false, true);
|
|
383
|
+
fragmentPath.children.length = 0;
|
|
384
|
+
varPaths.clear();
|
|
385
|
+
status.imports.add("t_frg");
|
|
386
|
+
b.append(`const ${fragmentName} = t_frg([`);
|
|
387
|
+
declareFragmentVars(node.fragment, node, fragmentPath, status, b, parentName, anchorName, varPaths, false, false);
|
|
388
|
+
b.append(`]);`);
|
|
389
|
+
} else {
|
|
390
|
+
status.imports.add("t_fragment");
|
|
391
|
+
const fragmentText = fragment.text.replaceAll("`", "\\`").replaceAll(/\s+/g, " ");
|
|
392
|
+
b.append(`const ${fragmentName} = t_fragment($parent.ownerDocument!, t_fragments, ${fragment.number}, \`${fragmentText}\`${fragment.ns ? ", true" : ""});`);
|
|
393
|
+
let fragmentPath = {
|
|
394
|
+
parent: null,
|
|
395
|
+
type: "fragment",
|
|
396
|
+
children: []
|
|
397
|
+
};
|
|
398
|
+
let varPaths = /* @__PURE__ */ new Map();
|
|
399
|
+
maybeAddRootNodeDeclaration(node, fragment, fragmentName, status, b, varPaths);
|
|
400
|
+
declareFragmentVars(node.fragment, node, fragmentPath, status, b, parentName, anchorName, varPaths, false, true);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function maybeAddRootNodeDeclaration(node, fragment, fragmentName, status, b, varPaths) {
|
|
405
|
+
if (!node.children.length) return;
|
|
406
|
+
let firstNode = node.children[0];
|
|
407
|
+
let firstNodeIsLastNode = node.children.length === 1;
|
|
408
|
+
if (isControlNode(firstNode) && firstNode.operation.endsWith(" group") || isElementNode(firstNode) && elementNodeNeedsDeclaration(firstNode, true, firstNodeIsLastNode) || isTextNode(firstNode) && textNodeNeedsDeclaration(firstNode, true, firstNodeIsLastNode)) {} else {
|
|
409
|
+
status.imports.add("t_root");
|
|
410
|
+
const rootName = `t_root_${fragment.number}`;
|
|
411
|
+
const params = [fragmentName];
|
|
412
|
+
if (firstNode.type === "text") params.push("true");
|
|
413
|
+
const rootPath = `t_root(${params.join(", ")})`;
|
|
414
|
+
b.append(`const ${rootName} = ${rootPath};`);
|
|
415
|
+
varPaths.set(`t_root(${fragmentName})`, rootName);
|
|
416
|
+
printDebug(rootName, status, b);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function declareFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare) {
|
|
420
|
+
switch (node.type) {
|
|
421
|
+
case "root":
|
|
422
|
+
declareRootFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, declare);
|
|
423
|
+
break;
|
|
424
|
+
case "control":
|
|
425
|
+
declareControlFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, declare);
|
|
426
|
+
break;
|
|
427
|
+
case "component":
|
|
428
|
+
declareComponentFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, declare);
|
|
429
|
+
break;
|
|
430
|
+
case "element":
|
|
431
|
+
declareElementFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare);
|
|
432
|
+
break;
|
|
433
|
+
case "text":
|
|
434
|
+
declareTextFragmentVars(fragment, node, path, status, b, varPaths, lastChild, declare);
|
|
435
|
+
break;
|
|
436
|
+
case "special":
|
|
437
|
+
declareSpecialFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare);
|
|
438
|
+
break;
|
|
439
|
+
default: throw new Error(`Invalid node type: ${node.type}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function declareRootFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, declare) {
|
|
443
|
+
for (let [i, child] of node.children.entries()) declareFragmentVars(fragment, child, path, status, b, parentName, anchorName, varPaths, i === node.children.length - 1, declare);
|
|
444
|
+
}
|
|
445
|
+
function declareControlFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, declare) {
|
|
446
|
+
switch (node.operation) {
|
|
447
|
+
case "@if group":
|
|
448
|
+
case "@switch group":
|
|
449
|
+
case "@for group":
|
|
450
|
+
case "@await group":
|
|
451
|
+
case "@replace group":
|
|
452
|
+
case "@html group": {
|
|
453
|
+
const operation = node.operation.substring(1).replace(" group", "");
|
|
454
|
+
declareParentAndAnchorFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, operation, declare);
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
default:
|
|
458
|
+
for (let [i, child] of node.children.entries()) declareFragmentVars(fragment, child, path, status, b, parentName, anchorName, varPaths, i === node.children.length - 1, declare);
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function declareComponentFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, declare) {
|
|
463
|
+
declareParentAndAnchorFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, "comp", declare);
|
|
464
|
+
}
|
|
465
|
+
function elementTypeName(node) {
|
|
466
|
+
switch (node.tagName.toLowerCase()) {
|
|
467
|
+
case "div":
|
|
468
|
+
case "input": return `HTML${node.tagName.substring(0, 1).toUpperCase() + node.tagName.substring(1)}Element`;
|
|
469
|
+
case "textarea": return "HTMLTextAreaElement";
|
|
470
|
+
}
|
|
471
|
+
return "HTMLElement";
|
|
472
|
+
}
|
|
473
|
+
function declareElementFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare) {
|
|
474
|
+
const tagName = node.tagName === "@element" ? "element" : node.tagName;
|
|
475
|
+
let elementPath = {
|
|
476
|
+
parent: path,
|
|
477
|
+
type: tagName,
|
|
478
|
+
children: []
|
|
479
|
+
};
|
|
480
|
+
path.children.push(elementPath);
|
|
481
|
+
const topLevel = !path.parent;
|
|
482
|
+
const declareVariable = elementNodeNeedsDeclaration(node, topLevel, lastChild);
|
|
483
|
+
if (declare) {
|
|
484
|
+
if (declareVariable) {
|
|
485
|
+
node.varName = nextVarName(tagName, status);
|
|
486
|
+
if (topLevel) fragment.endVarName = node.varName;
|
|
487
|
+
const varPath = getFragmentVarPath(fragment, status, node.varName, elementPath, varPaths);
|
|
488
|
+
if (status.options?.useCreateElement) b.append(`let ${node.varName};`);
|
|
489
|
+
else if (node.tagName === "@element") b.append(`let ${node.varName} = ${varPath} as ${elementTypeName(node)};`);
|
|
490
|
+
else {
|
|
491
|
+
b.append(`const ${node.varName} = ${varPath} as ${elementTypeName(node)};`);
|
|
492
|
+
printDebug(node.varName, status, b);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
let childParentName = node.children.find((c) => !!c.parentName)?.parentName;
|
|
497
|
+
if (!declare) {
|
|
498
|
+
let attributes = node.attributes.filter((a) => !a.name.startsWith("on")).map((a) => {
|
|
499
|
+
if (a.value && a.reactive) return `"${a.name}": "#"`;
|
|
500
|
+
else if (a.value) return `"${a.name}": "${trimQuotes(a.value) || a.name}"`;
|
|
501
|
+
else return `"${a.name}": true"`;
|
|
502
|
+
}).join(", ");
|
|
503
|
+
status.imports.add("t_elm");
|
|
504
|
+
if (declareVariable) b.append(`(${childParentName ? `${childParentName} = ` : ""}${node.varName} = t_elm("${node.tagName}", {${attributes}}, [`);
|
|
505
|
+
else if (childParentName) b.append(`(${childParentName} = t_elm("${node.tagName}", {${attributes}}, [`);
|
|
506
|
+
else b.append(`t_elm("${node.tagName}", {${attributes}}, [`);
|
|
507
|
+
}
|
|
508
|
+
for (let [i, child] of node.children.entries()) declareFragmentVars(fragment, child, elementPath, status, b, parentName, anchorName, varPaths, i === node.children.length - 1, declare);
|
|
509
|
+
if (!declare) if (declareVariable || childParentName) b.append("])),");
|
|
510
|
+
else b.append("]),");
|
|
511
|
+
}
|
|
512
|
+
function elementNodeNeedsDeclaration(node, topLevel, lastChild) {
|
|
513
|
+
const hasReactiveAttribute = node.attributes.some((a) => a.value && a.reactive);
|
|
514
|
+
const isDynamicElement = node.tagName === "@element" && node.attributes.find((a) => a.name === "self");
|
|
515
|
+
return hasReactiveAttribute || isDynamicElement || topLevel && lastChild;
|
|
516
|
+
}
|
|
517
|
+
function declareTextFragmentVars(fragment, node, path, status, b, varPaths, lastChild, declare) {
|
|
518
|
+
const lastType = path.children[path.children.length - 1]?.type;
|
|
519
|
+
if (lastType === "text" || lastType === "space") return;
|
|
520
|
+
let textPath = {
|
|
521
|
+
parent: path,
|
|
522
|
+
type: isSpace(node.content) ? "space" : "text",
|
|
523
|
+
children: []
|
|
524
|
+
};
|
|
525
|
+
path.children.push(textPath);
|
|
526
|
+
const topLevel = !path.parent;
|
|
527
|
+
const declareVariable = textNodeNeedsDeclaration(node, topLevel, lastChild);
|
|
528
|
+
if (declare) {
|
|
529
|
+
if (declareVariable) {
|
|
530
|
+
node.varName = nextVarName("text", status);
|
|
531
|
+
if (topLevel) fragment.endVarName = node.varName;
|
|
532
|
+
const varPath = getFragmentVarPath(fragment, status, node.varName, textPath, varPaths);
|
|
533
|
+
if (status.options?.useCreateElement) b.append(`let ${node.varName};`);
|
|
534
|
+
else {
|
|
535
|
+
b.append(`const ${node.varName} = ${varPath};`);
|
|
536
|
+
printDebug(node.varName, status, b);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
} else {
|
|
540
|
+
status.imports.add("t_txt");
|
|
541
|
+
if (declareVariable) b.append(`(${node.varName} = t_txt(" ")),`);
|
|
542
|
+
else b.append(`t_txt("${node.content}"),`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function textNodeNeedsDeclaration(node, topLevel, lastChild) {
|
|
546
|
+
return isReactive(node.content) || topLevel && lastChild;
|
|
547
|
+
}
|
|
548
|
+
function declareSpecialFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare) {
|
|
549
|
+
if (node.tagName === "slot") declareParentAndAnchorFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, "slot", declare);
|
|
550
|
+
switch (node.tagName) {
|
|
551
|
+
case "slot":
|
|
552
|
+
case "fill":
|
|
553
|
+
for (let [i, child] of node.children.entries()) {
|
|
554
|
+
if (node.tagName === "slot" && isSpecialNode(child) && child.tagName === "fill") continue;
|
|
555
|
+
declareFragmentVars(fragment, child, path, status, b, parentName, anchorName, varPaths, i === node.children.length - 1, declare);
|
|
556
|
+
}
|
|
557
|
+
break;
|
|
558
|
+
case "@element":
|
|
559
|
+
declareElementFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, lastChild, declare);
|
|
560
|
+
break;
|
|
561
|
+
case "@component":
|
|
562
|
+
declareComponentFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, declare);
|
|
563
|
+
break;
|
|
564
|
+
default: throw new Error(`Invalid special node: ${node.tagName}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function declareParentAndAnchorFragmentVars(fragment, node, path, status, b, parentName, anchorName, varPaths, name, declare) {
|
|
568
|
+
if (!path.parent) node.parentName = `t_fragment_${fragment.number}`;
|
|
569
|
+
else {
|
|
570
|
+
const parentPath = path;
|
|
571
|
+
const oldChildren = parentPath.children;
|
|
572
|
+
parentPath.children = [];
|
|
573
|
+
const parentVarPath = getFragmentVarPath(fragment, status, "?", parentPath, varPaths);
|
|
574
|
+
parentPath.children = oldChildren;
|
|
575
|
+
if (varPaths.has(parentVarPath)) node.parentName = varPaths.get(parentVarPath);
|
|
576
|
+
else if (declare) {
|
|
577
|
+
node.parentName = nextVarName(`${name}_parent`, status);
|
|
578
|
+
if (status.options?.useCreateElement) b.append(`let ${node.parentName} as HTMLElement;`);
|
|
579
|
+
else {
|
|
580
|
+
b.append(`const ${node.parentName} = ${parentVarPath} as HTMLElement;`);
|
|
581
|
+
printDebug(node.parentName, status, b);
|
|
582
|
+
}
|
|
583
|
+
varPaths.set(parentVarPath, node.parentName);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (declare) {
|
|
587
|
+
const anchorPath = {
|
|
588
|
+
parent: path,
|
|
589
|
+
type: "#",
|
|
590
|
+
children: []
|
|
591
|
+
};
|
|
592
|
+
path.children.push(anchorPath);
|
|
593
|
+
node.varName = nextVarName(`${name}_anchor`, status);
|
|
594
|
+
const anchorVarPath = getFragmentVarPath(fragment, status, node.varName, anchorPath, varPaths);
|
|
595
|
+
if (status.options?.useCreateElement) b.append(`let ${node.varName};`);
|
|
596
|
+
else {
|
|
597
|
+
status.imports.add("t_anchor");
|
|
598
|
+
b.append(`let ${node.varName} = t_anchor(${anchorVarPath}) as HTMLElement;`);
|
|
599
|
+
printDebug(node.varName, status, b);
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
status.imports.add("t_cmt");
|
|
603
|
+
b.append(`(${node.varName} = t_cmt()),`);
|
|
604
|
+
}
|
|
605
|
+
buildNode(node, status, b, parentName, anchorName);
|
|
606
|
+
node.handled = true;
|
|
607
|
+
}
|
|
608
|
+
function getFragmentVarPath(fragment, status, name, path, varPaths) {
|
|
609
|
+
let node = path;
|
|
610
|
+
while (node.parent) node = node.parent;
|
|
611
|
+
let varName = `t_fragment_${fragment.number}`;
|
|
612
|
+
let varPath = getFragmentVarPathPart(node, varName, status, true);
|
|
613
|
+
for (let [existingPath, existingName] of varPaths) if (varPath.includes(existingPath)) varPath = varPath.replace(existingPath, existingName);
|
|
614
|
+
if (name !== "?") {
|
|
615
|
+
varPaths.set(varPath, name);
|
|
616
|
+
varPaths.set(name, name);
|
|
617
|
+
}
|
|
618
|
+
while (varPath.includes("t_next(t_next(")) {
|
|
619
|
+
status.imports.add("t_skip");
|
|
620
|
+
const match = varPath.match(/(t_next\(){2,}/)[0];
|
|
621
|
+
const count = match.length / 7;
|
|
622
|
+
const pos = varPath.indexOf("t_next(t_next(");
|
|
623
|
+
const head = varPath.substring(0, pos);
|
|
624
|
+
let level = 0;
|
|
625
|
+
let end = 0;
|
|
626
|
+
for (let i = pos + match.length; i < varPath.length; i++) if (varPath[i] === "(") level++;
|
|
627
|
+
else if (varPath[i] === ")") if (level === 0) {
|
|
628
|
+
end = i;
|
|
629
|
+
break;
|
|
630
|
+
} else level--;
|
|
631
|
+
const tail = varPath.substring(end + count);
|
|
632
|
+
const param = varPath.substring(pos + match.length, end);
|
|
633
|
+
varPath = `${head}t_skip(${param}, ${count})${tail}`;
|
|
634
|
+
}
|
|
635
|
+
varPath = varPath.replaceAll("t_next_text", "t_next");
|
|
636
|
+
return varPath;
|
|
637
|
+
}
|
|
638
|
+
function getFragmentVarPathPart(path, varPath, status, root = false) {
|
|
639
|
+
if (root) {
|
|
640
|
+
status.imports.add("t_root");
|
|
641
|
+
varPath = `t_root(${varPath})`;
|
|
642
|
+
} else {
|
|
643
|
+
status.imports.add("t_child");
|
|
644
|
+
varPath = `t_child(${varPath})`;
|
|
645
|
+
}
|
|
646
|
+
for (let [i, child] of path.children.entries()) {
|
|
647
|
+
if (i > 0) if (child.type === "text" || child.type === "space") varPath = `t_next_text(${varPath}, true)`;
|
|
648
|
+
else {
|
|
649
|
+
status.imports.add("t_next");
|
|
650
|
+
varPath = `t_next(${varPath})`;
|
|
651
|
+
}
|
|
652
|
+
if (i === path.children.length - 1 && child.children.length) varPath = getFragmentVarPathPart(child, varPath, status);
|
|
653
|
+
}
|
|
654
|
+
return varPath;
|
|
655
|
+
}
|
|
656
|
+
function printDebug(varName, status, b) {}
|
|
657
|
+
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/compile/build/client/replaceForVarNames.ts
|
|
660
|
+
/**
|
|
661
|
+
* Variables used within a for loop need to be retrieved from the loop data.
|
|
662
|
+
*/
|
|
663
|
+
function replaceForVarNames(value, status) {
|
|
664
|
+
for (let varName of status.forVarNames) value = value.replaceAll(new RegExp(`(^|\\s|\\(|\\[|\\{)${varName}($|\\s|\\.|\\(|\\)|\\[|\\]|\\}|;)`, "g"), `$1t_item.data.${varName}$2`);
|
|
665
|
+
return value;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
//#endregion
|
|
669
|
+
//#region src/compile/build/client/buildRun.ts
|
|
670
|
+
function buildRun(functionName, functionBody, status, b) {
|
|
671
|
+
functionBody = replaceForVarNames(functionBody, status);
|
|
672
|
+
status.imports.add("$run");
|
|
673
|
+
b.append(`$run(function ${functionName}() {`);
|
|
674
|
+
b.append(functionBody);
|
|
675
|
+
b.append("});");
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
//#endregion
|
|
679
|
+
//#region src/compile/build/client/buildComponentNode.ts
|
|
680
|
+
function buildComponentNode(node, status, b, root = false) {
|
|
681
|
+
b.append("");
|
|
682
|
+
b.append("/* @component */");
|
|
683
|
+
const componentHasProps = node.attributes.length || root;
|
|
684
|
+
const propsName = componentHasProps ? nextVarName("props", status) : "undefined";
|
|
685
|
+
if (componentHasProps) {
|
|
686
|
+
status.imports.add("$watch");
|
|
687
|
+
b.append(`const ${propsName}: any = $watch({});`);
|
|
688
|
+
for (let { name, value, reactive, fullyReactive } of node.attributes) if (name === "self" && node.tagName === "@component") {} else if (name.startsWith("&") && value != null && fullyReactive) {
|
|
689
|
+
name = name.substring(1);
|
|
690
|
+
buildRun("setProp", `${propsName}["${name}"] = ${value};`, status, b);
|
|
691
|
+
buildRun("setBinding", `${value} = ${propsName}["${name}"];`, status, b);
|
|
692
|
+
} else if (name === "class" && value != null) {
|
|
693
|
+
status.imports.add("t_class");
|
|
694
|
+
const params = [value];
|
|
695
|
+
if (node.scopeStyles) params.push(`"torp-${status.styleHash}"`);
|
|
696
|
+
buildRun("setClasses", `${propsName}["class"] = t_class(${params.join(", ")});`, status, b);
|
|
697
|
+
} else if (name === "style" && value != null) {
|
|
698
|
+
status.imports.add("t_style");
|
|
699
|
+
buildRun("setStyles", `${propsName}["style"] = t_style(${value});`, status, b);
|
|
700
|
+
} else if (value != null) {
|
|
701
|
+
const setProp = `${propsName}["${name}"] = ${value};`;
|
|
702
|
+
if (reactive) buildRun("setProp", setProp, status, b);
|
|
703
|
+
else b.append(setProp);
|
|
704
|
+
} else b.append(`${propsName}["${name}"] = true;`);
|
|
705
|
+
if (root) {
|
|
706
|
+
status.imports.add("$run");
|
|
707
|
+
b.append(`
|
|
708
|
+
if ($props) {
|
|
709
|
+
const propNames = [${status.props.map((p) => `'${p}'`).join(", ")}];
|
|
710
|
+
for (let name of Object.keys($props)) {
|
|
711
|
+
if (!name.startsWith("$") && !propNames.includes(name)) {
|
|
712
|
+
$run(() => ${propsName}[name] = $props[name]);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const componentHasSlots = node.children.length;
|
|
719
|
+
const slotsName = componentHasSlots ? nextVarName("slots", status) : "undefined";
|
|
720
|
+
if (componentHasSlots) {
|
|
721
|
+
b.append(`const ${slotsName}: Record<string, SlotRender> = {};`);
|
|
722
|
+
for (let slot of node.children) if (isSpecialNode(slot) && slot.tagName === "fill") {
|
|
723
|
+
const nameAttribute = slot.attributes.find((a) => a.name === "name");
|
|
724
|
+
const slotName = nameAttribute?.value ? trimQuotes(nameAttribute.value) : "_";
|
|
725
|
+
const slotParams = [
|
|
726
|
+
"$sparent: ParentNode",
|
|
727
|
+
"$sanchor: Node | null",
|
|
728
|
+
`${slot.hasSlotProps ? "$sprops" : "_$sprops?"}: Record<PropertyKey, any>`,
|
|
729
|
+
"// @ts-ignore\n$context?: Record<PropertyKey, any>"
|
|
730
|
+
];
|
|
731
|
+
b.append(`${slotsName}["${slotName}"] = (\n${slotParams.join(",\n")}\n) => {`);
|
|
732
|
+
buildFragment(slot, status, b, "$sparent", "$sanchor");
|
|
733
|
+
status.fragmentStack.push({
|
|
734
|
+
fragment: slot.fragment,
|
|
735
|
+
path: ""
|
|
736
|
+
});
|
|
737
|
+
for (let child of slot.children) buildNode(child, status, b, "$sparent", "$sanchor");
|
|
738
|
+
status.fragmentStack.pop();
|
|
739
|
+
buildAddFragment(slot, status, b, "$sparent", "$sanchor");
|
|
740
|
+
b.append(`}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const compParentName = node.parentName;
|
|
744
|
+
const compAnchorName = node.varName;
|
|
745
|
+
let renderParams = `${compParentName}, ${compAnchorName}, ${propsName}, $context`;
|
|
746
|
+
if (slotsName !== "undefined") renderParams += `, ${slotsName}`;
|
|
747
|
+
let componentName = node.tagName;
|
|
748
|
+
if (componentName === "@component") {
|
|
749
|
+
let selfAttribute = node.attributes.find((a) => a.name === "self");
|
|
750
|
+
if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) componentName = selfAttribute.value;
|
|
751
|
+
}
|
|
752
|
+
addMappedText("", `${componentName}(${renderParams})`, ";", node.range, status, b);
|
|
753
|
+
b.append("");
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
//#endregion
|
|
757
|
+
//#region src/compile/utils/trimMatched.ts
|
|
758
|
+
/**
|
|
759
|
+
* Trims chars from the start and end of the supplied text when both the start and end match
|
|
760
|
+
*
|
|
761
|
+
* @param text The text to trim
|
|
762
|
+
*
|
|
763
|
+
* @returns The text without the chars at the start or the end
|
|
764
|
+
*/
|
|
765
|
+
function trimMatched(text, startChar, endChar) {
|
|
766
|
+
let start = 0;
|
|
767
|
+
let end = text.length;
|
|
768
|
+
while (text[start] === startChar && text[end - 1] === endChar) {
|
|
769
|
+
start += 1;
|
|
770
|
+
end -= 1;
|
|
771
|
+
}
|
|
772
|
+
return text.substring(start, end);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region src/compile/build/client/buildAwaitNode.ts
|
|
777
|
+
function buildAwaitNode(node, status, b) {
|
|
778
|
+
const anchorName = node.varName;
|
|
779
|
+
const parentName = node.parentName;
|
|
780
|
+
const rangeName = nextVarName("await_range", status);
|
|
781
|
+
const stateName = "$" + nextVarName("await_state", status);
|
|
782
|
+
const awaitVarsName = nextVarName("await_vars", status);
|
|
783
|
+
const branches = node.children.filter((n) => isControlNode(n));
|
|
784
|
+
let awaitBranch = branches.find((n) => n.operation === "@await");
|
|
785
|
+
if (!awaitBranch) {}
|
|
786
|
+
let thenBranch = branches.find((n) => n.operation === "@then");
|
|
787
|
+
if (!thenBranch) thenBranch = {
|
|
788
|
+
type: "control",
|
|
789
|
+
operation: "@then",
|
|
790
|
+
statement: "then",
|
|
791
|
+
children: [],
|
|
792
|
+
range: {
|
|
793
|
+
start: 0,
|
|
794
|
+
end: 0
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
let catchBranch = branches.find((n) => n.operation === "@catch");
|
|
798
|
+
if (!catchBranch) catchBranch = {
|
|
799
|
+
type: "control",
|
|
800
|
+
operation: "@catch",
|
|
801
|
+
statement: "catch",
|
|
802
|
+
children: [],
|
|
803
|
+
range: {
|
|
804
|
+
start: 0,
|
|
805
|
+
end: 0
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
const awaiterName = trimMatched(awaitBranch.statement.substring(5).trim(), "(", ")");
|
|
809
|
+
const thenVar = trimMatched(thenBranch.statement.substring(4).trim(), "(", ")");
|
|
810
|
+
const catchVar = trimMatched(catchBranch.statement.substring(5).trim(), "(", ")");
|
|
811
|
+
status.imports.add("$watch");
|
|
812
|
+
status.imports.add("$run");
|
|
813
|
+
status.imports.add("t_range");
|
|
814
|
+
status.imports.add("t_run_control");
|
|
815
|
+
status.imports.add("t_run_branch");
|
|
816
|
+
b.append("");
|
|
817
|
+
b.append(`
|
|
818
|
+
/* @await */
|
|
819
|
+
const ${rangeName} = t_range();
|
|
820
|
+
let ${stateName} = $watch({ creator: (_: Node | null) => {} });`);
|
|
821
|
+
b.append(`
|
|
822
|
+
let ${awaitVarsName} = { t_token: 0 };
|
|
823
|
+
$run(function runAwait() {`);
|
|
824
|
+
buildAwaitBranch(awaitBranch, status, b, parentName, stateName);
|
|
825
|
+
b.append(`${awaitVarsName}.t_token++;
|
|
826
|
+
((t_token) => {`);
|
|
827
|
+
awaitBranch.range.start += 7;
|
|
828
|
+
addMappedText("", replaceForVarNames(awaiterName, status), "", awaitBranch.range, status, b);
|
|
829
|
+
thenBranch.range.start -= 1;
|
|
830
|
+
addMappedText(".then((", replaceForVarNames(thenVar, status), ") => {", thenBranch.range, status, b);
|
|
831
|
+
b.append(`if (t_token === ${awaitVarsName}.t_token) {`);
|
|
832
|
+
buildAwaitBranch(thenBranch, status, b, parentName, stateName);
|
|
833
|
+
b.append(`}
|
|
834
|
+
})`);
|
|
835
|
+
catchBranch.range.start -= 1;
|
|
836
|
+
addMappedText(".catch((", replaceForVarNames(catchVar, status), ") => {", catchBranch.range, status, b);
|
|
837
|
+
b.append(`
|
|
838
|
+
if (t_token === ${awaitVarsName}.t_token) {`);
|
|
839
|
+
buildAwaitBranch(catchBranch, status, b, parentName, stateName);
|
|
840
|
+
b.append(`}
|
|
841
|
+
});
|
|
842
|
+
})(${awaitVarsName}.t_token);
|
|
843
|
+
});`);
|
|
844
|
+
b.append(`
|
|
845
|
+
t_run_control(${rangeName}, ${anchorName}, (t_before) => {
|
|
846
|
+
t_run_branch(${rangeName}, () => ${stateName}.creator(t_before));
|
|
847
|
+
});`);
|
|
848
|
+
b.append("");
|
|
849
|
+
}
|
|
850
|
+
function buildAwaitBranch(node, status, b, parentName, stateName) {
|
|
851
|
+
if (node.children.length > 0) {
|
|
852
|
+
b.append(`${stateName}.creator = (t_before) => {`);
|
|
853
|
+
buildFragment(node, status, b, parentName, "t_before");
|
|
854
|
+
status.fragmentStack.push({
|
|
855
|
+
fragment: node.fragment,
|
|
856
|
+
path: ""
|
|
857
|
+
});
|
|
858
|
+
for (let child of node.children) buildNode(child, status, b, parentName, "t_before");
|
|
859
|
+
status.fragmentStack.pop();
|
|
860
|
+
buildAddFragment(node, status, b, parentName, "t_before");
|
|
861
|
+
b.append("};");
|
|
862
|
+
} else b.append(`${stateName}.creator = (_) => {};`);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
//#endregion
|
|
866
|
+
//#region src/compile/utils/trimEnd.ts
|
|
867
|
+
/**
|
|
868
|
+
* Trims a char from the end of the supplied text
|
|
869
|
+
*
|
|
870
|
+
* @param text The text to trim
|
|
871
|
+
* @param char The char to remove from the end of the text
|
|
872
|
+
*
|
|
873
|
+
* @returns The text without the char on the end
|
|
874
|
+
*/
|
|
875
|
+
function trimEnd(text, char) {
|
|
876
|
+
let end = text.length;
|
|
877
|
+
while (text[end - 1] === char) end -= 1;
|
|
878
|
+
return text.substring(0, end);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
//#endregion
|
|
882
|
+
//#region src/compile/build/client/buildForNode.ts
|
|
883
|
+
const forLoopRegex = /for\s*\((.+?);.*?;.*?\)/;
|
|
884
|
+
const forLoopVarsRegex = /(?:let\s+|var\s+){0,1}([^\s,;+=]+)(?:\s*=\s*[^,;]+){0,1}/g;
|
|
885
|
+
const forOfRegex = /for\s*\(\s*(?:let\s*|var\s*){0,1}(.+?)\s+(?:of|in).*?\)/;
|
|
886
|
+
function buildForNode(node, status, b) {
|
|
887
|
+
const forParentName = node.parentName;
|
|
888
|
+
const forAnchorName = node.varName;
|
|
889
|
+
node = node.children[0];
|
|
890
|
+
const forVarNames = [];
|
|
891
|
+
const forIndexMatch = node.statement.match(forLoopRegex);
|
|
892
|
+
if (forIndexMatch) {
|
|
893
|
+
const forVarMatches = forIndexMatch[1].matchAll(forLoopVarsRegex);
|
|
894
|
+
for (let match of forVarMatches) forVarNames.push(match[1]);
|
|
895
|
+
} else {
|
|
896
|
+
const forOfMatch = node.statement.match(forOfRegex);
|
|
897
|
+
if (forOfMatch) {
|
|
898
|
+
const match = forOfMatch[1];
|
|
899
|
+
if (match.startsWith("{") && match.endsWith("}") || match.startsWith("[") && match.endsWith("]")) forVarNames.push(...trimMatched(trimMatched(match, "{", "}"), "[", "]").split(",").map((m) => m.trim()));
|
|
900
|
+
else forVarNames.push(match);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
const forRangeName = nextVarName("for_range", status);
|
|
904
|
+
const key = node.children.find((n) => n.type === "control" && n.operation === "@key");
|
|
905
|
+
let keyStatement = key ? key.statement : "";
|
|
906
|
+
if (keyStatement) keyStatement = trimEnd(keyStatement.substring(keyStatement.indexOf("=") + 1).trim(), ";");
|
|
907
|
+
status.imports.add("t_range");
|
|
908
|
+
status.imports.add("t_run_list");
|
|
909
|
+
status.imports.add("t_list_item");
|
|
910
|
+
status.imports.add("ListItem");
|
|
911
|
+
b.append("");
|
|
912
|
+
b.append(`
|
|
913
|
+
/* @for */
|
|
914
|
+
let ${forRangeName} = t_range();
|
|
915
|
+
t_run_list(
|
|
916
|
+
${forRangeName},
|
|
917
|
+
${forParentName},
|
|
918
|
+
${forAnchorName},
|
|
919
|
+
function createNewItems() {
|
|
920
|
+
let t_new_items: ListItem[] = [];
|
|
921
|
+
let t_previous_item = ${forRangeName};
|
|
922
|
+
let t_next_item = ${forRangeName}.nextRange;`);
|
|
923
|
+
addMappedText("", `${node.statement}`, " {", node.range, status, b);
|
|
924
|
+
b.append(`
|
|
925
|
+
let t_new_item = t_list_item({ ${forVarNames.join(",\n")} }${keyStatement ? `, ${keyStatement}` : ""});
|
|
926
|
+
t_new_item.previousRange = t_previous_item;
|
|
927
|
+
t_previous_item.nextRange = t_new_item;
|
|
928
|
+
t_previous_item = t_new_item;
|
|
929
|
+
t_new_items.push(t_new_item);
|
|
930
|
+
}
|
|
931
|
+
${forRangeName}.nextRange = t_next_item;
|
|
932
|
+
return t_new_items;
|
|
933
|
+
},
|
|
934
|
+
function createListItem(t_item, t_before) {`);
|
|
935
|
+
status.forVarNames = forVarNames;
|
|
936
|
+
buildForItem(node, status, b, forParentName);
|
|
937
|
+
status.forVarNames = [];
|
|
938
|
+
b.append(`},
|
|
939
|
+
function updateListItem(t_old_item, t_new_item) {`);
|
|
940
|
+
for (let varName of forVarNames) b.append(`t_old_item.data.${varName} = t_new_item.data.${varName};`);
|
|
941
|
+
b.append(`}
|
|
942
|
+
);`);
|
|
943
|
+
b.append("");
|
|
944
|
+
}
|
|
945
|
+
function buildForItem(node, status, b, parentName) {
|
|
946
|
+
const oldRangeName = nextVarName("old_range", status);
|
|
947
|
+
status.imports.add("t_push_range");
|
|
948
|
+
b.append(`let ${oldRangeName} = t_push_range(t_item);`);
|
|
949
|
+
buildFragment(node, status, b, parentName, "t_before");
|
|
950
|
+
status.fragmentStack.push({
|
|
951
|
+
fragment: node.fragment,
|
|
952
|
+
path: ""
|
|
953
|
+
});
|
|
954
|
+
for (let child of node.children) {
|
|
955
|
+
if (isControlNode(child) && child.operation === "@key") continue;
|
|
956
|
+
buildNode(child, status, b, parentName, "t_before");
|
|
957
|
+
}
|
|
958
|
+
status.fragmentStack.pop();
|
|
959
|
+
buildAddFragment(node, status, b, parentName, "t_before");
|
|
960
|
+
status.imports.add("t_pop_range");
|
|
961
|
+
b.append(`t_pop_range(${oldRangeName});`);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region src/compile/build/client/buildHtmlNode.ts
|
|
966
|
+
function buildHtmlNode(node, status, b) {
|
|
967
|
+
const htmlAnchorName = node.varName;
|
|
968
|
+
const htmlParentName = node.parentName || htmlAnchorName + ".parentNode";
|
|
969
|
+
const htmlRangeName = nextVarName("html_range", status);
|
|
970
|
+
node = node.children[0];
|
|
971
|
+
status.imports.add("t_range");
|
|
972
|
+
status.imports.add("t_run_control");
|
|
973
|
+
b.append("");
|
|
974
|
+
b.append(`
|
|
975
|
+
/* @html */
|
|
976
|
+
const ${htmlRangeName} = t_range();
|
|
977
|
+
t_run_control(${htmlRangeName}, ${htmlAnchorName}, (t_before) => {`);
|
|
978
|
+
buildHtmlBranch(node, status, b, htmlParentName, htmlRangeName);
|
|
979
|
+
b.append("});");
|
|
980
|
+
b.append("");
|
|
981
|
+
}
|
|
982
|
+
function buildHtmlBranch(node, status, b, parentName, rangeName) {
|
|
983
|
+
status.imports.add("t_run_branch");
|
|
984
|
+
b.append(`${node.statement};`);
|
|
985
|
+
b.append(`t_run_branch(${rangeName}, () => {`);
|
|
986
|
+
const templateName = nextVarName("template", status);
|
|
987
|
+
const fragmentName = `t_fragment_${node.fragment.number}`;
|
|
988
|
+
b.append(`const ${templateName} = document.createElement("template");`);
|
|
989
|
+
b.append(`${templateName}.innerHTML = ${node.statement};`);
|
|
990
|
+
b.append(`let ${fragmentName} = ${templateName}.content.cloneNode(true) as DocumentFragment;`);
|
|
991
|
+
buildAddFragment(node, status, b, parentName, "t_before");
|
|
992
|
+
b.append(`});`);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
//#endregion
|
|
996
|
+
//#region src/compile/build/client/buildIfNode.ts
|
|
997
|
+
function buildIfNode(node, status, b) {
|
|
998
|
+
const anchorName = node.varName;
|
|
999
|
+
const parentName = node.parentName || anchorName + ".parentNode";
|
|
1000
|
+
const rangeName = nextVarName("if_range", status);
|
|
1001
|
+
const stateName = "$" + nextVarName("if_state", status);
|
|
1002
|
+
const branches = node.children.filter((n) => isControlNode(n));
|
|
1003
|
+
if (branches.findIndex((n) => n.operation === "@else") === -1) branches.push({
|
|
1004
|
+
type: "control",
|
|
1005
|
+
operation: "@else",
|
|
1006
|
+
statement: "else",
|
|
1007
|
+
children: [],
|
|
1008
|
+
range: {
|
|
1009
|
+
start: 0,
|
|
1010
|
+
end: 0
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
status.imports.add("$watch");
|
|
1014
|
+
status.imports.add("$run");
|
|
1015
|
+
status.imports.add("t_range");
|
|
1016
|
+
status.imports.add("t_run_control");
|
|
1017
|
+
status.imports.add("t_run_branch");
|
|
1018
|
+
b.append("");
|
|
1019
|
+
b.append(`
|
|
1020
|
+
/* @if */
|
|
1021
|
+
const ${rangeName} = t_range();
|
|
1022
|
+
let ${stateName} = $watch({ creator: (_: Node | null) => {} });`);
|
|
1023
|
+
b.append(`$run(function runIf() {`);
|
|
1024
|
+
for (let branch of branches) buildIfBranch(branch, status, b, parentName, stateName);
|
|
1025
|
+
b.append(`});`);
|
|
1026
|
+
b.append(`
|
|
1027
|
+
t_run_control(${rangeName}, ${anchorName}, (t_before) => {
|
|
1028
|
+
t_run_branch(${rangeName}, () => ${stateName}.creator(t_before));
|
|
1029
|
+
});`);
|
|
1030
|
+
b.append("");
|
|
1031
|
+
}
|
|
1032
|
+
function buildIfBranch(node, status, b, parentName, stateName) {
|
|
1033
|
+
addMappedText("", `${replaceForVarNames(node.statement, status)}`, " {", node.range, status, b);
|
|
1034
|
+
if (node.children.length > 0) {
|
|
1035
|
+
b.append(`${stateName}.creator = (t_before) => {`);
|
|
1036
|
+
buildFragment(node, status, b, parentName, "t_before");
|
|
1037
|
+
status.fragmentStack.push({
|
|
1038
|
+
fragment: node.fragment,
|
|
1039
|
+
path: ""
|
|
1040
|
+
});
|
|
1041
|
+
for (let child of node.children) buildNode(child, status, b, parentName, "t_before");
|
|
1042
|
+
status.fragmentStack.pop();
|
|
1043
|
+
buildAddFragment(node, status, b, parentName, "t_before");
|
|
1044
|
+
b.append("};");
|
|
1045
|
+
} else b.append(`${stateName}.creator = (_) => {};`);
|
|
1046
|
+
b.append("}");
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
//#endregion
|
|
1050
|
+
//#region src/compile/build/client/buildReplaceNode.ts
|
|
1051
|
+
function buildReplaceNode(node, status, b) {
|
|
1052
|
+
const replaceAnchorName = node.varName;
|
|
1053
|
+
const replaceParentName = node.parentName || replaceAnchorName + ".parentNode";
|
|
1054
|
+
const replaceRangeName = nextVarName("replace_range", status);
|
|
1055
|
+
node = node.children[0];
|
|
1056
|
+
status.imports.add("t_range");
|
|
1057
|
+
status.imports.add("t_run_control");
|
|
1058
|
+
b.append("");
|
|
1059
|
+
b.append(`
|
|
1060
|
+
/* @replace */
|
|
1061
|
+
const ${replaceRangeName} = t_range();
|
|
1062
|
+
t_run_control(${replaceRangeName}, ${replaceAnchorName}, (t_before) => {`);
|
|
1063
|
+
buildReplaceBranch(node, status, b, replaceParentName, replaceRangeName);
|
|
1064
|
+
b.append("});");
|
|
1065
|
+
b.append("");
|
|
1066
|
+
}
|
|
1067
|
+
function buildReplaceBranch(node, status, b, parentName, rangeName) {
|
|
1068
|
+
status.imports.add("t_run_branch");
|
|
1069
|
+
b.append(`${replaceForVarNames(node.statement, status)};`);
|
|
1070
|
+
b.append(`t_run_branch(${rangeName}, () => {`);
|
|
1071
|
+
buildFragment(node, status, b, parentName, "t_before");
|
|
1072
|
+
status.fragmentStack.push({
|
|
1073
|
+
fragment: node.fragment,
|
|
1074
|
+
path: ""
|
|
1075
|
+
});
|
|
1076
|
+
for (let child of node.children) buildNode(child, status, b, parentName, "t_before");
|
|
1077
|
+
status.fragmentStack.pop();
|
|
1078
|
+
buildAddFragment(node, status, b, parentName, "t_before");
|
|
1079
|
+
b.append(`});`);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
//#endregion
|
|
1083
|
+
//#region src/compile/build/client/buildScriptNode.ts
|
|
1084
|
+
function buildScriptNode(node, status, b) {
|
|
1085
|
+
b.append(`/* ${node.operation} */`);
|
|
1086
|
+
addMappedText("", `${maybeAppend$1(node.statement, ";")}`, "", node.range, status, b);
|
|
1087
|
+
}
|
|
1088
|
+
function maybeAppend$1(text, end) {
|
|
1089
|
+
if (!text.endsWith(end)) text += end;
|
|
1090
|
+
return text;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
//#endregion
|
|
1094
|
+
//#region src/compile/build/client/buildSwitchNode.ts
|
|
1095
|
+
function buildSwitchNode(node, status, b) {
|
|
1096
|
+
const anchorName = node.varName;
|
|
1097
|
+
const parentName = node.parentName;
|
|
1098
|
+
const rangeName = nextVarName("switch_range", status);
|
|
1099
|
+
const stateName = "$" + nextVarName("switch_state", status);
|
|
1100
|
+
const branches = node.children.filter((n) => isControlNode(n));
|
|
1101
|
+
if (branches.findIndex((n) => n.operation === "@default") === -1) branches.push({
|
|
1102
|
+
type: "control",
|
|
1103
|
+
operation: "@default",
|
|
1104
|
+
statement: "default",
|
|
1105
|
+
children: [],
|
|
1106
|
+
range: {
|
|
1107
|
+
start: 0,
|
|
1108
|
+
end: 0
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
status.imports.add("$watch");
|
|
1112
|
+
status.imports.add("$run");
|
|
1113
|
+
status.imports.add("t_range");
|
|
1114
|
+
status.imports.add("t_run_control");
|
|
1115
|
+
status.imports.add("t_run_branch");
|
|
1116
|
+
b.append("");
|
|
1117
|
+
b.append(`
|
|
1118
|
+
/* @switch */
|
|
1119
|
+
const ${rangeName} = t_range();
|
|
1120
|
+
let ${stateName} = $watch({ creator: (_: Node | null) => {} });`);
|
|
1121
|
+
b.append(`
|
|
1122
|
+
$run(function runSwitch() {`);
|
|
1123
|
+
addMappedText("", `${replaceForVarNames(node.statement, status)}`, " {", node.range, status, b);
|
|
1124
|
+
for (let branch of branches) buildSwitchBranch(branch, status, b, parentName, stateName);
|
|
1125
|
+
b.append(`
|
|
1126
|
+
}
|
|
1127
|
+
});`);
|
|
1128
|
+
b.append(`
|
|
1129
|
+
t_run_control(${rangeName}, ${anchorName}, (t_before) => {
|
|
1130
|
+
t_run_branch(${rangeName}, () => ${stateName}.creator(t_before));
|
|
1131
|
+
});`);
|
|
1132
|
+
b.append("");
|
|
1133
|
+
}
|
|
1134
|
+
function buildSwitchBranch(node, status, b, parentName, stateName) {
|
|
1135
|
+
addMappedText("", `${replaceForVarNames(node.statement, status)}`, " {", node.range, status, b);
|
|
1136
|
+
if (node.children.length > 0) {
|
|
1137
|
+
b.append(`${stateName}.creator = (t_before) => {`);
|
|
1138
|
+
buildFragment(node, status, b, parentName, "t_before");
|
|
1139
|
+
status.fragmentStack.push({
|
|
1140
|
+
fragment: node.fragment,
|
|
1141
|
+
path: ""
|
|
1142
|
+
});
|
|
1143
|
+
for (let child of node.children) buildNode(child, status, b, parentName, "t_before");
|
|
1144
|
+
status.fragmentStack.pop();
|
|
1145
|
+
buildAddFragment(node, status, b, parentName, "t_before");
|
|
1146
|
+
b.append("};");
|
|
1147
|
+
} else b.append(`${stateName}.creator = (_) => {};`);
|
|
1148
|
+
b.append(`
|
|
1149
|
+
break;
|
|
1150
|
+
}`);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
//#endregion
|
|
1154
|
+
//#region src/compile/build/client/buildControlNode.ts
|
|
1155
|
+
function buildControlNode(node, status, b) {
|
|
1156
|
+
switch (node.operation) {
|
|
1157
|
+
case "@if group":
|
|
1158
|
+
buildIfNode(node, status, b);
|
|
1159
|
+
break;
|
|
1160
|
+
case "@if":
|
|
1161
|
+
case "@else if":
|
|
1162
|
+
case "@else": break;
|
|
1163
|
+
case "@switch group":
|
|
1164
|
+
buildSwitchNode(node, status, b);
|
|
1165
|
+
break;
|
|
1166
|
+
case "@case":
|
|
1167
|
+
case "@default": break;
|
|
1168
|
+
case "@for group":
|
|
1169
|
+
buildForNode(node, status, b);
|
|
1170
|
+
break;
|
|
1171
|
+
case "@for":
|
|
1172
|
+
case "@key": break;
|
|
1173
|
+
case "@await group":
|
|
1174
|
+
buildAwaitNode(node, status, b);
|
|
1175
|
+
break;
|
|
1176
|
+
case "@await":
|
|
1177
|
+
case "@then":
|
|
1178
|
+
case "@catch": break;
|
|
1179
|
+
case "@replace group":
|
|
1180
|
+
buildReplaceNode(node, status, b);
|
|
1181
|
+
break;
|
|
1182
|
+
case "@replace": break;
|
|
1183
|
+
case "@html group":
|
|
1184
|
+
buildHtmlNode(node, status, b);
|
|
1185
|
+
break;
|
|
1186
|
+
case "@html": break;
|
|
1187
|
+
case "@const":
|
|
1188
|
+
case "@console":
|
|
1189
|
+
case "@debugger":
|
|
1190
|
+
buildScriptNode(node, status, b);
|
|
1191
|
+
break;
|
|
1192
|
+
case "@function":
|
|
1193
|
+
case "@async function":
|
|
1194
|
+
b.append("");
|
|
1195
|
+
buildScriptNode(node, status, b);
|
|
1196
|
+
b.append("");
|
|
1197
|
+
break;
|
|
1198
|
+
default: throw new Error(`Invalid operation: ${node.operation}`);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
//#endregion
|
|
1203
|
+
//#region src/compile/build/client/buildMount.ts
|
|
1204
|
+
function buildMount(functionName, functionBody, status, b) {
|
|
1205
|
+
functionBody = replaceForVarNames(functionBody, status);
|
|
1206
|
+
status.imports.add("$mount");
|
|
1207
|
+
b.append(`$mount(function ${functionName}() {`);
|
|
1208
|
+
b.append(functionBody);
|
|
1209
|
+
b.append("});");
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
//#endregion
|
|
1213
|
+
//#region src/compile/build/client/stashRun.ts
|
|
1214
|
+
function stashRun(fragment, functionStart, value, functionEnd, range, status) {
|
|
1215
|
+
value = replaceForVarNames(value, status);
|
|
1216
|
+
let functionBody = functionStart + value + functionEnd;
|
|
1217
|
+
fragment.effects.push({
|
|
1218
|
+
functionBody,
|
|
1219
|
+
ranges: [range],
|
|
1220
|
+
offsets: [functionStart.length],
|
|
1221
|
+
lengths: [value.length]
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
//#endregion
|
|
1226
|
+
//#region src/compile/build/client/buildElementNode.ts
|
|
1227
|
+
function buildElementNode(node, status, b, parentName, root = false) {
|
|
1228
|
+
if (status.inHead) {
|
|
1229
|
+
if (node.tagName === "title") buildTitleNode(node, status, b);
|
|
1230
|
+
else buildHeadNode(node, status, b);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
const svgElement = node.tagName === "svg";
|
|
1234
|
+
const oldns = status.ns;
|
|
1235
|
+
if (svgElement) status.ns = true;
|
|
1236
|
+
const varName = node.varName;
|
|
1237
|
+
if (varName) {
|
|
1238
|
+
if (node.tagName === "@element") buildDynamicElementNode(node, status, b);
|
|
1239
|
+
if (root) {}
|
|
1240
|
+
buildElementAttributes$1(node, varName, status, b);
|
|
1241
|
+
}
|
|
1242
|
+
const oldPreserveWhitespace = status.preserveWhitespace;
|
|
1243
|
+
status.preserveWhitespace = node.tagName === "code" || node.tagName === "pre";
|
|
1244
|
+
for (let child of node.children) buildNode(child, status, b, parentName, "null");
|
|
1245
|
+
status.preserveWhitespace = oldPreserveWhitespace;
|
|
1246
|
+
if (svgElement) status.ns = oldns;
|
|
1247
|
+
}
|
|
1248
|
+
function buildDynamicElementNode(node, status, b) {
|
|
1249
|
+
let selfAttribute = node.attributes.find((a) => a.name === "self");
|
|
1250
|
+
if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) {
|
|
1251
|
+
status.imports.add("$run");
|
|
1252
|
+
status.imports.add("t_dynamic");
|
|
1253
|
+
let selfValue = selfAttribute.value;
|
|
1254
|
+
b.append(`$run(function setDynamic() {`);
|
|
1255
|
+
b.append(`${node.varName} = t_dynamic(${node.varName}, ${selfValue});`);
|
|
1256
|
+
let parentName = node.varName;
|
|
1257
|
+
buildFragment(node, status, b, parentName, "null");
|
|
1258
|
+
status.fragmentStack.push({
|
|
1259
|
+
fragment: node.fragment,
|
|
1260
|
+
path: ""
|
|
1261
|
+
});
|
|
1262
|
+
for (let child of node.children) buildNode(child, status, b, parentName, "null");
|
|
1263
|
+
status.fragmentStack.pop();
|
|
1264
|
+
buildAddFragment(node, status, b, parentName, "null");
|
|
1265
|
+
b.append(`});`);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
function buildTitleNode(node, status, b) {
|
|
1269
|
+
let content = node.children[0].content || "";
|
|
1270
|
+
content = content.replace(/\s+/g, " ");
|
|
1271
|
+
let reactiveStarted = false;
|
|
1272
|
+
let reactiveCount = 0;
|
|
1273
|
+
for (let i = 0; i < content.length; i++) if (content[i] === "{") reactiveStarted = true;
|
|
1274
|
+
else if (content[i] === "}") {
|
|
1275
|
+
if (reactiveStarted) {
|
|
1276
|
+
reactiveCount += 1;
|
|
1277
|
+
reactiveStarted = false;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
if (reactiveCount) {
|
|
1281
|
+
status.imports.add("t_fmt");
|
|
1282
|
+
if (reactiveCount === 1 && content.startsWith("{") && content.endsWith("}")) content = `t_fmt(${content.substring(1, content.length - 1)})`;
|
|
1283
|
+
else content = `\`${content.replaceAll("{", "${t_fmt(").replaceAll("}", ")}")}\``;
|
|
1284
|
+
} else content = `"${content}"`;
|
|
1285
|
+
status.imports.add("$run");
|
|
1286
|
+
b.append(`
|
|
1287
|
+
$run(function runTitle() {
|
|
1288
|
+
const t_old_title = document.title;
|
|
1289
|
+
document.title = ${content};
|
|
1290
|
+
return () => document.title = t_old_title;
|
|
1291
|
+
});`);
|
|
1292
|
+
}
|
|
1293
|
+
function buildHeadNode(node, status, b) {
|
|
1294
|
+
status.imports.add("$run");
|
|
1295
|
+
b.append(`
|
|
1296
|
+
$run(function runHead() {
|
|
1297
|
+
const t_headel = document.createElement("${node.tagName}");
|
|
1298
|
+
document.getElementsByTagName("head")[0].appendChild(t_headel);`);
|
|
1299
|
+
for (let { name, value } of node.attributes) if (value != null) {
|
|
1300
|
+
status.imports.add("t_attribute");
|
|
1301
|
+
b.append(`t_attribute(t_headel, "${name}", ${value});`);
|
|
1302
|
+
}
|
|
1303
|
+
b.append(`
|
|
1304
|
+
});`);
|
|
1305
|
+
}
|
|
1306
|
+
function buildElementAttributes$1(node, varName, status, b) {
|
|
1307
|
+
let fragment = status.fragmentStack.at(-1)?.fragment;
|
|
1308
|
+
if (!fragment) throw new Error("No fragment on stack");
|
|
1309
|
+
const refAttribute = node.attributes.find((a) => a.name === "&ref");
|
|
1310
|
+
if (refAttribute) {
|
|
1311
|
+
let { value, fullyReactive } = refAttribute;
|
|
1312
|
+
if (value != null && fullyReactive) b.append(`${value} = ${varName};`);
|
|
1313
|
+
}
|
|
1314
|
+
for (let { name, value, reactive, range } of node.attributes) if (name === "self" && node.tagName === "@element") {} else if (name === "&ref") {} else if (value != null && reactive) if (name === "&group") buildBindGroupAttribute(node, varName, value, status, b);
|
|
1315
|
+
else if (name === "&value" || name === "&checked") buildBindAttribute(node, varName, name, value, status, b);
|
|
1316
|
+
else if (name === "onmount") buildMount("elMount", `return (${trimEnd(value.trim(), ";")})(${varName});`, status, b);
|
|
1317
|
+
else if (name.startsWith("on")) buildEventAttribute(varName, name, value, range, status, b);
|
|
1318
|
+
else if (name.startsWith("transition")) buildTransitionAttribute(node, varName, name, value, status, b);
|
|
1319
|
+
else if (name === "class") {
|
|
1320
|
+
status.imports.add("t_class");
|
|
1321
|
+
const params = [value];
|
|
1322
|
+
if (node.scopeStyles) params.push(`"torp-${status.styleHash}"`);
|
|
1323
|
+
const propName = status.ns ? "className.baseVal" : "className";
|
|
1324
|
+
stashRun(fragment, `${varName}.${propName} = t_class(`, params.join(", "), ");", range, status);
|
|
1325
|
+
} else if (name === "style") {
|
|
1326
|
+
status.imports.add("t_style");
|
|
1327
|
+
stashRun(fragment, `${varName}.setAttribute("style", t_style(`, value, "));", range, status);
|
|
1328
|
+
} else if (name.includes("-")) {
|
|
1329
|
+
status.imports.add("t_attribute");
|
|
1330
|
+
stashRun(fragment, `t_attribute(${varName}, "${name}", `, value, ");", range, status);
|
|
1331
|
+
} else {
|
|
1332
|
+
status.imports.add("t_attribute");
|
|
1333
|
+
stashRun(fragment, `t_attribute(${varName}, "${name}", `, value, ");", range, status);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function buildBindGroupAttribute(node, varName, value, status, b) {
|
|
1337
|
+
value = replaceForVarNames(value, status);
|
|
1338
|
+
let eventName = "change";
|
|
1339
|
+
let inputValue = node.attributes.find((a) => a.name === "value")?.value;
|
|
1340
|
+
let set = `${value} == ${inputValue}`;
|
|
1341
|
+
let propName = "checked";
|
|
1342
|
+
const setAttribute = `${varName}.${propName} = ${set}`;
|
|
1343
|
+
buildRun("setBinding", `${setAttribute};`, status, b);
|
|
1344
|
+
status.imports.add("t_event");
|
|
1345
|
+
b.append(`t_event(${varName}, "${eventName}", (e) => {
|
|
1346
|
+
if (e.target.${propName}) ${value} = ${inputValue};
|
|
1347
|
+
});`);
|
|
1348
|
+
}
|
|
1349
|
+
function buildBindAttribute(node, varName, name, value, status, b) {
|
|
1350
|
+
value = replaceForVarNames(value, status);
|
|
1351
|
+
let eventName = "input";
|
|
1352
|
+
let defaultValue = "\"\"";
|
|
1353
|
+
let inputValue = "e.target.value";
|
|
1354
|
+
if (node.tagName === "input") {
|
|
1355
|
+
let typeAttribute = node.attributes.find((a) => a.name === "type");
|
|
1356
|
+
if (typeAttribute && typeAttribute.value) switch (trimQuotes(typeAttribute.value)) {
|
|
1357
|
+
case "number":
|
|
1358
|
+
defaultValue = "0";
|
|
1359
|
+
inputValue = "Number(e.target.value)";
|
|
1360
|
+
break;
|
|
1361
|
+
case "checkbox":
|
|
1362
|
+
defaultValue = "false";
|
|
1363
|
+
inputValue = "e.target.checked";
|
|
1364
|
+
break;
|
|
1365
|
+
case "radio":
|
|
1366
|
+
eventName = "change";
|
|
1367
|
+
inputValue = "e.target.value";
|
|
1368
|
+
break;
|
|
1369
|
+
}
|
|
1370
|
+
} else if (node.tagName === "select") eventName = "change";
|
|
1371
|
+
let set = `${value} || ${defaultValue}`;
|
|
1372
|
+
const propName = name.substring(1);
|
|
1373
|
+
const setAttribute = `${varName}.${propName} = ${set}`;
|
|
1374
|
+
buildRun("setBinding", `${setAttribute};`, status, b);
|
|
1375
|
+
status.imports.add("t_event");
|
|
1376
|
+
b.append(`t_event(${varName}, "${eventName}", (e) => ${value} = ${inputValue});`);
|
|
1377
|
+
}
|
|
1378
|
+
function buildEventAttribute(varName, name, value, range, status, b) {
|
|
1379
|
+
value = replaceForVarNames(value, status);
|
|
1380
|
+
const eventName = name.substring(2);
|
|
1381
|
+
status.imports.add("t_event");
|
|
1382
|
+
addMappedText(`t_event(${varName}, "${eventName}", `, value, `);`, range, status, b);
|
|
1383
|
+
}
|
|
1384
|
+
function buildTransitionAttribute(node, varName, name, value, status, b) {
|
|
1385
|
+
value = replaceForVarNames(value, status);
|
|
1386
|
+
status.imports.add("t_animate");
|
|
1387
|
+
let entryVarName = nextVarName("trans_in", status);
|
|
1388
|
+
let exitVarName = nextVarName("trans_out", status);
|
|
1389
|
+
if (name === "transition") {
|
|
1390
|
+
b.append(`const ${entryVarName} = ${getAnimationDetails(value)};`);
|
|
1391
|
+
b.append(`const ${exitVarName} = ${entryVarName};`);
|
|
1392
|
+
b.append(`t_animate(${varName}, ${entryVarName}, ${exitVarName});`);
|
|
1393
|
+
} else if (name === "transition-in") {
|
|
1394
|
+
let outAttribute = node.attributes.find((a) => a.name === "transition-out");
|
|
1395
|
+
if (outAttribute && outAttribute.value && outAttribute.fullyReactive) {
|
|
1396
|
+
let outValue = outAttribute.value;
|
|
1397
|
+
b.append(`const ${entryVarName} = ${getAnimationDetails(value)};`);
|
|
1398
|
+
b.append(`const ${exitVarName} = ${getAnimationDetails(outValue)};`);
|
|
1399
|
+
b.append(`t_animate(${varName}, ${entryVarName}, ${exitVarName});`);
|
|
1400
|
+
} else {
|
|
1401
|
+
b.append(`const ${entryVarName} = ${getAnimationDetails(value)};`);
|
|
1402
|
+
b.append(`t_animate(${varName}, ${entryVarName});`);
|
|
1403
|
+
}
|
|
1404
|
+
} else if (name === "transition-out") if (node.attributes.find((a) => a.name === "transition-in")) return;
|
|
1405
|
+
else {
|
|
1406
|
+
b.append(`const ${exitVarName} = ${getAnimationDetails(value)};`);
|
|
1407
|
+
b.append(`t_animate(${varName}, null, ${entryVarName});`);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
function getAnimationDetails(value) {
|
|
1411
|
+
let parts = [];
|
|
1412
|
+
let start = 0;
|
|
1413
|
+
let squareCount = 0;
|
|
1414
|
+
let curlyCount = 0;
|
|
1415
|
+
for (let i = 0; i < value.length; i++) switch (value[i]) {
|
|
1416
|
+
case ",":
|
|
1417
|
+
if (squareCount === 0 && curlyCount === 0) {
|
|
1418
|
+
parts.push(value.substring(start, i));
|
|
1419
|
+
start = i + 1;
|
|
1420
|
+
}
|
|
1421
|
+
break;
|
|
1422
|
+
case "[":
|
|
1423
|
+
squareCount += 1;
|
|
1424
|
+
break;
|
|
1425
|
+
case "]":
|
|
1426
|
+
squareCount -= 1;
|
|
1427
|
+
break;
|
|
1428
|
+
case "{":
|
|
1429
|
+
curlyCount += 1;
|
|
1430
|
+
break;
|
|
1431
|
+
case "}":
|
|
1432
|
+
curlyCount -= 1;
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
parts.push(value.substring(start));
|
|
1436
|
+
let keyframes = parts[0]?.trim();
|
|
1437
|
+
let options = parts[1]?.trim();
|
|
1438
|
+
return `{ ${[`keyframes: ${keyframes}`, options ? `options: ${options}` : null].filter(Boolean).join(", ")} }`;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
//#endregion
|
|
1442
|
+
//#region src/compile/build/client/buildRootNode.ts
|
|
1443
|
+
function buildRootNode(node, status, b, parentName, anchorName) {
|
|
1444
|
+
buildFragment(node, status, b, parentName, anchorName);
|
|
1445
|
+
status.fragmentStack.push({
|
|
1446
|
+
fragment: node.fragment,
|
|
1447
|
+
path: "0:ch/"
|
|
1448
|
+
});
|
|
1449
|
+
for (let child of node.children) buildNode(child, status, b, parentName, anchorName, true);
|
|
1450
|
+
status.fragmentStack.pop();
|
|
1451
|
+
buildAddFragment(node, status, b, parentName, anchorName);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
//#endregion
|
|
1455
|
+
//#region src/compile/build/client/buildSlotNode.ts
|
|
1456
|
+
function buildSlotNode(node, status, b) {
|
|
1457
|
+
let slotName = node.attributes.find((a) => a.name === "name")?.value;
|
|
1458
|
+
slotName = slotName ? trimQuotes(slotName) : "_";
|
|
1459
|
+
const propsName = nextVarName("sprops", status);
|
|
1460
|
+
const slotAttributes = node.attributes.filter((a) => a.name !== "name");
|
|
1461
|
+
const slotHasProps = slotAttributes.length;
|
|
1462
|
+
if (slotHasProps) {
|
|
1463
|
+
status.imports.add("$watch");
|
|
1464
|
+
b.append(`const ${propsName} = $watch({});`);
|
|
1465
|
+
for (let { name, value, reactive } of slotAttributes) if (value != null) {
|
|
1466
|
+
const setProp = `${propsName}["${name}"] = ${value}`;
|
|
1467
|
+
if (reactive) buildRun("setProp", `${setProp};`, status, b);
|
|
1468
|
+
else b.append(`${setProp};`);
|
|
1469
|
+
} else b.append(`${propsName}["${name}"] = true;`);
|
|
1470
|
+
}
|
|
1471
|
+
const slotParentName = node.parentName;
|
|
1472
|
+
const slotAnchorName = node.varName;
|
|
1473
|
+
b.append(`if ($slots && $slots["${slotName}"]) {`);
|
|
1474
|
+
let params = [
|
|
1475
|
+
slotParentName,
|
|
1476
|
+
slotAnchorName,
|
|
1477
|
+
slotHasProps ? propsName : "undefined",
|
|
1478
|
+
"$context"
|
|
1479
|
+
];
|
|
1480
|
+
b.append(`$slots["${slotName}"](${params.join(", ")})`);
|
|
1481
|
+
const fill = node.children.find((c) => isSpecialNode(c) && c.tagName === "fill");
|
|
1482
|
+
if (fill && isSpecialNode(fill) && fill.children.length) {
|
|
1483
|
+
b.append(`} else {`);
|
|
1484
|
+
buildFragment(fill, status, b, slotParentName, slotAnchorName);
|
|
1485
|
+
status.fragmentStack.push({
|
|
1486
|
+
fragment: fill.fragment,
|
|
1487
|
+
path: "0:ch/"
|
|
1488
|
+
});
|
|
1489
|
+
for (let child of fill.children) buildNode(child, status, b, slotParentName, slotAnchorName);
|
|
1490
|
+
status.fragmentStack.pop();
|
|
1491
|
+
buildAddFragment(fill, status, b, slotParentName, slotAnchorName);
|
|
1492
|
+
}
|
|
1493
|
+
b.append(`}`);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
//#endregion
|
|
1497
|
+
//#region src/compile/build/client/buildSpecialNode.ts
|
|
1498
|
+
function buildSpecialNode(node, status, b, parentName, root = false) {
|
|
1499
|
+
switch (node.tagName) {
|
|
1500
|
+
case "slot":
|
|
1501
|
+
buildSlotNode(node, status, b);
|
|
1502
|
+
break;
|
|
1503
|
+
case "fill": break;
|
|
1504
|
+
case "@element":
|
|
1505
|
+
buildElementNode(node, status, b, parentName, root);
|
|
1506
|
+
break;
|
|
1507
|
+
case "@component":
|
|
1508
|
+
buildComponentNode(node, status, b, root);
|
|
1509
|
+
break;
|
|
1510
|
+
default: throw new Error(`Invalid special node: ${node.tagName}`);
|
|
1511
|
+
}
|
|
1512
|
+
return "";
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
//#endregion
|
|
1516
|
+
//#region src/compile/utils/endOfString.ts
|
|
1517
|
+
function endOfString(char, content, start) {
|
|
1518
|
+
for (let j = start + 1; j < content.length; j++) if (content[j] === char && content[j - 1] !== "\\") return j;
|
|
1519
|
+
return content.length;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
//#endregion
|
|
1523
|
+
//#region src/compile/build/client/stashRunWithOffsets.ts
|
|
1524
|
+
function stashRunWithOffsets(fragment, functionBody, ranges, offsets, lengths, status) {
|
|
1525
|
+
for (let i = 0; i < offsets.length; i++) {
|
|
1526
|
+
const oldLength = functionBody.length;
|
|
1527
|
+
functionBody = functionBody.substring(0, offsets[i]) + replaceForVarNames(functionBody.substring(offsets[i], offsets[i] + lengths[i]), status) + functionBody.substring(offsets[i] + lengths[i]);
|
|
1528
|
+
const changedLength = functionBody.length - oldLength;
|
|
1529
|
+
lengths[i] += changedLength;
|
|
1530
|
+
for (let j = i + 1; j < offsets.length; j++) offsets[j] += changedLength;
|
|
1531
|
+
}
|
|
1532
|
+
fragment.effects.push({
|
|
1533
|
+
functionBody,
|
|
1534
|
+
ranges,
|
|
1535
|
+
offsets,
|
|
1536
|
+
lengths
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
//#endregion
|
|
1541
|
+
//#region src/compile/build/client/buildTextNode.ts
|
|
1542
|
+
function buildTextNode(node, status) {
|
|
1543
|
+
let fragment = status.fragmentStack.at(-1)?.fragment;
|
|
1544
|
+
if (!fragment) return;
|
|
1545
|
+
let content = node.content || "";
|
|
1546
|
+
if (!status.preserveWhitespace) content = content.replaceAll(/\s+/g, " ");
|
|
1547
|
+
let textContent = "";
|
|
1548
|
+
let reactiveCount = 0;
|
|
1549
|
+
let reactiveStart = 0;
|
|
1550
|
+
let offsets = [];
|
|
1551
|
+
let lengths = [];
|
|
1552
|
+
let level = 0;
|
|
1553
|
+
let maxLevel = 0;
|
|
1554
|
+
for (let i = 0; i < content.length; i++) {
|
|
1555
|
+
const char = content[i];
|
|
1556
|
+
const nextChar = content[i + 1];
|
|
1557
|
+
if (char === "{") {
|
|
1558
|
+
level++;
|
|
1559
|
+
maxLevel = Math.max(level, maxLevel);
|
|
1560
|
+
if (level === 1) {
|
|
1561
|
+
reactiveCount += 1;
|
|
1562
|
+
textContent += "${t_fmt(";
|
|
1563
|
+
reactiveStart = textContent.length;
|
|
1564
|
+
offsets.push(reactiveStart);
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
} else if (char === "}") {
|
|
1568
|
+
level--;
|
|
1569
|
+
if (level === 0) {
|
|
1570
|
+
lengths.push(textContent.length - reactiveStart);
|
|
1571
|
+
textContent += ")";
|
|
1572
|
+
}
|
|
1573
|
+
} else if (char === "`" && level === 0) textContent += "\\";
|
|
1574
|
+
else if (level > 0) {
|
|
1575
|
+
if (char === "/" && nextChar === "/") {
|
|
1576
|
+
let start = i;
|
|
1577
|
+
i = content.indexOf("\n", i);
|
|
1578
|
+
textContent += content.substring(start, i + 1);
|
|
1579
|
+
continue;
|
|
1580
|
+
} else if (char === "/" && nextChar === "*") {
|
|
1581
|
+
let start = i;
|
|
1582
|
+
i = content.indexOf("*/", i) + 1;
|
|
1583
|
+
textContent += content.substring(start, i + 1);
|
|
1584
|
+
continue;
|
|
1585
|
+
} else if (char === "\"" || char === "'") {
|
|
1586
|
+
let start = i;
|
|
1587
|
+
i = endOfString(char, content, i);
|
|
1588
|
+
textContent += content.substring(start, i + 1);
|
|
1589
|
+
continue;
|
|
1590
|
+
} else if (char === "`") {
|
|
1591
|
+
let start = i;
|
|
1592
|
+
i = endOfTemplateString(content, i);
|
|
1593
|
+
textContent += content.substring(start, i + 1);
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
textContent += content[i];
|
|
1598
|
+
}
|
|
1599
|
+
if (maxLevel) {
|
|
1600
|
+
let offsetChange = 0;
|
|
1601
|
+
if (reactiveCount === 1 && content.startsWith("{") && content.endsWith("}")) {
|
|
1602
|
+
textContent = textContent.substring(2, textContent.length - 1);
|
|
1603
|
+
offsetChange = -2;
|
|
1604
|
+
} else {
|
|
1605
|
+
textContent = `\`${textContent}\``;
|
|
1606
|
+
offsetChange = 1;
|
|
1607
|
+
}
|
|
1608
|
+
status.imports.add("t_fmt");
|
|
1609
|
+
let functionBody = `${node.varName}.textContent = `;
|
|
1610
|
+
offsetChange += functionBody.length;
|
|
1611
|
+
offsets.map((_, i) => offsets[i] += offsetChange);
|
|
1612
|
+
functionBody += `${textContent};`;
|
|
1613
|
+
stashRunWithOffsets(fragment, functionBody, node.ranges, offsets, lengths, status);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
//#endregion
|
|
1618
|
+
//#region src/compile/build/client/buildNode.ts
|
|
1619
|
+
function buildNode(node, status, b, parentName, anchorName, root = false) {
|
|
1620
|
+
if (node.handled) {
|
|
1621
|
+
node.handled = false;
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
switch (node.type) {
|
|
1625
|
+
case "root":
|
|
1626
|
+
buildRootNode(node, status, b, parentName, anchorName);
|
|
1627
|
+
break;
|
|
1628
|
+
case "control":
|
|
1629
|
+
buildControlNode(node, status, b);
|
|
1630
|
+
break;
|
|
1631
|
+
case "component":
|
|
1632
|
+
buildComponentNode(node, status, b, root);
|
|
1633
|
+
break;
|
|
1634
|
+
case "element":
|
|
1635
|
+
buildElementNode(node, status, b, parentName, root);
|
|
1636
|
+
break;
|
|
1637
|
+
case "text":
|
|
1638
|
+
buildTextNode(node, status);
|
|
1639
|
+
break;
|
|
1640
|
+
case "special":
|
|
1641
|
+
buildSpecialNode(node, status, b, parentName, root);
|
|
1642
|
+
break;
|
|
1643
|
+
default: throw new Error(`Invalid node type: ${node.type}`);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
//#endregion
|
|
1648
|
+
//#region src/compile/build/client/buildCode.ts
|
|
1649
|
+
const importsMap$1 = {
|
|
1650
|
+
$watch: "import { $watch } from \"${folder}\";",
|
|
1651
|
+
$cache: "import { $cache } from \"${folder}\";",
|
|
1652
|
+
$run: "import { $run } from \"${folder}\";",
|
|
1653
|
+
$mount: "import { $mount } from \"${folder}\";",
|
|
1654
|
+
$unwrap: "import { $unwrap } from \"${folder}\";",
|
|
1655
|
+
$peek: "import { $peek } from \"${folder}\";",
|
|
1656
|
+
$batch: "import { $batch } from \"${folder}\";",
|
|
1657
|
+
t_range: "import { t_range } from \"${folder}\";",
|
|
1658
|
+
t_push_range: "import { t_push_range } from \"${folder}\";",
|
|
1659
|
+
t_pop_range: "import { t_pop_range } from \"${folder}\";",
|
|
1660
|
+
t_run_control: "import { t_run_control } from \"${folder}\";",
|
|
1661
|
+
t_run_branch: "import { t_run_branch } from \"${folder}\";",
|
|
1662
|
+
t_list_item: "import { t_list_item } from \"${folder}\";",
|
|
1663
|
+
t_run_list: "import { t_run_list } from \"${folder}\";",
|
|
1664
|
+
t_add_fragment: "import { t_add_fragment } from \"${folder}\";",
|
|
1665
|
+
t_apply_props: "import { t_apply_props } from \"${folder}\";",
|
|
1666
|
+
t_class: "import { t_class } from \"${folder}\";",
|
|
1667
|
+
t_style: "import { t_style } from \"${folder}\";",
|
|
1668
|
+
t_attribute: "import { t_attribute } from \"${folder}\";",
|
|
1669
|
+
t_dynamic: "import { t_dynamic } from \"${folder}\";",
|
|
1670
|
+
t_fmt: "import { t_fmt } from \"${folder}\";",
|
|
1671
|
+
t_fragment: "import { t_fragment } from \"${folder}\";",
|
|
1672
|
+
t_event: "import { t_event } from \"${folder}\";",
|
|
1673
|
+
t_animate: "import { t_animate } from \"${folder}\";",
|
|
1674
|
+
t_root: "import { t_root } from \"${folder}\";",
|
|
1675
|
+
t_anchor: "import { t_anchor } from \"${folder}\";",
|
|
1676
|
+
t_child: "import { t_child } from \"${folder}\";",
|
|
1677
|
+
t_next: "import { t_next } from \"${folder}\";",
|
|
1678
|
+
t_skip: "import { t_skip } from \"${folder}\";",
|
|
1679
|
+
t_frg: "import { t_frg } from \"${folder}\";",
|
|
1680
|
+
t_elm: "import { t_elm } from \"${folder}\";",
|
|
1681
|
+
t_txt: "import { t_txt } from \"${folder}\";",
|
|
1682
|
+
t_cmt: "import { t_cmt } from \"${folder}\";",
|
|
1683
|
+
t_print: "import { t_print } from \"${folder}\";",
|
|
1684
|
+
ListItem: "import { type ListItem } from \"${folder}\";",
|
|
1685
|
+
SlotRender: "import { type SlotRender } from \"${folder}\";"
|
|
1686
|
+
};
|
|
1687
|
+
function buildCode(template, map, options) {
|
|
1688
|
+
let b = new Builder(options?.mapped);
|
|
1689
|
+
let imports = /* @__PURE__ */ new Set();
|
|
1690
|
+
imports.add("SlotRender");
|
|
1691
|
+
buildTemplate(template, map, imports, b, options);
|
|
1692
|
+
let startSize = b.toString().length;
|
|
1693
|
+
if (imports.size) {
|
|
1694
|
+
b.prepend("");
|
|
1695
|
+
for (let imp of Array.from(imports).sort().reverse()) {
|
|
1696
|
+
imp = importsMap$1[imp] || imp;
|
|
1697
|
+
b.prepend(imp.replace("${folder}", options?.renderFolder || "@torpor/view"));
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
if (options?.mapped) {
|
|
1701
|
+
let diff = b.toString().length - startSize + 1;
|
|
1702
|
+
for (let m of map) {
|
|
1703
|
+
m.compiled.start += diff;
|
|
1704
|
+
m.compiled.end += diff;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return b.toString();
|
|
1708
|
+
}
|
|
1709
|
+
function buildTemplate(template, map, imports, b, options) {
|
|
1710
|
+
let script = template.script.map((s) => s.script).join("\n");
|
|
1711
|
+
if (/\$watch\b/.test(script)) imports.add("$watch");
|
|
1712
|
+
if (/\$cache\b/.test(script)) imports.add("$cache");
|
|
1713
|
+
if (/\$run\b/.test(script)) imports.add("$run");
|
|
1714
|
+
if (/\$mount\b/.test(script)) imports.add("$mount");
|
|
1715
|
+
if (/\$unwrap\b/.test(script)) imports.add("$unwrap");
|
|
1716
|
+
if (/\$peek\b/.test(script)) imports.add("$peek");
|
|
1717
|
+
if (/\$batch\b/.test(script)) imports.add("$batch");
|
|
1718
|
+
let currentIndex = 0;
|
|
1719
|
+
let current = template.components[0];
|
|
1720
|
+
for (let chunk of template.script) if (chunk.script === "/* @params */") {
|
|
1721
|
+
let params = [
|
|
1722
|
+
`${current.markup ? "$parent" : "_$parent"}: ParentNode`,
|
|
1723
|
+
`${current.markup ? "$anchor" : "_$anchor"}: Node | null`,
|
|
1724
|
+
current.params ?? `${current.props?.length ? "$props: Record<PropertyKey, any>" : "_$props: Record<PropertyKey, any> | undefined"}`,
|
|
1725
|
+
`${current.contextProps?.length || current.needsContext ? "$context" : "_$context"}: Record<PropertyKey, any>`,
|
|
1726
|
+
`${current.slotProps?.length ? "$slots" : "_$slots"}?: Record<string, SlotRender>`
|
|
1727
|
+
];
|
|
1728
|
+
b.append(params.join(",\n"));
|
|
1729
|
+
} else if (chunk.script === ") /* @return_type */ {") b.append("): void {");
|
|
1730
|
+
else if (chunk.script === "/* @start */") {
|
|
1731
|
+
if (current.contextProps?.length) b.append(`$context = Object.assign({}, $context);`);
|
|
1732
|
+
b.append("");
|
|
1733
|
+
} else if (chunk.script === "/* @render */") {
|
|
1734
|
+
if (current.markup) {
|
|
1735
|
+
let status = makeStatus(imports, map, current, options);
|
|
1736
|
+
b.append("");
|
|
1737
|
+
b.append("/* User interface */");
|
|
1738
|
+
buildFragmentText(current.markup, status, b);
|
|
1739
|
+
b.append("");
|
|
1740
|
+
buildNode(current.markup, status, b, "$parent", "$anchor", true);
|
|
1741
|
+
}
|
|
1742
|
+
} else if (chunk.script === "/* @head */") {
|
|
1743
|
+
if (current.head) {
|
|
1744
|
+
let status = makeStatus(imports, map, current, options);
|
|
1745
|
+
b.append("");
|
|
1746
|
+
b.append("/* Head */");
|
|
1747
|
+
status.inHead = true;
|
|
1748
|
+
buildNode(current.head, status, b, "$parent", "$anchor", true);
|
|
1749
|
+
status.inHead = false;
|
|
1750
|
+
}
|
|
1751
|
+
} else if (chunk.script === "/* @style */") {} else if (chunk.script === "/* @end */") {
|
|
1752
|
+
currentIndex += 1;
|
|
1753
|
+
current = template.components[currentIndex];
|
|
1754
|
+
} else {
|
|
1755
|
+
let status = makeStatus(imports, map, current, options);
|
|
1756
|
+
addMappedText("", chunk.script, "", chunk.range, status, b);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
function makeStatus(imports, map, current, options) {
|
|
1760
|
+
return {
|
|
1761
|
+
imports,
|
|
1762
|
+
props: current?.props ?? [],
|
|
1763
|
+
contextProps: current?.contextProps ?? [],
|
|
1764
|
+
slotProps: current?.slotProps ?? [],
|
|
1765
|
+
styleHash: current?.style?.hash ?? "",
|
|
1766
|
+
map,
|
|
1767
|
+
varNames: {},
|
|
1768
|
+
fragmentStack: [],
|
|
1769
|
+
forVarNames: [],
|
|
1770
|
+
ns: false,
|
|
1771
|
+
preserveWhitespace: false,
|
|
1772
|
+
options
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
//#endregion
|
|
1777
|
+
//#region src/compile/build/client/buildStyles.ts
|
|
1778
|
+
function buildStyles(style, styleHash) {
|
|
1779
|
+
const b = new Builder();
|
|
1780
|
+
for (let block of style.blocks) buildStyleBlock(block, b, styleHash);
|
|
1781
|
+
return b.toString();
|
|
1782
|
+
}
|
|
1783
|
+
function buildStyleBlock(block, b, styleHash) {
|
|
1784
|
+
if (block.selector.startsWith("@")) b.append(`${block.selector} {`);
|
|
1785
|
+
else {
|
|
1786
|
+
block.selector = block.selector.replaceAll(/\s+/g, " ");
|
|
1787
|
+
let selectors = [];
|
|
1788
|
+
let global = false;
|
|
1789
|
+
let start = 0;
|
|
1790
|
+
for (let i = 0; i <= block.selector.length; i++) if (block.selector.substring(i).startsWith(":global(")) {
|
|
1791
|
+
i += 7;
|
|
1792
|
+
start = i + 1;
|
|
1793
|
+
global = true;
|
|
1794
|
+
} else if (global && block.selector[i] === ")") {
|
|
1795
|
+
addSelector(selectors, block.selector, styleHash, start, i, global);
|
|
1796
|
+
start = i + 1;
|
|
1797
|
+
global = false;
|
|
1798
|
+
} else if ("*,>+~".includes(block.selector[i])) {
|
|
1799
|
+
addSelector(selectors, block.selector, styleHash, start, i, global);
|
|
1800
|
+
selectors.push(block.selector[i]);
|
|
1801
|
+
start = i + 1;
|
|
1802
|
+
} else if (i === block.selector.length) addSelector(selectors, block.selector, styleHash, start, i, global);
|
|
1803
|
+
else if (block.selector[i] === " ") {
|
|
1804
|
+
addSelector(selectors, block.selector, styleHash, start, i, global);
|
|
1805
|
+
start = i + 1;
|
|
1806
|
+
}
|
|
1807
|
+
b.append(`${selectors.join(" ").replaceAll(" ,", ",")} {`);
|
|
1808
|
+
}
|
|
1809
|
+
for (let attribute of block.attributes) buildStyleAttribute(attribute, b);
|
|
1810
|
+
for (let child of block.children) buildStyleBlock(child, b, styleHash);
|
|
1811
|
+
b.append(`}`);
|
|
1812
|
+
}
|
|
1813
|
+
function addSelector(selectors, selector, styleHash, start, end, global) {
|
|
1814
|
+
if (end > start) {
|
|
1815
|
+
let s = selector.substring(start, end);
|
|
1816
|
+
if (!global) {
|
|
1817
|
+
let pre = s;
|
|
1818
|
+
let post = "";
|
|
1819
|
+
let colonPosition = s.indexOf(":");
|
|
1820
|
+
if (colonPosition !== -1) {
|
|
1821
|
+
pre = s.substring(0, colonPosition);
|
|
1822
|
+
post = s.substring(colonPosition);
|
|
1823
|
+
}
|
|
1824
|
+
s = `${pre}.torp-${styleHash}${post}`;
|
|
1825
|
+
}
|
|
1826
|
+
selectors.push(s);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
function buildStyleAttribute(attribute, b) {
|
|
1830
|
+
b.append(`${attribute.name}: ${attribute.value};`);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
//#endregion
|
|
1834
|
+
//#region src/compile/build/server/buildServerComponentNode.ts
|
|
1835
|
+
function buildServerComponentNode(node, status, b) {
|
|
1836
|
+
status.output += HYDRATION_START_COMMENT;
|
|
1837
|
+
if (status.output) {
|
|
1838
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1839
|
+
status.output = "";
|
|
1840
|
+
}
|
|
1841
|
+
const componentHasProps = node.attributes.length;
|
|
1842
|
+
const propsName = componentHasProps ? nextVarName("props", status) : "undefined";
|
|
1843
|
+
if (componentHasProps) {
|
|
1844
|
+
b.append(`const ${propsName}: any = {};`);
|
|
1845
|
+
for (let { name, value } of node.attributes) if (name === "class" && value != null) {
|
|
1846
|
+
status.imports.add("t_class");
|
|
1847
|
+
const params = [value];
|
|
1848
|
+
if (node.scopeStyles) params.push(`"torp-${status.styleHash}"`);
|
|
1849
|
+
b.append(`${propsName}["class"] = t_class(${params.join(", ")});`);
|
|
1850
|
+
} else if (name === "style" && value != null) {
|
|
1851
|
+
status.imports.add("t_style");
|
|
1852
|
+
b.append(`${propsName}["style"] = t_style(${value});`);
|
|
1853
|
+
} else if (value != null) b.append(`${propsName}["${name}"] = ${value};`);
|
|
1854
|
+
else b.append(`${propsName}["${name}"] = true;`);
|
|
1855
|
+
}
|
|
1856
|
+
const componentHasSlots = node.children.length;
|
|
1857
|
+
const slotsName = componentHasSlots ? nextVarName("slots", status) : "undefined";
|
|
1858
|
+
if (componentHasSlots) {
|
|
1859
|
+
b.append(`const ${slotsName}: Record<string, ServerSlotRender> = {};`);
|
|
1860
|
+
for (let slot of node.children) if (isSpecialNode(slot) && slot.tagName === "fill") {
|
|
1861
|
+
const nameAttribute = slot.attributes.find((a) => a.name === "name");
|
|
1862
|
+
const slotName = nameAttribute?.value ? trimQuotes(nameAttribute.value) : "_";
|
|
1863
|
+
const slotParams = [`${slot.hasSlotProps ? "$sprops" : "_$sprops?"}: Record<PropertyKey, any>`, "// @ts-ignore\n$context?: Record<PropertyKey, any>"];
|
|
1864
|
+
b.append(`${slotsName}["${slotName}"] = (\n${slotParams.join(",\n")}\n) => {`);
|
|
1865
|
+
b.append(`let t_body = "";`);
|
|
1866
|
+
for (let child of slot.children) buildServerNode(child, status, b);
|
|
1867
|
+
if (status.output) {
|
|
1868
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1869
|
+
status.output = "";
|
|
1870
|
+
}
|
|
1871
|
+
b.append("return t_body;");
|
|
1872
|
+
b.append(`}`);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
let componentName = node.tagName;
|
|
1876
|
+
if (componentName === "@component") {
|
|
1877
|
+
let selfAttribute = node.attributes.find((a) => a.name === "self");
|
|
1878
|
+
if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) componentName = selfAttribute.value;
|
|
1879
|
+
}
|
|
1880
|
+
let renderParams = `${propsName}, $context`;
|
|
1881
|
+
if (slotsName !== "undefined") renderParams += `, ${slotsName}`;
|
|
1882
|
+
b.append("");
|
|
1883
|
+
const componentResult = nextVarName("comp", status);
|
|
1884
|
+
b.append(`const ${componentResult} = ${componentName}(${renderParams});`);
|
|
1885
|
+
b.append(`t_body += ${componentResult}.body;`);
|
|
1886
|
+
b.append(`t_head += ${componentResult}.head;`);
|
|
1887
|
+
status.output += HYDRATION_END_COMMENT;
|
|
1888
|
+
status.output += ANCHOR_COMMENT;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
//#endregion
|
|
1892
|
+
//#region src/compile/build/server/buildServerAwaitNode.ts
|
|
1893
|
+
function buildServerAwaitNode(node, status, b) {
|
|
1894
|
+
status.output += HYDRATION_START_COMMENT;
|
|
1895
|
+
if (status.output) {
|
|
1896
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1897
|
+
status.output = "";
|
|
1898
|
+
}
|
|
1899
|
+
for (let branch of node.children) if (isControlNode(branch) && branch.operation === "@await") buildServerAwaitBranch(branch, status, b);
|
|
1900
|
+
status.output += HYDRATION_END_COMMENT;
|
|
1901
|
+
status.output += ANCHOR_COMMENT;
|
|
1902
|
+
}
|
|
1903
|
+
function buildServerAwaitBranch(node, status, b) {
|
|
1904
|
+
for (let child of node.children) buildServerNode(child, status, b);
|
|
1905
|
+
if (status.output) {
|
|
1906
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1907
|
+
status.output = "";
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
//#endregion
|
|
1912
|
+
//#region src/compile/build/server/buildServerForNode.ts
|
|
1913
|
+
function buildServerForNode(node, status, b) {
|
|
1914
|
+
status.output += HYDRATION_START_COMMENT;
|
|
1915
|
+
if (status.output) {
|
|
1916
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1917
|
+
status.output = "";
|
|
1918
|
+
}
|
|
1919
|
+
for (let branch of node.children) if (isControlNode(branch)) buildServerForBranch(branch, status, b);
|
|
1920
|
+
status.output += HYDRATION_END_COMMENT;
|
|
1921
|
+
status.output += ANCHOR_COMMENT;
|
|
1922
|
+
}
|
|
1923
|
+
function buildServerForBranch(node, status, b) {
|
|
1924
|
+
b.append(`${node.statement} {`);
|
|
1925
|
+
status.output += HYDRATION_BREAK_COMMENT;
|
|
1926
|
+
for (let child of node.children) buildServerNode(child, status, b);
|
|
1927
|
+
if (status.output) {
|
|
1928
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1929
|
+
status.output = "";
|
|
1930
|
+
}
|
|
1931
|
+
b.append("}");
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
//#endregion
|
|
1935
|
+
//#region src/compile/build/server/buildServerHtmlNode.ts
|
|
1936
|
+
function buildServerHtmlNode(node, status) {
|
|
1937
|
+
status.output += HYDRATION_START_COMMENT;
|
|
1938
|
+
for (let branch of node.children) if (isControlNode(branch)) status.output += `\${${branch.statement}}`;
|
|
1939
|
+
status.output += HYDRATION_END_COMMENT;
|
|
1940
|
+
status.output += ANCHOR_COMMENT;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
//#endregion
|
|
1944
|
+
//#region src/compile/build/server/buildServerIfNode.ts
|
|
1945
|
+
function buildServerIfNode(node, status, b) {
|
|
1946
|
+
status.output += HYDRATION_START_COMMENT;
|
|
1947
|
+
if (status.output) {
|
|
1948
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1949
|
+
status.output = "";
|
|
1950
|
+
}
|
|
1951
|
+
for (let branch of node.children) if (isControlNode(branch)) buildServerIfBranch(branch, status, b);
|
|
1952
|
+
status.output += HYDRATION_END_COMMENT;
|
|
1953
|
+
status.output += ANCHOR_COMMENT;
|
|
1954
|
+
}
|
|
1955
|
+
function buildServerIfBranch(node, status, b) {
|
|
1956
|
+
b.append(`${node.statement} {`);
|
|
1957
|
+
status.output += HYDRATION_BREAK_COMMENT;
|
|
1958
|
+
for (let child of node.children) buildServerNode(child, status, b);
|
|
1959
|
+
if (status.output) {
|
|
1960
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1961
|
+
status.output = "";
|
|
1962
|
+
}
|
|
1963
|
+
b.append("}");
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
//#endregion
|
|
1967
|
+
//#region src/compile/build/server/buildServerReplaceNode.ts
|
|
1968
|
+
function buildServerReplaceNode(node, status, b) {
|
|
1969
|
+
status.output += HYDRATION_START_COMMENT;
|
|
1970
|
+
if (status.output) {
|
|
1971
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1972
|
+
status.output = "";
|
|
1973
|
+
}
|
|
1974
|
+
for (let branch of node.children) if (isControlNode(branch)) buildServerReplaceBranch(branch, status, b);
|
|
1975
|
+
status.output += HYDRATION_END_COMMENT;
|
|
1976
|
+
status.output += ANCHOR_COMMENT;
|
|
1977
|
+
}
|
|
1978
|
+
function buildServerReplaceBranch(node, status, b) {
|
|
1979
|
+
b.append(`${node.statement};`);
|
|
1980
|
+
for (let child of node.children) buildServerNode(child, status, b);
|
|
1981
|
+
if (status.output) {
|
|
1982
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
1983
|
+
status.output = "";
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
//#endregion
|
|
1988
|
+
//#region src/compile/build/server/buildServerScriptNode.ts
|
|
1989
|
+
function buildServerScriptNode(node, b) {
|
|
1990
|
+
b.append(`${maybeAppend(node.statement, ";")}`);
|
|
1991
|
+
}
|
|
1992
|
+
function maybeAppend(text, end) {
|
|
1993
|
+
if (!text.endsWith(end)) text += end;
|
|
1994
|
+
return text;
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
//#endregion
|
|
1998
|
+
//#region src/compile/build/server/buildServerSwitchNode.ts
|
|
1999
|
+
function buildServerSwitchNode(node, status, b) {
|
|
2000
|
+
status.output += HYDRATION_START_COMMENT;
|
|
2001
|
+
if (status.output) {
|
|
2002
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2003
|
+
status.output = "";
|
|
2004
|
+
}
|
|
2005
|
+
b.append(`${node.statement} {`);
|
|
2006
|
+
for (let branch of node.children.filter((c) => isControlNode(c))) if (isControlNode(branch)) buildServerSwitchBranch(branch, status, b);
|
|
2007
|
+
b.append("}");
|
|
2008
|
+
status.output += HYDRATION_END_COMMENT;
|
|
2009
|
+
status.output += ANCHOR_COMMENT;
|
|
2010
|
+
}
|
|
2011
|
+
function buildServerSwitchBranch(node, status, b) {
|
|
2012
|
+
b.append(`${node.statement} {`);
|
|
2013
|
+
status.output += HYDRATION_BREAK_COMMENT;
|
|
2014
|
+
for (let child of node.children) buildServerNode(child, status, b);
|
|
2015
|
+
if (status.output) {
|
|
2016
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2017
|
+
status.output = "";
|
|
2018
|
+
}
|
|
2019
|
+
b.append("break;");
|
|
2020
|
+
b.append("}");
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
//#endregion
|
|
2024
|
+
//#region src/compile/build/server/buildServerControlNode.ts
|
|
2025
|
+
function buildServerControlNode(node, status, b) {
|
|
2026
|
+
switch (node.operation) {
|
|
2027
|
+
case "@if group":
|
|
2028
|
+
buildServerIfNode(node, status, b);
|
|
2029
|
+
break;
|
|
2030
|
+
case "@if":
|
|
2031
|
+
case "@else if":
|
|
2032
|
+
case "@else": break;
|
|
2033
|
+
case "@switch group":
|
|
2034
|
+
buildServerSwitchNode(node, status, b);
|
|
2035
|
+
break;
|
|
2036
|
+
case "@case":
|
|
2037
|
+
case "@default": break;
|
|
2038
|
+
case "@for group":
|
|
2039
|
+
buildServerForNode(node, status, b);
|
|
2040
|
+
break;
|
|
2041
|
+
case "@for":
|
|
2042
|
+
case "@key": break;
|
|
2043
|
+
case "@await group":
|
|
2044
|
+
buildServerAwaitNode(node, status, b);
|
|
2045
|
+
break;
|
|
2046
|
+
case "@await":
|
|
2047
|
+
case "@then":
|
|
2048
|
+
case "@catch": break;
|
|
2049
|
+
case "@replace group":
|
|
2050
|
+
buildServerReplaceNode(node, status, b);
|
|
2051
|
+
break;
|
|
2052
|
+
case "@replace": break;
|
|
2053
|
+
case "@html group":
|
|
2054
|
+
buildServerHtmlNode(node, status);
|
|
2055
|
+
break;
|
|
2056
|
+
case "@html": break;
|
|
2057
|
+
case "@const":
|
|
2058
|
+
if (status.output) {
|
|
2059
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2060
|
+
status.output = "";
|
|
2061
|
+
}
|
|
2062
|
+
buildServerScriptNode(node, b);
|
|
2063
|
+
break;
|
|
2064
|
+
case "@function":
|
|
2065
|
+
case "@async function":
|
|
2066
|
+
if (status.output) {
|
|
2067
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2068
|
+
status.output = "";
|
|
2069
|
+
}
|
|
2070
|
+
b.append("");
|
|
2071
|
+
buildServerScriptNode(node, b);
|
|
2072
|
+
b.append("");
|
|
2073
|
+
break;
|
|
2074
|
+
case "@console":
|
|
2075
|
+
case "@debugger": break;
|
|
2076
|
+
default: throw new Error(`Invalid operation: ${node.operation}`);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
//#endregion
|
|
2081
|
+
//#region src/compile/utils/voidTags.ts
|
|
2082
|
+
const voidTags = [
|
|
2083
|
+
"area",
|
|
2084
|
+
"base",
|
|
2085
|
+
"br",
|
|
2086
|
+
"col",
|
|
2087
|
+
"embed",
|
|
2088
|
+
"hr",
|
|
2089
|
+
"img",
|
|
2090
|
+
"input",
|
|
2091
|
+
"link",
|
|
2092
|
+
"meta",
|
|
2093
|
+
"param",
|
|
2094
|
+
"source",
|
|
2095
|
+
"track",
|
|
2096
|
+
"wbr"
|
|
2097
|
+
];
|
|
2098
|
+
var voidTags_default = voidTags;
|
|
2099
|
+
|
|
2100
|
+
//#endregion
|
|
2101
|
+
//#region src/compile/build/server/buildServerElementNode.ts
|
|
2102
|
+
function buildServerElementNode(node, status, b) {
|
|
2103
|
+
let attributes = buildElementAttributes(node, status);
|
|
2104
|
+
if (attributes.length) attributes = " " + attributes;
|
|
2105
|
+
let tagName = node.tagName;
|
|
2106
|
+
if (tagName === "@element") {
|
|
2107
|
+
let selfAttribute = node.attributes.find((a) => a.name === "self");
|
|
2108
|
+
if (selfAttribute) tagName = `$\{${selfAttribute.value}}`;
|
|
2109
|
+
}
|
|
2110
|
+
status.output += `<${tagName}${attributes}>`;
|
|
2111
|
+
if (!voidTags_default.includes(tagName)) {
|
|
2112
|
+
const oldPreserveWhitespace = status.preserveWhitespace;
|
|
2113
|
+
status.preserveWhitespace = ["pre", "code"].includes(tagName);
|
|
2114
|
+
for (let child of node.children) buildServerNode(child, status, b);
|
|
2115
|
+
status.preserveWhitespace = oldPreserveWhitespace;
|
|
2116
|
+
status.output += `</${tagName}>`;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function buildElementAttributes(node, status) {
|
|
2120
|
+
let needsClass = node.scopeStyles;
|
|
2121
|
+
let attributes = [];
|
|
2122
|
+
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) {
|
|
2123
|
+
value = value.split(",")[0].trim();
|
|
2124
|
+
if (value.startsWith("[")) value = value.substring(1);
|
|
2125
|
+
else value = value + "[0]";
|
|
2126
|
+
attributes.push(`style="\${Object.entries(${value}).map(([k, v]) => \`$\{k}: $\{v}\`).join("; ")}"`);
|
|
2127
|
+
} else if (value != null && fullyReactive) if (name === "&value" || name === "&checked" || name === "&group") {
|
|
2128
|
+
let defaultValue = "";
|
|
2129
|
+
let typeAttribute = node.attributes.find((a) => a.name === "type");
|
|
2130
|
+
if (typeAttribute && typeAttribute.value) switch (trimQuotes(typeAttribute.value)) {
|
|
2131
|
+
case "number":
|
|
2132
|
+
defaultValue = "0";
|
|
2133
|
+
break;
|
|
2134
|
+
case "checkbox":
|
|
2135
|
+
defaultValue = "false";
|
|
2136
|
+
break;
|
|
2137
|
+
}
|
|
2138
|
+
if (!defaultValue) {
|
|
2139
|
+
status.imports.add("t_attr");
|
|
2140
|
+
value = `t_attr(${value})`;
|
|
2141
|
+
defaultValue = "\"\"";
|
|
2142
|
+
}
|
|
2143
|
+
let valueOrDefault = `${value} || ${defaultValue}`;
|
|
2144
|
+
const propName = name.substring(1);
|
|
2145
|
+
attributes.push(`${propName}="\${${valueOrDefault}}"`);
|
|
2146
|
+
} else if (name === "class") {
|
|
2147
|
+
status.imports.add("t_class");
|
|
2148
|
+
let params = [value];
|
|
2149
|
+
if (node.scopeStyles) {
|
|
2150
|
+
params.push(`"torp-${status.styleHash}"`);
|
|
2151
|
+
needsClass = false;
|
|
2152
|
+
}
|
|
2153
|
+
attributes.push(`class="\${t_class(${params.join(", ")})}"`);
|
|
2154
|
+
} else if (name === "style") {
|
|
2155
|
+
status.imports.add("t_style");
|
|
2156
|
+
attributes.push(`style="\${t_style(${value})}"`);
|
|
2157
|
+
} else {
|
|
2158
|
+
status.imports.add("t_attr");
|
|
2159
|
+
attributes.push(`\${${value} ? \`${name}="\${t_attr(${value})}"\` : ''}`);
|
|
2160
|
+
}
|
|
2161
|
+
else if (value != null && reactive) {
|
|
2162
|
+
status.imports.add("t_attr");
|
|
2163
|
+
attributes.push(`${name}="${trimQuotes(value).replaceAll("${", "${t_attr(").replaceAll("}", ")}")}"`);
|
|
2164
|
+
} else if (name === "class" && node.scopeStyles) {
|
|
2165
|
+
value = value ? `"${trimQuotes(value)} torp-${status.styleHash}"` : `"torp-${status.styleHash}"`;
|
|
2166
|
+
attributes.push(`${name}="${trimQuotes(value).replaceAll("\"", """)}"`);
|
|
2167
|
+
needsClass = false;
|
|
2168
|
+
} else if (value != null) attributes.push(`${name}="${trimQuotes(value).replaceAll("\"", """)}"`);
|
|
2169
|
+
else attributes.push(`${name}`);
|
|
2170
|
+
if (needsClass) attributes.push(`class="torp-${status.styleHash}"`);
|
|
2171
|
+
return attributes.join(" ");
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
//#endregion
|
|
2175
|
+
//#region src/compile/build/server/buildServerRootNode.ts
|
|
2176
|
+
function buildServerRootNode(node, status, b) {
|
|
2177
|
+
for (let child of node.children) buildServerNode(child, status, b);
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
//#endregion
|
|
2181
|
+
//#region src/compile/build/server/buildServerSlotNode.ts
|
|
2182
|
+
function buildServerSlotNode(node, status, b) {
|
|
2183
|
+
status.output += HYDRATION_START_COMMENT;
|
|
2184
|
+
if (status.output) {
|
|
2185
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2186
|
+
status.output = "";
|
|
2187
|
+
}
|
|
2188
|
+
let slotName = node.attributes.find((a) => a.name === "name")?.value;
|
|
2189
|
+
slotName = slotName ? trimQuotes(slotName) : "_";
|
|
2190
|
+
const propsName = nextVarName("sprops", status);
|
|
2191
|
+
const slotAttributes = node.attributes.filter((a) => a.name !== "name");
|
|
2192
|
+
const slotHasProps = slotAttributes.length;
|
|
2193
|
+
if (slotHasProps) {
|
|
2194
|
+
b.append(`const ${propsName}: any = {};`);
|
|
2195
|
+
for (let { name, value } of slotAttributes) if (value != null) b.append(`${propsName}["${name}"] = ${value};`);
|
|
2196
|
+
else b.append(`${propsName}["${name}"] = true;`);
|
|
2197
|
+
}
|
|
2198
|
+
b.append(`if ($slots && $slots["${slotName}"]) {`);
|
|
2199
|
+
b.append(`t_body += $slots["${slotName}"](${slotHasProps ? propsName : "undefined"}, $context);`);
|
|
2200
|
+
const fill = node.children.find((c) => isSpecialNode(c) && c.tagName === "fill");
|
|
2201
|
+
if (fill && isSpecialNode(fill) && fill.children.length) {
|
|
2202
|
+
b.append(`} else {`);
|
|
2203
|
+
for (let child of fill.children) buildServerNode(child, status, b);
|
|
2204
|
+
if (status.output) {
|
|
2205
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2206
|
+
status.output = "";
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
b.append(`}`);
|
|
2210
|
+
status.output += HYDRATION_END_COMMENT;
|
|
2211
|
+
status.output += ANCHOR_COMMENT;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
//#endregion
|
|
2215
|
+
//#region src/compile/build/server/buildServerSpecialNode.ts
|
|
2216
|
+
function buildServerSpecialNode(node, status, b) {
|
|
2217
|
+
switch (node.tagName) {
|
|
2218
|
+
case "slot":
|
|
2219
|
+
buildServerSlotNode(node, status, b);
|
|
2220
|
+
break;
|
|
2221
|
+
case "fill": break;
|
|
2222
|
+
case "@element":
|
|
2223
|
+
buildServerElementNode(node, status, b);
|
|
2224
|
+
break;
|
|
2225
|
+
case "@component":
|
|
2226
|
+
buildServerComponentNode(node, status, b);
|
|
2227
|
+
break;
|
|
2228
|
+
default: throw new Error(`Invalid special node: ${node.tagName}`);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
//#endregion
|
|
2233
|
+
//#region src/compile/build/server/buildServerTextNode.ts
|
|
2234
|
+
function buildServerTextNode(node, status) {
|
|
2235
|
+
let content = node.content || "";
|
|
2236
|
+
if (!status.preserveWhitespace) content = content.replaceAll(/\s+/g, " ");
|
|
2237
|
+
let level = 0;
|
|
2238
|
+
for (let i = 0; i < content.length; i++) {
|
|
2239
|
+
const char = content[i];
|
|
2240
|
+
const nextChar = content[i + 1];
|
|
2241
|
+
if (char === "{") {
|
|
2242
|
+
level++;
|
|
2243
|
+
if (level === 1) {
|
|
2244
|
+
status.imports.add("t_fmt");
|
|
2245
|
+
status.output += "${t_fmt(";
|
|
2246
|
+
continue;
|
|
2247
|
+
}
|
|
2248
|
+
} else if (char === "}") {
|
|
2249
|
+
level--;
|
|
2250
|
+
if (level === 0) status.output += ")";
|
|
2251
|
+
} else if (char === "`" && level === 0) status.output += "\\";
|
|
2252
|
+
else if (level > 0) {
|
|
2253
|
+
if (char === "/" && nextChar === "/") {
|
|
2254
|
+
let start = i;
|
|
2255
|
+
i = content.indexOf("\n", i);
|
|
2256
|
+
status.output += escapeHtml(content.substring(start, i + 1));
|
|
2257
|
+
continue;
|
|
2258
|
+
} else if (char === "/" && nextChar === "*") {
|
|
2259
|
+
let start = i;
|
|
2260
|
+
i = content.indexOf("*/", i) + 1;
|
|
2261
|
+
status.output += escapeHtml(content.substring(start, i + 1));
|
|
2262
|
+
continue;
|
|
2263
|
+
} else if (char === "\"" || char === "'") {
|
|
2264
|
+
let start = i;
|
|
2265
|
+
i = endOfString(char, content, i);
|
|
2266
|
+
status.output += escapeHtml(content.substring(start, i + 1));
|
|
2267
|
+
continue;
|
|
2268
|
+
} else if (char === "`") {
|
|
2269
|
+
let start = i;
|
|
2270
|
+
i = endOfTemplateString(content, i);
|
|
2271
|
+
status.output += escapeHtml(content.substring(start, i + 1));
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
status.output += char;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
function escapeHtml(text) {
|
|
2279
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
//#endregion
|
|
2283
|
+
//#region src/compile/build/server/buildServerNode.ts
|
|
2284
|
+
function buildServerNode(node, status, b) {
|
|
2285
|
+
switch (node.type) {
|
|
2286
|
+
case "root":
|
|
2287
|
+
buildServerRootNode(node, status, b);
|
|
2288
|
+
break;
|
|
2289
|
+
case "control":
|
|
2290
|
+
buildServerControlNode(node, status, b);
|
|
2291
|
+
break;
|
|
2292
|
+
case "component":
|
|
2293
|
+
buildServerComponentNode(node, status, b);
|
|
2294
|
+
break;
|
|
2295
|
+
case "element":
|
|
2296
|
+
buildServerElementNode(node, status, b);
|
|
2297
|
+
break;
|
|
2298
|
+
case "text":
|
|
2299
|
+
buildServerTextNode(node, status);
|
|
2300
|
+
break;
|
|
2301
|
+
case "special":
|
|
2302
|
+
buildServerSpecialNode(node, status, b);
|
|
2303
|
+
break;
|
|
2304
|
+
default: throw new Error(`Invalid node type: ${node.type}`);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
//#endregion
|
|
2309
|
+
//#region src/compile/build/server/buildServerCode.ts
|
|
2310
|
+
const importsMap = {
|
|
2311
|
+
$watch: "import { $watch } from \"${folder}\";",
|
|
2312
|
+
$cache: "import { $cache } from \"${folder}\";",
|
|
2313
|
+
$run: "import { $run } from \"${folder}\";",
|
|
2314
|
+
$mount: "import { $mount } from \"${folder}\";",
|
|
2315
|
+
$unwrap: "import { $unwrap } from \"${folder}\";",
|
|
2316
|
+
$peek: "import { $peek } from \"${folder}\";",
|
|
2317
|
+
$batch: "import { $batch } from \"${folder}\";",
|
|
2318
|
+
t_fmt: "import { t_fmt } from \"${folder}\";",
|
|
2319
|
+
t_attr: "import { t_attr } from \"${folder}\";",
|
|
2320
|
+
t_class: "import { t_class } from \"${folder}\";",
|
|
2321
|
+
t_style: "import { t_style } from \"${folder}\";",
|
|
2322
|
+
ServerSlotRender: "import { type ServerSlotRender } from \"${folder}\";"
|
|
2323
|
+
};
|
|
2324
|
+
function buildServerCode(template, options) {
|
|
2325
|
+
let b = new Builder(options?.mapped);
|
|
2326
|
+
let imports = /* @__PURE__ */ new Set();
|
|
2327
|
+
imports.add("ServerSlotRender");
|
|
2328
|
+
buildServerTemplate(template, imports, b, options);
|
|
2329
|
+
if (imports.size) {
|
|
2330
|
+
b.prepend("");
|
|
2331
|
+
for (let imp of Array.from(imports).sort().reverse()) {
|
|
2332
|
+
imp = importsMap[imp] || imp;
|
|
2333
|
+
b.prepend(imp.replace("${folder}", options?.renderFolder || "@torpor/view/ssr"));
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
return b.toString();
|
|
2337
|
+
}
|
|
2338
|
+
function buildServerTemplate(template, imports, b, options) {
|
|
2339
|
+
let script = template.script.map((s) => s.script).join("\n");
|
|
2340
|
+
if (/\$watch\b/.test(script)) imports.add("$watch");
|
|
2341
|
+
if (/\$cache\b/.test(script)) imports.add("$cache");
|
|
2342
|
+
if (/\$run\b/.test(script)) imports.add("$run");
|
|
2343
|
+
if (/\$mount\b/.test(script)) imports.add("$mount");
|
|
2344
|
+
if (/\$unwrap\b/.test(script)) imports.add("$unwrap");
|
|
2345
|
+
if (/\$peek\b/.test(script)) imports.add("$peek");
|
|
2346
|
+
if (/\$batch\b/.test(script)) imports.add("$batch");
|
|
2347
|
+
let currentIndex = 0;
|
|
2348
|
+
let current = template.components[0];
|
|
2349
|
+
for (let chunk of template.script) if (chunk.script === "/* @params */") {
|
|
2350
|
+
let params = [
|
|
2351
|
+
current.params ?? `${current.props?.length ? "$props: Record<PropertyKey, any>" : "_$props: Record<PropertyKey, any> | undefined"}`,
|
|
2352
|
+
`${current.contextProps?.length || current.needsContext ? "$context" : "_$context"}: Record<PropertyKey, any>`,
|
|
2353
|
+
`${current.slotProps?.length ? "$slots" : "_$slots"}?: Record<string, ServerSlotRender>`
|
|
2354
|
+
];
|
|
2355
|
+
b.append(params.join(",\n"));
|
|
2356
|
+
} else if (chunk.script === ") /* @return_type */ {") b.append("): { body: string, head: string } {");
|
|
2357
|
+
else if (chunk.script === "/* @start */") {
|
|
2358
|
+
if (current.contextProps?.length) b.append(`$context = Object.assign({}, $context);`);
|
|
2359
|
+
b.append(`let t_body = "";`);
|
|
2360
|
+
b.append(`let t_head = "";`);
|
|
2361
|
+
} else if (chunk.script === "/* @render */") {
|
|
2362
|
+
if (current.markup) {
|
|
2363
|
+
const status = {
|
|
2364
|
+
imports,
|
|
2365
|
+
output: "",
|
|
2366
|
+
styleHash: current.style?.hash || "",
|
|
2367
|
+
varNames: {},
|
|
2368
|
+
preserveWhitespace: false,
|
|
2369
|
+
options
|
|
2370
|
+
};
|
|
2371
|
+
b.append("");
|
|
2372
|
+
b.append("/* User interface */");
|
|
2373
|
+
buildServerNode(current.markup, status, b);
|
|
2374
|
+
if (status.output) {
|
|
2375
|
+
b.append(`t_body += \`${status.output}\`;`);
|
|
2376
|
+
status.output = "";
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
} else if (chunk.script === "/* @head */") {} else if (chunk.script === "/* @style */") {
|
|
2380
|
+
if (current.style) {
|
|
2381
|
+
b.append("");
|
|
2382
|
+
b.append("/* Style */");
|
|
2383
|
+
const styles = buildStyles(current.style, current.style.hash).replaceAll("\"", "\\\"").replaceAll(/\s+/g, " ");
|
|
2384
|
+
b.append(`t_head += "<style id='${current.style.hash}'>${styles}</style>";\n`);
|
|
2385
|
+
}
|
|
2386
|
+
} else if (chunk.script === "/* @end */") {
|
|
2387
|
+
b.append(`return { body: t_body, head: t_head };`);
|
|
2388
|
+
currentIndex += 1;
|
|
2389
|
+
current = template.components[currentIndex];
|
|
2390
|
+
} else b.append(chunk.script);
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
//#endregion
|
|
2394
|
+
//#region src/compile/build.ts
|
|
2395
|
+
/**
|
|
2396
|
+
* Builds a component template into code and styles for rendering
|
|
2397
|
+
*
|
|
2398
|
+
* @param template The component template, possibly including script, markup and styles
|
|
2399
|
+
*
|
|
2400
|
+
* @returns The component's code and styles
|
|
2401
|
+
*/
|
|
2402
|
+
function build(template, options) {
|
|
2403
|
+
let map = [];
|
|
2404
|
+
let code = options?.server ? buildServerCode(template, options) : buildCode(template, map, options);
|
|
2405
|
+
let styles = template.components.map((c) => c.style ? {
|
|
2406
|
+
style: buildStyles(c.style, c.style.hash),
|
|
2407
|
+
hash: c.style.hash
|
|
2408
|
+
} : void 0).filter((c) => c !== void 0);
|
|
2409
|
+
return {
|
|
2410
|
+
code,
|
|
2411
|
+
styles,
|
|
2412
|
+
map
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
//#endregion
|
|
2417
|
+
//#region src/compile/buildType.ts
|
|
2418
|
+
/**
|
|
2419
|
+
* Builds a component template into a d.ts file
|
|
2420
|
+
*
|
|
2421
|
+
* @param name The name of the component
|
|
2422
|
+
* @param template The component template, possibly including script, markup and styles
|
|
2423
|
+
*
|
|
2424
|
+
* @returns The d.ts file content
|
|
2425
|
+
*/
|
|
2426
|
+
function buildType(template, options) {
|
|
2427
|
+
let b = new Builder();
|
|
2428
|
+
b.append(`import { type SlotRender } from "${options?.renderFolder || "@torpor/view"}";`);
|
|
2429
|
+
for (let component of template.components) {
|
|
2430
|
+
b.append("");
|
|
2431
|
+
b.append(`
|
|
2432
|
+
/**
|
|
2433
|
+
* Mounts or hydrates the component into the supplied parent node.
|
|
2434
|
+
* @param $parent -- The parent node.
|
|
2435
|
+
* @param $anchor -- The node to mount the component before.
|
|
2436
|
+
* @param $props -- The values that have been passed into the component as properties.
|
|
2437
|
+
* @param $context -- Values that have been passed into the component from its ancestors.
|
|
2438
|
+
* @param $slots -- Functions for rendering children into slot nodes within the component.
|
|
2439
|
+
*/
|
|
2440
|
+
${component.default ? "declare" : "export declare"} function ${component.name}(
|
|
2441
|
+
$parent: ParentNode,
|
|
2442
|
+
$anchor: Node | null,
|
|
2443
|
+
${component.params || "$props?: Record<PropertyKey, any>"},
|
|
2444
|
+
$context?: Record<PropertyKey, any>,
|
|
2445
|
+
$slots?: Record<string, SlotRender>,
|
|
2446
|
+
): void;
|
|
2447
|
+
`);
|
|
2448
|
+
if (component.default) b.append(`export default ${component.name};`);
|
|
2449
|
+
}
|
|
2450
|
+
return b.toString();
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
//#endregion
|
|
2454
|
+
//#region src/compile/utils/isSpaceNode.ts
|
|
2455
|
+
function isSpaceNode(node) {
|
|
2456
|
+
return isTextNode(node) && isSpace(node.content);
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
//#endregion
|
|
2460
|
+
//#region src/compile/utils/trimStart.ts
|
|
2461
|
+
/**
|
|
2462
|
+
* Trims a char from the start of the supplied text
|
|
2463
|
+
*
|
|
2464
|
+
* @param text The text to trim
|
|
2465
|
+
* @param char The char to remove from the start of the text
|
|
2466
|
+
*
|
|
2467
|
+
* @returns The text without the char on the start
|
|
2468
|
+
*/
|
|
2469
|
+
function trimStart(text, char) {
|
|
2470
|
+
let start = 0;
|
|
2471
|
+
while (text[start] === char) start += 1;
|
|
2472
|
+
return text.substring(start);
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
//#endregion
|
|
2476
|
+
//#region src/compile/parse/utils/consumeSpace.ts
|
|
2477
|
+
/**
|
|
2478
|
+
* Consumes space characters and advances the current parse position to before
|
|
2479
|
+
* the next non-space character
|
|
2480
|
+
*
|
|
2481
|
+
* @param status The parse status
|
|
2482
|
+
*
|
|
2483
|
+
* @returns The string of space characters from the current parse position to
|
|
2484
|
+
* the next non-space character
|
|
2485
|
+
*/
|
|
2486
|
+
function consumeSpace(status) {
|
|
2487
|
+
const start = status.i;
|
|
2488
|
+
for (status.i; status.i < status.source.length; status.i++) if (!isSpaceChar(status.source, status.i)) return status.source.substring(start, status.i);
|
|
2489
|
+
return "";
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
//#endregion
|
|
2493
|
+
//#region src/compile/parse/addSpaceElement.ts
|
|
2494
|
+
function addSpaceElement(parent, status) {
|
|
2495
|
+
const content = consumeSpace(status);
|
|
2496
|
+
if (content) {
|
|
2497
|
+
const previousNode = parent.children[parent.children.length - 1];
|
|
2498
|
+
if (previousNode && isTextNode(previousNode)) previousNode.content += content;
|
|
2499
|
+
else {
|
|
2500
|
+
const space = {
|
|
2501
|
+
type: "text",
|
|
2502
|
+
content,
|
|
2503
|
+
ranges: []
|
|
2504
|
+
};
|
|
2505
|
+
parent.children.push(space);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
//#endregion
|
|
2511
|
+
//#region src/compile/utils/isFullyReactive.ts
|
|
2512
|
+
/**
|
|
2513
|
+
* Checks whether the supplied string is fully reactive (i.e. starts and ends with braces)
|
|
2514
|
+
*
|
|
2515
|
+
* @param text The string to check
|
|
2516
|
+
*
|
|
2517
|
+
* @returns True if the supplied string is fully reactive
|
|
2518
|
+
*/
|
|
2519
|
+
function isFullyReactive(text) {
|
|
2520
|
+
return text.trim().startsWith("{") && text.trim().endsWith("}");
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
//#endregion
|
|
2524
|
+
//#region src/compile/parse/utils/accept.ts
|
|
2525
|
+
/**
|
|
2526
|
+
* Checks whether the source at the current parse position matches the supplied
|
|
2527
|
+
* string
|
|
2528
|
+
*
|
|
2529
|
+
* @param value The string to check
|
|
2530
|
+
* @param status The parse status
|
|
2531
|
+
* @param advance (Optional) If true, advances the parse position past the
|
|
2532
|
+
* checked string (defaults to true)
|
|
2533
|
+
*
|
|
2534
|
+
* @returns True if the supplied string was found at the current parse position
|
|
2535
|
+
*/
|
|
2536
|
+
function accept(value, status, advance = true) {
|
|
2537
|
+
if (status.source.substring(status.i, status.i + value.length) == value) {
|
|
2538
|
+
status.i += advance ? value.length : 0;
|
|
2539
|
+
return true;
|
|
2540
|
+
}
|
|
2541
|
+
return false;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
//#endregion
|
|
2545
|
+
//#region src/compile/parse/parseInlineScript.ts
|
|
2546
|
+
function parseInlineScript(status) {
|
|
2547
|
+
const start = status.i;
|
|
2548
|
+
let braceCount = 0;
|
|
2549
|
+
while (status.i < status.source.length) if (accept("{", status)) braceCount += 1;
|
|
2550
|
+
else if (accept("}", status)) if (braceCount > 0) braceCount -= 1;
|
|
2551
|
+
else return status.source.substring(start, status.i - 1);
|
|
2552
|
+
else if (accept("//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
|
|
2553
|
+
else if (accept("/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
|
|
2554
|
+
else if (accept("\"", status) || accept("'", status)) {
|
|
2555
|
+
const char = status.source[status.i - 1];
|
|
2556
|
+
status.i = endOfString(char, status.source, status.i - 1) + 1;
|
|
2557
|
+
} else if (accept("`", status)) status.i = endOfTemplateString(status.source, status.i - 1) + 1;
|
|
2558
|
+
else status.i += 1;
|
|
2559
|
+
return "";
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
//#endregion
|
|
2563
|
+
//#region src/compile/parse/utils/isAlphaNumericChar.ts
|
|
2564
|
+
/**
|
|
2565
|
+
* Checks whether the supplied character is a alpha-numeric
|
|
2566
|
+
*
|
|
2567
|
+
* @param text The string to check
|
|
2568
|
+
* @param index The index of the character in the string
|
|
2569
|
+
*
|
|
2570
|
+
* @returns True if the supplied character is alpha-numeric
|
|
2571
|
+
*/
|
|
2572
|
+
function isAlphaNumericChar(text, index) {
|
|
2573
|
+
let code = text.charCodeAt(index);
|
|
2574
|
+
return code > 47 && code < 58 || code > 64 && code < 91 || code > 96 && code < 123;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
//#endregion
|
|
2578
|
+
//#region src/compile/parse/utils/consumeAlphaNumeric.ts
|
|
2579
|
+
/**
|
|
2580
|
+
* Consumes alpha-numeric characters and advances the current parse position to
|
|
2581
|
+
* before the next non-alpha character
|
|
2582
|
+
*
|
|
2583
|
+
* @param status The parse status
|
|
2584
|
+
*
|
|
2585
|
+
* @returns The string of alpha-numeric characters from the current parse position
|
|
2586
|
+
* to the next non-alpha character
|
|
2587
|
+
*/
|
|
2588
|
+
function consumeAlphaNumeric(status) {
|
|
2589
|
+
const start = status.i;
|
|
2590
|
+
for (status.i; status.i < status.source.length; status.i++) if (!isAlphaNumericChar(status.source, status.i)) return status.source.substring(start, status.i);
|
|
2591
|
+
return "";
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
//#endregion
|
|
2595
|
+
//#region src/compile/parse/utils/consumeUntil.ts
|
|
2596
|
+
/**
|
|
2597
|
+
* Consumes characters until one of the characters in the supplied string is
|
|
2598
|
+
* found. The parse position will be advanced to before the found character
|
|
2599
|
+
*
|
|
2600
|
+
* @param chars The characters to match
|
|
2601
|
+
* @param status The parse status
|
|
2602
|
+
*
|
|
2603
|
+
* @returns The string of characters from the current parse position to the
|
|
2604
|
+
* found character
|
|
2605
|
+
*/
|
|
2606
|
+
function consumeUntil(chars, status) {
|
|
2607
|
+
const start = status.i;
|
|
2608
|
+
for (status.i; status.i < status.source.length; status.i++) if (chars.includes(status.source[status.i])) return status.source.substring(start, status.i);
|
|
2609
|
+
return "";
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
//#endregion
|
|
2613
|
+
//#region src/compile/parse/parseTag.ts
|
|
2614
|
+
function parseTag(status) {
|
|
2615
|
+
accept("<", status);
|
|
2616
|
+
let closeTag = accept("/", status);
|
|
2617
|
+
consumeSpace(status);
|
|
2618
|
+
let tagStart = status.i;
|
|
2619
|
+
let snailed = accept("@", status);
|
|
2620
|
+
let tagName = consumeAlphaNumeric(status);
|
|
2621
|
+
let tagEnd = status.i;
|
|
2622
|
+
if (snailed) tagName = "@" + tagName;
|
|
2623
|
+
if (closeTag) tagName = "/" + tagName;
|
|
2624
|
+
consumeSpace(status);
|
|
2625
|
+
let attributes = parseTagAttributes(status);
|
|
2626
|
+
let selfClosed = false;
|
|
2627
|
+
if (accept("/>", status)) selfClosed = true;
|
|
2628
|
+
else accept(">", status);
|
|
2629
|
+
if (closeTag) selfClosed = true;
|
|
2630
|
+
return {
|
|
2631
|
+
type: [
|
|
2632
|
+
"slot",
|
|
2633
|
+
"fill",
|
|
2634
|
+
"@component",
|
|
2635
|
+
"@element"
|
|
2636
|
+
].includes(tagName) ? "special" : "element",
|
|
2637
|
+
tagName,
|
|
2638
|
+
selfClosed: selfClosed || void 0,
|
|
2639
|
+
closed: selfClosed || void 0,
|
|
2640
|
+
attributes,
|
|
2641
|
+
children: [],
|
|
2642
|
+
range: {
|
|
2643
|
+
start: tagStart,
|
|
2644
|
+
end: tagEnd
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
function parseTagAttributes(status) {
|
|
2649
|
+
let attributes = [];
|
|
2650
|
+
while (status.i < status.source.length) {
|
|
2651
|
+
consumeSpace(status);
|
|
2652
|
+
if (accept("@//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
|
|
2653
|
+
else if (accept("@/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
|
|
2654
|
+
else if (accept("/>", status, false) || accept(">", status, false)) break;
|
|
2655
|
+
else {
|
|
2656
|
+
const attribute = parseAttribute(status);
|
|
2657
|
+
attributes.push(attribute);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
return attributes;
|
|
2661
|
+
}
|
|
2662
|
+
function parseAttribute(status) {
|
|
2663
|
+
let valueStart = status.i;
|
|
2664
|
+
let valueEnd = status.i;
|
|
2665
|
+
let name = consumeUntil("= \r\n/>", status);
|
|
2666
|
+
let value = void 0;
|
|
2667
|
+
let reactive = false;
|
|
2668
|
+
let fullyReactive = false;
|
|
2669
|
+
consumeSpace(status);
|
|
2670
|
+
if (accept("=", status)) {
|
|
2671
|
+
consumeSpace(status);
|
|
2672
|
+
valueStart = status.i;
|
|
2673
|
+
value = parseAttributeValue(status);
|
|
2674
|
+
valueEnd = status.i;
|
|
2675
|
+
reactive = isReactive(value);
|
|
2676
|
+
fullyReactive = isFullyReactive(value);
|
|
2677
|
+
if (fullyReactive) {
|
|
2678
|
+
value = value.substring(1, value.length - 1);
|
|
2679
|
+
valueStart++;
|
|
2680
|
+
valueEnd--;
|
|
2681
|
+
} else if (reactive) value = `\`${trimQuotes(value).replaceAll("{", "${")}\``;
|
|
2682
|
+
} else if (name.startsWith("{") && name.endsWith("}")) {
|
|
2683
|
+
value = name.substring(1, name.length - 1);
|
|
2684
|
+
valueStart++;
|
|
2685
|
+
valueEnd = valueStart + name.length;
|
|
2686
|
+
name = value.split(".").at(-1);
|
|
2687
|
+
reactive = fullyReactive = true;
|
|
2688
|
+
}
|
|
2689
|
+
return {
|
|
2690
|
+
name,
|
|
2691
|
+
value,
|
|
2692
|
+
reactive,
|
|
2693
|
+
fullyReactive,
|
|
2694
|
+
range: {
|
|
2695
|
+
start: valueStart,
|
|
2696
|
+
end: valueEnd
|
|
2697
|
+
}
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
function parseAttributeValue(status) {
|
|
2701
|
+
if (accept("\"", status)) {
|
|
2702
|
+
const value = consumeUntil("\"", status);
|
|
2703
|
+
accept("\"", status);
|
|
2704
|
+
return `"${value}"`;
|
|
2705
|
+
} else if (accept("'", status)) {
|
|
2706
|
+
const value = consumeUntil("'", status);
|
|
2707
|
+
accept("'", status);
|
|
2708
|
+
return `'${value}'`;
|
|
2709
|
+
} else if (accept("{", status)) return `{${parseInlineScript(status)}}`;
|
|
2710
|
+
else {
|
|
2711
|
+
let value = consumeUntil(" \r\n>", status);
|
|
2712
|
+
if (status.source.substring(status.i - 1, status.i + 1) === "/>") {
|
|
2713
|
+
value = value.substring(0, value.length - 1);
|
|
2714
|
+
status.i -= 1;
|
|
2715
|
+
}
|
|
2716
|
+
return value;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
//#endregion
|
|
2721
|
+
//#region src/compile/parse/parseText.ts
|
|
2722
|
+
function parseText(status, element) {
|
|
2723
|
+
let ranges = [];
|
|
2724
|
+
const start = status.i;
|
|
2725
|
+
let end = -1;
|
|
2726
|
+
for (let j = status.i; j < status.source.length; j++) {
|
|
2727
|
+
if (status.source[j] === "}") end = j;
|
|
2728
|
+
else if (status.source[j] === "{") {
|
|
2729
|
+
const reactiveStart = j + 1;
|
|
2730
|
+
let level = 0;
|
|
2731
|
+
for (let k = j; k < status.source.length; k++) {
|
|
2732
|
+
const char = status.source[k];
|
|
2733
|
+
const nextChar = status.source[k + 1];
|
|
2734
|
+
if (char === "/" && nextChar === "/") k = status.source.indexOf("\n", k);
|
|
2735
|
+
else if (char === "/" && nextChar === "*") k = status.source.indexOf("*/", k) + 1;
|
|
2736
|
+
else if (char === "\"" || char === "'") k = endOfString(char, status.source, k);
|
|
2737
|
+
else if (char === "`") k = endOfTemplateString(status.source, k);
|
|
2738
|
+
else if (char === "{") level += 1;
|
|
2739
|
+
else if (char === "}") {
|
|
2740
|
+
level -= 1;
|
|
2741
|
+
if (level === 0) {
|
|
2742
|
+
j = k;
|
|
2743
|
+
break;
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
ranges.push({
|
|
2748
|
+
start: reactiveStart,
|
|
2749
|
+
end: j
|
|
2750
|
+
});
|
|
2751
|
+
} else if (status.source[j] === "<") end = j;
|
|
2752
|
+
else if (status.source[j] === "@") {
|
|
2753
|
+
for (let k = j - 1; k > status.i; k--) if (status.source[k] === "\n") {
|
|
2754
|
+
end = j;
|
|
2755
|
+
break;
|
|
2756
|
+
} else if (!isSpaceChar(status.source, k)) break;
|
|
2757
|
+
}
|
|
2758
|
+
if (end !== -1) break;
|
|
2759
|
+
}
|
|
2760
|
+
if (end !== -1) {
|
|
2761
|
+
const content = status.source.substring(start, end).trimEnd();
|
|
2762
|
+
const spaceContent = status.source.substring(start + content.length, end);
|
|
2763
|
+
if (content || spaceContent) {
|
|
2764
|
+
const previousNode = element.children[element.children.length - 1];
|
|
2765
|
+
if (previousNode && isTextNode(previousNode)) {
|
|
2766
|
+
previousNode.content += content + spaceContent;
|
|
2767
|
+
previousNode.ranges.push(...ranges);
|
|
2768
|
+
} else {
|
|
2769
|
+
const text = {
|
|
2770
|
+
type: "text",
|
|
2771
|
+
content: content + spaceContent,
|
|
2772
|
+
ranges
|
|
2773
|
+
};
|
|
2774
|
+
element.children.push(text);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
status.i = end;
|
|
2778
|
+
} else status.i += 1;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
//#endregion
|
|
2782
|
+
//#region src/compile/parse/slottifyChildNodes.ts
|
|
2783
|
+
/**
|
|
2784
|
+
* Moves any child nodes that aren't already in a <fill> node into a default
|
|
2785
|
+
* <fill> node
|
|
2786
|
+
*/
|
|
2787
|
+
function slottifyChildNodes(node) {
|
|
2788
|
+
const nonFillNodes = node.children.filter((c) => !isFillNode(c));
|
|
2789
|
+
if (nonFillNodes.length) {
|
|
2790
|
+
let defaultFillNode = node.children.find((n) => isFillNode(n) && !n.attributes.find((a) => a.name === "name"));
|
|
2791
|
+
if (!defaultFillNode) {
|
|
2792
|
+
defaultFillNode = {
|
|
2793
|
+
type: "special",
|
|
2794
|
+
tagName: "fill",
|
|
2795
|
+
attributes: [],
|
|
2796
|
+
children: nonFillNodes,
|
|
2797
|
+
range: {
|
|
2798
|
+
start: 0,
|
|
2799
|
+
end: 0
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
node.children.unshift(defaultFillNode);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
node.children = node.children.filter((c) => isFillNode(c));
|
|
2806
|
+
}
|
|
2807
|
+
function isFillNode(n) {
|
|
2808
|
+
return isSpecialNode(n) && n.tagName === "fill";
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
//#endregion
|
|
2812
|
+
//#region src/compile/parse/utils/addError.ts
|
|
2813
|
+
/**
|
|
2814
|
+
* Adds an error to the parse status
|
|
2815
|
+
*
|
|
2816
|
+
* @param status The parse status
|
|
2817
|
+
* @param message The error message to add
|
|
2818
|
+
* @param start (Optional) The start position of the error (defaults to the
|
|
2819
|
+
* current parse position)
|
|
2820
|
+
*/
|
|
2821
|
+
function addError(status, message, start = status.i, end = status.i) {
|
|
2822
|
+
let startLine = 0;
|
|
2823
|
+
let lastLineStart = 0;
|
|
2824
|
+
let i = 0;
|
|
2825
|
+
for (; i < start; i++) if (status.source[i] === "\n") {
|
|
2826
|
+
startLine += 1;
|
|
2827
|
+
lastLineStart = i + 1;
|
|
2828
|
+
}
|
|
2829
|
+
let startChar = start - lastLineStart;
|
|
2830
|
+
let endLine = startLine;
|
|
2831
|
+
for (; i < end; i++) if (status.source[i] === "\n") {
|
|
2832
|
+
endLine += 1;
|
|
2833
|
+
lastLineStart = i + 1;
|
|
2834
|
+
}
|
|
2835
|
+
let endChar = end - lastLineStart;
|
|
2836
|
+
status.errors.push({
|
|
2837
|
+
message,
|
|
2838
|
+
startIndex: start,
|
|
2839
|
+
startLine,
|
|
2840
|
+
startChar,
|
|
2841
|
+
endIndex: end,
|
|
2842
|
+
endLine,
|
|
2843
|
+
endChar
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
//#endregion
|
|
2848
|
+
//#region src/compile/parse/parseElement.ts
|
|
2849
|
+
function parseElement(status) {
|
|
2850
|
+
const current = status.components.at(-1);
|
|
2851
|
+
if (!current) throw new Error("No component in stack");
|
|
2852
|
+
const start = status.i;
|
|
2853
|
+
const element = parseTag(status);
|
|
2854
|
+
if (element.tagName.startsWith("/")) {
|
|
2855
|
+
addError(status, `Non-matching close tag: ${element.tagName.substring(1)}`, start, start + element.tagName.length);
|
|
2856
|
+
return element;
|
|
2857
|
+
}
|
|
2858
|
+
if (voidTags_default.includes(element.tagName)) checkVoidElement(status, element);
|
|
2859
|
+
if (!element.selfClosed && !voidTags_default.includes(element.tagName)) {
|
|
2860
|
+
current.stack ??= [];
|
|
2861
|
+
current.stack.push(element);
|
|
2862
|
+
while (status.i < status.source.length) {
|
|
2863
|
+
addSpaceElement(element, status);
|
|
2864
|
+
if (accept("}", status, false)) break;
|
|
2865
|
+
else if (accept("<!--", status)) status.i = status.source.indexOf("-->", status.i) + 3;
|
|
2866
|
+
else if (accept("@//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
|
|
2867
|
+
else if (accept("@/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
|
|
2868
|
+
else if (accept("</", status, false)) {
|
|
2869
|
+
if (checkCloseTag(status, element, current)) break;
|
|
2870
|
+
} else if (accept("<", status, false)) {
|
|
2871
|
+
const child = parseElement(status);
|
|
2872
|
+
element.children.push(child);
|
|
2873
|
+
} else if (accept("@", status, false)) parseControl(status, element);
|
|
2874
|
+
else parseText(status, element);
|
|
2875
|
+
}
|
|
2876
|
+
current.stack.pop();
|
|
2877
|
+
}
|
|
2878
|
+
if (/[A-Z]/.test(element.tagName[0])) {
|
|
2879
|
+
element.type = "component";
|
|
2880
|
+
slottifyChildNodes(element);
|
|
2881
|
+
current.needsContext = true;
|
|
2882
|
+
}
|
|
2883
|
+
if (isSpecialNode(element)) {
|
|
2884
|
+
if (element.tagName === "slot") {
|
|
2885
|
+
slottifyChildNodes(element);
|
|
2886
|
+
current.needsContext = true;
|
|
2887
|
+
current.slotProps ??= [];
|
|
2888
|
+
current.slotProps.push(element.attributes.find((a) => a.name === "name")?.name ?? "default");
|
|
2889
|
+
} else if (element.tagName === "fill") {
|
|
2890
|
+
const fillSource = status.source.substring(start, status.i);
|
|
2891
|
+
element.hasSlotProps = /\$sprops\b/.test(fillSource);
|
|
2892
|
+
} else if (element.tagName === "@component") {
|
|
2893
|
+
const selfAttribute = element.attributes.find((a) => a.name === "self");
|
|
2894
|
+
if (selfAttribute && selfAttribute.value && selfAttribute.fullyReactive) {
|
|
2895
|
+
element.type = "component";
|
|
2896
|
+
slottifyChildNodes(element);
|
|
2897
|
+
current.needsContext = true;
|
|
2898
|
+
const selfValue = selfAttribute.value;
|
|
2899
|
+
return {
|
|
2900
|
+
type: "control",
|
|
2901
|
+
operation: "@replace group",
|
|
2902
|
+
statement: selfValue,
|
|
2903
|
+
children: [{
|
|
2904
|
+
type: "control",
|
|
2905
|
+
operation: "@replace",
|
|
2906
|
+
statement: selfValue,
|
|
2907
|
+
children: [element],
|
|
2908
|
+
range: {
|
|
2909
|
+
start: 0,
|
|
2910
|
+
end: 0
|
|
2911
|
+
}
|
|
2912
|
+
}],
|
|
2913
|
+
range: {
|
|
2914
|
+
start: 0,
|
|
2915
|
+
end: 0
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
} else addError(status, "@component element must have a self attribute", start, start + element.tagName.length + 1);
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
if (!element.closed && !voidTags_default.includes(element.tagName)) addError(status, `Unclosed non-void element: ${element.tagName}`, start, start + element.tagName.length + 1);
|
|
2922
|
+
return element;
|
|
2923
|
+
}
|
|
2924
|
+
function checkVoidElement(status, element) {
|
|
2925
|
+
let i = status.i;
|
|
2926
|
+
consumeSpace(status);
|
|
2927
|
+
if (accept("</", status)) {
|
|
2928
|
+
consumeSpace(status);
|
|
2929
|
+
if (accept(element.tagName, status)) {
|
|
2930
|
+
addError(status, `Closed void element: ${element.tagName}`, i, i + element.tagName.length + 1);
|
|
2931
|
+
consumeSpace(status);
|
|
2932
|
+
accept(">", status);
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
status.i = i;
|
|
2937
|
+
}
|
|
2938
|
+
function checkCloseTag(status, element, current) {
|
|
2939
|
+
const start = status.i;
|
|
2940
|
+
accept("</", status);
|
|
2941
|
+
consumeSpace(status);
|
|
2942
|
+
let snailed = accept("@", status);
|
|
2943
|
+
let closingTagName = consumeAlphaNumeric(status);
|
|
2944
|
+
if (snailed) closingTagName = "@" + closingTagName;
|
|
2945
|
+
consumeSpace(status);
|
|
2946
|
+
accept(">", status);
|
|
2947
|
+
let found = false;
|
|
2948
|
+
if (current.stack) {
|
|
2949
|
+
for (let i = current.stack.length - 1; i >= 0; i--) if (closingTagName === current.stack[i].tagName) {
|
|
2950
|
+
current.stack[i].closed = true;
|
|
2951
|
+
found = true;
|
|
2952
|
+
break;
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
if (!found) addError(status, `Non-matching close tag: ${closingTagName} (expected ${element.tagName})`, start, start + element.tagName.length + 1);
|
|
2956
|
+
return found;
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
//#endregion
|
|
2960
|
+
//#region src/compile/parse/parseControl.ts
|
|
2961
|
+
const controlOperations = [
|
|
2962
|
+
"@if",
|
|
2963
|
+
"@else",
|
|
2964
|
+
"@for",
|
|
2965
|
+
"@key",
|
|
2966
|
+
"@switch",
|
|
2967
|
+
"@case",
|
|
2968
|
+
"@default",
|
|
2969
|
+
"@await",
|
|
2970
|
+
"@then",
|
|
2971
|
+
"@catch",
|
|
2972
|
+
"@replace",
|
|
2973
|
+
"@html",
|
|
2974
|
+
"@const",
|
|
2975
|
+
"@console",
|
|
2976
|
+
"@debugger",
|
|
2977
|
+
"@function",
|
|
2978
|
+
"@async function"
|
|
2979
|
+
];
|
|
2980
|
+
const standaloneOperations = [
|
|
2981
|
+
"@key",
|
|
2982
|
+
"@html",
|
|
2983
|
+
"@const",
|
|
2984
|
+
"@console",
|
|
2985
|
+
"@debugger",
|
|
2986
|
+
"@function",
|
|
2987
|
+
"@async function"
|
|
2988
|
+
];
|
|
2989
|
+
function parseControl(status, parentNode) {
|
|
2990
|
+
const node = parseControlOpen(status);
|
|
2991
|
+
if (!node) return;
|
|
2992
|
+
if (standaloneOperations.includes(node.operation)) {
|
|
2993
|
+
wrangleControlNode(node, parentNode);
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
while (status.i < status.source.length) {
|
|
2997
|
+
addSpaceElement(node, status);
|
|
2998
|
+
if (accept("}", status)) break;
|
|
2999
|
+
else if (accept("<", status, false)) {
|
|
3000
|
+
const child = parseElement(status);
|
|
3001
|
+
node.children.push(child);
|
|
3002
|
+
} else if (accept("@//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
|
|
3003
|
+
else if (accept("@/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
|
|
3004
|
+
else if (accept("@", status, false)) parseControl(status, node);
|
|
3005
|
+
else if (accept("case", status, false) || accept("key", status, false)) parseControl(status, node);
|
|
3006
|
+
else {
|
|
3007
|
+
const char = status.source[status.i];
|
|
3008
|
+
addError(status, `Unexpected token in control block: ${char}`, status.i, status.i + 1);
|
|
3009
|
+
break;
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
wrangleControlNode(node, parentNode);
|
|
3013
|
+
parseControlBranches(status, parentNode);
|
|
3014
|
+
}
|
|
3015
|
+
function parseControlOpen(status) {
|
|
3016
|
+
const start = status.i;
|
|
3017
|
+
let statementStart = start;
|
|
3018
|
+
let operation = "";
|
|
3019
|
+
for (status.i; status.i < status.source.length; status.i++) {
|
|
3020
|
+
const char = status.source[status.i];
|
|
3021
|
+
if (isSpaceChar(status.source, status.i) && operation !== "@async" || accept(":", status) && operation === "default" || accept(".", status) && operation === "@console" || accept("(", status, false) && operation === "@html") break;
|
|
3022
|
+
operation += char;
|
|
3023
|
+
}
|
|
3024
|
+
if (!operation.startsWith("@")) operation = "@" + operation;
|
|
3025
|
+
else statementStart++;
|
|
3026
|
+
let statement = "";
|
|
3027
|
+
if (controlOperations.includes(operation)) {
|
|
3028
|
+
statement = parseControlStatement(start, operation, status);
|
|
3029
|
+
if (operation === "@function" || operation === "@async function") {
|
|
3030
|
+
statement += ` {${parseInlineScript(status)}}`;
|
|
3031
|
+
accept("}", status);
|
|
3032
|
+
}
|
|
3033
|
+
if (operation === "@replace" || operation === "@html") statement = trimMatched(statement.substring(statement.indexOf("(")).trim(), "(", ")");
|
|
3034
|
+
} else {
|
|
3035
|
+
addError(status, `Unknown operation: ${operation}`, status.i, status.i + operation.length);
|
|
3036
|
+
return null;
|
|
3037
|
+
}
|
|
3038
|
+
return {
|
|
3039
|
+
type: "control",
|
|
3040
|
+
operation,
|
|
3041
|
+
statement,
|
|
3042
|
+
children: [],
|
|
3043
|
+
range: {
|
|
3044
|
+
start: statementStart,
|
|
3045
|
+
end: status.i
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
function parseControlStatement(start, operation, status) {
|
|
3050
|
+
let parenCount = 0;
|
|
3051
|
+
while (status.i < status.source.length) if (accept("(", status)) parenCount += 1;
|
|
3052
|
+
else if (accept(")", status)) parenCount -= 1;
|
|
3053
|
+
else if (accept("{", status) || accept("\n", status) && standaloneOperations.includes(operation)) {
|
|
3054
|
+
if (parenCount === 0) return trimStart(status.source.substring(start, status.i - 1).trim(), "@");
|
|
3055
|
+
} else if (accept("//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
|
|
3056
|
+
else if (accept("/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
|
|
3057
|
+
else if (accept("\"", status) || accept("'", status)) {
|
|
3058
|
+
let char = status.source[status.i - 1];
|
|
3059
|
+
status.i = endOfString(char, status.source, status.i - 1) + 1;
|
|
3060
|
+
} else if (accept("`", status)) status.i = endOfTemplateString(status.source, status.i - 1) + 1;
|
|
3061
|
+
else status.i += 1;
|
|
3062
|
+
return "";
|
|
3063
|
+
}
|
|
3064
|
+
/**
|
|
3065
|
+
* Groups the control node into the correct parent e.g. if/else into an if group
|
|
3066
|
+
* @param node The control node
|
|
3067
|
+
* @param parentNode The parent node
|
|
3068
|
+
*/
|
|
3069
|
+
function wrangleControlNode(node, parentNode) {
|
|
3070
|
+
if (node.operation === "@if") {
|
|
3071
|
+
const ifGroup = {
|
|
3072
|
+
type: "control",
|
|
3073
|
+
operation: "@if group",
|
|
3074
|
+
statement: "",
|
|
3075
|
+
children: [node],
|
|
3076
|
+
range: {
|
|
3077
|
+
start: 0,
|
|
3078
|
+
end: 0
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
parentNode.children.push(ifGroup);
|
|
3082
|
+
} else if (node.operation === "@else") {
|
|
3083
|
+
if (/^else\s+if/.test(node.statement)) node.operation = "@else if";
|
|
3084
|
+
for (let i = parentNode.children.length - 1; i >= 0; i--) {
|
|
3085
|
+
const lastChild = parentNode.children[i];
|
|
3086
|
+
if (isControlNode(lastChild) && lastChild.operation === "@if group") {
|
|
3087
|
+
lastChild.children.push(node);
|
|
3088
|
+
break;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
} else if (node.operation === "@for") {
|
|
3092
|
+
const forGroup = {
|
|
3093
|
+
type: "control",
|
|
3094
|
+
operation: "@for group",
|
|
3095
|
+
statement: "",
|
|
3096
|
+
children: [node],
|
|
3097
|
+
range: {
|
|
3098
|
+
start: 0,
|
|
3099
|
+
end: 0
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
3102
|
+
parentNode.children.push(forGroup);
|
|
3103
|
+
} else if (node.operation === "@switch") {
|
|
3104
|
+
node.operation = "@switch group";
|
|
3105
|
+
parentNode.children.push(node);
|
|
3106
|
+
} else if (node.operation === "@await") {
|
|
3107
|
+
const awaitGroup = {
|
|
3108
|
+
type: "control",
|
|
3109
|
+
operation: "@await group",
|
|
3110
|
+
statement: "",
|
|
3111
|
+
children: [node],
|
|
3112
|
+
range: {
|
|
3113
|
+
start: 0,
|
|
3114
|
+
end: 0
|
|
3115
|
+
}
|
|
3116
|
+
};
|
|
3117
|
+
parentNode.children.push(awaitGroup);
|
|
3118
|
+
} else if (node.operation === "@then" || node.operation === "@catch") for (let i = parentNode.children.length - 1; i >= 0; i--) {
|
|
3119
|
+
const lastChild = parentNode.children[i];
|
|
3120
|
+
if (isControlNode(lastChild) && lastChild.operation === "@await group") {
|
|
3121
|
+
lastChild.children.push(node);
|
|
3122
|
+
break;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
else if (node.operation === "@replace") {
|
|
3126
|
+
const replaceGroup = {
|
|
3127
|
+
type: "control",
|
|
3128
|
+
operation: "@replace group",
|
|
3129
|
+
statement: "",
|
|
3130
|
+
children: [node],
|
|
3131
|
+
range: {
|
|
3132
|
+
start: 0,
|
|
3133
|
+
end: 0
|
|
3134
|
+
}
|
|
3135
|
+
};
|
|
3136
|
+
parentNode.children.push(replaceGroup);
|
|
3137
|
+
} else if (node.operation === "@html") {
|
|
3138
|
+
const htmlGroup = {
|
|
3139
|
+
type: "control",
|
|
3140
|
+
operation: "@html group",
|
|
3141
|
+
statement: "",
|
|
3142
|
+
children: [node],
|
|
3143
|
+
range: {
|
|
3144
|
+
start: 0,
|
|
3145
|
+
end: 0
|
|
3146
|
+
}
|
|
3147
|
+
};
|
|
3148
|
+
parentNode.children.push(htmlGroup);
|
|
3149
|
+
} else parentNode.children.push(node);
|
|
3150
|
+
}
|
|
3151
|
+
/**
|
|
3152
|
+
* Parses control branches that don't start with an @ symbol e.g. else or case
|
|
3153
|
+
* @param status The parse status
|
|
3154
|
+
* @param parentNode The parent node
|
|
3155
|
+
*/
|
|
3156
|
+
function parseControlBranches(status, parentNode) {
|
|
3157
|
+
let gotAnotherBranch = true;
|
|
3158
|
+
while (gotAnotherBranch) {
|
|
3159
|
+
let start = status.i;
|
|
3160
|
+
status.i += 1;
|
|
3161
|
+
consumeSpace(status);
|
|
3162
|
+
if (accept("else", status, false) || accept("case", status, false) || accept("default", status, false) || accept("then", status, false) || accept("catch", status, false)) parseControl(status, parentNode);
|
|
3163
|
+
else {
|
|
3164
|
+
gotAnotherBranch = false;
|
|
3165
|
+
status.i = start;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
//#endregion
|
|
3171
|
+
//#region src/compile/parse/parseMarkup.ts
|
|
3172
|
+
function parseMarkup(status, source) {
|
|
3173
|
+
const current = status.components.at(-1);
|
|
3174
|
+
if (!current) return;
|
|
3175
|
+
while (status.i < source.length) if (accept("}", status, false)) return;
|
|
3176
|
+
else if (accept("<!--", status)) status.i = status.source.indexOf("-->", status.i) + 3;
|
|
3177
|
+
else if (accept("@//", status)) status.i = status.source.indexOf("\n", status.i) + 1;
|
|
3178
|
+
else if (accept("@/*", status)) status.i = status.source.indexOf("*/", status.i) + 2;
|
|
3179
|
+
else if (isSpaceChar(status.source, status.i)) {
|
|
3180
|
+
current.markup ??= {
|
|
3181
|
+
type: "root",
|
|
3182
|
+
children: []
|
|
3183
|
+
};
|
|
3184
|
+
const text = {
|
|
3185
|
+
type: "text",
|
|
3186
|
+
content: consumeSpace(status),
|
|
3187
|
+
ranges: []
|
|
3188
|
+
};
|
|
3189
|
+
current.markup.children.push(text);
|
|
3190
|
+
} else if (accept("<", status, false)) {
|
|
3191
|
+
current.markup ??= {
|
|
3192
|
+
type: "root",
|
|
3193
|
+
children: []
|
|
3194
|
+
};
|
|
3195
|
+
const element = parseElement(status);
|
|
3196
|
+
current.markup.children.push(element);
|
|
3197
|
+
} else if (accept("@", status, false)) {
|
|
3198
|
+
current.markup ??= {
|
|
3199
|
+
type: "root",
|
|
3200
|
+
children: []
|
|
3201
|
+
};
|
|
3202
|
+
parseControl(status, current.markup);
|
|
3203
|
+
} else status.i += 1;
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
//#endregion
|
|
3207
|
+
//#region src/compile/utils/hash.ts
|
|
3208
|
+
const returnRegex = /\r/g;
|
|
3209
|
+
function hash(input) {
|
|
3210
|
+
input = input.replace(returnRegex, "");
|
|
3211
|
+
let hash$1 = 5381;
|
|
3212
|
+
let i = input.length;
|
|
3213
|
+
while (i--) hash$1 = (hash$1 << 5) - hash$1 ^ input.charCodeAt(i);
|
|
3214
|
+
return (hash$1 >>> 0).toString(36);
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
//#endregion
|
|
3218
|
+
//#region src/compile/parse/utils/expect.ts
|
|
3219
|
+
/**
|
|
3220
|
+
* Checks whether the source at the current parse position matches the supplied
|
|
3221
|
+
* string and if not, adds an error to the parse status
|
|
3222
|
+
*
|
|
3223
|
+
* @param value The string to check
|
|
3224
|
+
* @param status The parse status
|
|
3225
|
+
* @param advance (Optional) If true, advances the parse position past the
|
|
3226
|
+
* checked string (defaults to true)
|
|
3227
|
+
*
|
|
3228
|
+
* @returns True if the supplied string was found at the current parse position
|
|
3229
|
+
*/
|
|
3230
|
+
function expect(value, status, advance = true) {
|
|
3231
|
+
if (status.i < status.source.length) if (status.source.substring(status.i, status.i + value.length) == value) {
|
|
3232
|
+
status.i += advance ? value.length : 0;
|
|
3233
|
+
return true;
|
|
3234
|
+
} else addError(status, `Expected ${value}`);
|
|
3235
|
+
else addError(status, "Expected token");
|
|
3236
|
+
return false;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
//#endregion
|
|
3240
|
+
//#region src/compile/parse/parseStyles.ts
|
|
3241
|
+
function parseStyles(source, status) {
|
|
3242
|
+
const current = status.components.at(-1);
|
|
3243
|
+
if (!current) return;
|
|
3244
|
+
current.style = {
|
|
3245
|
+
global: false,
|
|
3246
|
+
blocks: [],
|
|
3247
|
+
hash: hash(source)
|
|
3248
|
+
};
|
|
3249
|
+
const styleStatus = {
|
|
3250
|
+
source,
|
|
3251
|
+
i: 0,
|
|
3252
|
+
marker: 0,
|
|
3253
|
+
level: 0,
|
|
3254
|
+
imports: [],
|
|
3255
|
+
script: [],
|
|
3256
|
+
components: [],
|
|
3257
|
+
errors: []
|
|
3258
|
+
};
|
|
3259
|
+
while (styleStatus.i < styleStatus.source.length) if (isSpaceChar(styleStatus.source, styleStatus.i)) consumeSpace(styleStatus);
|
|
3260
|
+
else {
|
|
3261
|
+
const block = parseStyleBlock(styleStatus);
|
|
3262
|
+
if (block) current.style.blocks.push(block);
|
|
3263
|
+
else break;
|
|
3264
|
+
}
|
|
3265
|
+
status.errors = status.errors.concat(styleStatus.errors);
|
|
3266
|
+
}
|
|
3267
|
+
function parseStyleBlock(status) {
|
|
3268
|
+
const selector = consumeUntil("{", status).trim();
|
|
3269
|
+
if (expect("{", status)) {
|
|
3270
|
+
const block = {
|
|
3271
|
+
selector,
|
|
3272
|
+
attributes: [],
|
|
3273
|
+
children: []
|
|
3274
|
+
};
|
|
3275
|
+
consumeSpace(status);
|
|
3276
|
+
while (status.source[status.i] !== "}") {
|
|
3277
|
+
let nextColon = status.source.indexOf(":", status.i);
|
|
3278
|
+
if (nextColon === -1) nextColon = status.source.length;
|
|
3279
|
+
let nextOpenBrace = status.source.indexOf("{", status.i);
|
|
3280
|
+
if (nextOpenBrace === -1) nextOpenBrace = status.source.length;
|
|
3281
|
+
if (nextColon < nextOpenBrace) {
|
|
3282
|
+
const attribute = parseStyleAttribute(status);
|
|
3283
|
+
block.attributes.push(attribute);
|
|
3284
|
+
} else if (nextOpenBrace < nextColon) {
|
|
3285
|
+
const child = parseStyleBlock(status);
|
|
3286
|
+
if (child) block.children.push(child);
|
|
3287
|
+
else break;
|
|
3288
|
+
} else return block;
|
|
3289
|
+
consumeSpace(status);
|
|
3290
|
+
}
|
|
3291
|
+
accept("}", status);
|
|
3292
|
+
return block;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
function parseStyleAttribute(status) {
|
|
3296
|
+
const name = consumeUntil(":", status).trim();
|
|
3297
|
+
accept(":", status);
|
|
3298
|
+
const value = consumeUntil(";", status).trim();
|
|
3299
|
+
accept(";", status);
|
|
3300
|
+
return {
|
|
3301
|
+
name,
|
|
3302
|
+
value
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
//#endregion
|
|
3307
|
+
//#region src/compile/utils/isComponentNode.ts
|
|
3308
|
+
function isComponentNode(node) {
|
|
3309
|
+
return node.type === "component";
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
//#endregion
|
|
3313
|
+
//#region src/compile/utils/isParentNode.ts
|
|
3314
|
+
function isParentNode(node) {
|
|
3315
|
+
return "children" in node;
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
//#endregion
|
|
3319
|
+
//#region src/compile/parse/scopeStyles.ts
|
|
3320
|
+
function scopeStyles(status) {
|
|
3321
|
+
let selectors = [];
|
|
3322
|
+
for (let component of status.components) if (component.style && component.markup) {
|
|
3323
|
+
for (let block of component.style.blocks) collectStyleSelectors(block, selectors);
|
|
3324
|
+
scopeStylesOnNode(component.markup, selectors);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
function scopeStylesOnNode(node, selectors) {
|
|
3328
|
+
if (isElementNode(node) || isComponentNode(node)) {
|
|
3329
|
+
let scopeStyles$1 = selectors.includes(node.tagName);
|
|
3330
|
+
if (!scopeStyles$1) for (let a of node.attributes) {
|
|
3331
|
+
if (a.name === "class" && a.reactive) scopeStyles$1 = true;
|
|
3332
|
+
else if (a.name === "id" && a.value) scopeStyles$1 = selectors.includes(`#${trimQuotes(a.value)}`);
|
|
3333
|
+
else if (a.name === "class" && a.value) scopeStyles$1 = selectors.includes(`.${trimQuotes(a.value)}`);
|
|
3334
|
+
if (scopeStyles$1) break;
|
|
3335
|
+
}
|
|
3336
|
+
node.scopeStyles = scopeStyles$1;
|
|
3337
|
+
}
|
|
3338
|
+
if (isParentNode(node)) for (let child of node.children) scopeStylesOnNode(child, selectors);
|
|
3339
|
+
}
|
|
3340
|
+
function collectStyleSelectors(block, selectors) {
|
|
3341
|
+
for (let s of block.selector.split(/[\s*,>+~]/)) selectors.push(s);
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
//#endregion
|
|
3345
|
+
//#region src/compile/parse/parseCode.ts
|
|
3346
|
+
function parseCode(source) {
|
|
3347
|
+
const status = {
|
|
3348
|
+
source,
|
|
3349
|
+
i: 0,
|
|
3350
|
+
marker: 0,
|
|
3351
|
+
level: 0,
|
|
3352
|
+
imports: [],
|
|
3353
|
+
script: [],
|
|
3354
|
+
components: [],
|
|
3355
|
+
errors: []
|
|
3356
|
+
};
|
|
3357
|
+
for (status.i; status.i < source.length; status.i++) if (consumeScriptComments(status)) {} else if (status.source[status.i] === "{") status.level += 1;
|
|
3358
|
+
else if (status.source[status.i] === "}") {
|
|
3359
|
+
status.level -= 1;
|
|
3360
|
+
if (status.level === 0) parseComponentEnd(status);
|
|
3361
|
+
} else if (accept("function", status)) {
|
|
3362
|
+
if (consumeSpace(status)) if (/[A-Z]/.test(source[status.i])) parseComponentStart(status);
|
|
3363
|
+
else status.i--;
|
|
3364
|
+
} else if (accept("const", status)) {
|
|
3365
|
+
if (consumeSpace(status)) if (/[A-Z]/.test(source[status.i])) {
|
|
3366
|
+
const start = status.i;
|
|
3367
|
+
consumeAlphaNumeric(status);
|
|
3368
|
+
consumeSpace(status);
|
|
3369
|
+
if (accept("=", status)) {
|
|
3370
|
+
consumeSpace(status);
|
|
3371
|
+
if (accept("(", status, false)) {
|
|
3372
|
+
status.i = start;
|
|
3373
|
+
parseComponentStart(status);
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
} else status.i--;
|
|
3377
|
+
} else if (accept("export", status)) {
|
|
3378
|
+
const start = status.i;
|
|
3379
|
+
if (consumeSpace(status) && accept("default", status) && consumeSpace(status) && accept("(", status, false)) parseComponentStart(status);
|
|
3380
|
+
else status.i = start;
|
|
3381
|
+
} else if (accept("@render", status, false)) parseComponentRender(status);
|
|
3382
|
+
else if (accept("@head", status, false)) parseComponentHead(status);
|
|
3383
|
+
else if (accept("@style", status, false)) parseComponentStyle(status);
|
|
3384
|
+
status.script.push({
|
|
3385
|
+
script: source.substring(status.marker, source.length),
|
|
3386
|
+
range: {
|
|
3387
|
+
start: status.marker,
|
|
3388
|
+
end: source.length
|
|
3389
|
+
}
|
|
3390
|
+
});
|
|
3391
|
+
scopeStyles(status);
|
|
3392
|
+
const ok = !status.errors.length;
|
|
3393
|
+
if (!ok) status.errors = status.errors.sort((a, b) => a.startIndex - b.startIndex);
|
|
3394
|
+
return {
|
|
3395
|
+
ok,
|
|
3396
|
+
errors: status.errors,
|
|
3397
|
+
template: ok ? {
|
|
3398
|
+
imports: status.imports,
|
|
3399
|
+
script: status.script,
|
|
3400
|
+
components: status.components.map((c) => {
|
|
3401
|
+
return {
|
|
3402
|
+
start: c.start,
|
|
3403
|
+
name: c.name,
|
|
3404
|
+
default: c.default,
|
|
3405
|
+
params: c.params,
|
|
3406
|
+
markup: c.markup,
|
|
3407
|
+
head: c.head,
|
|
3408
|
+
style: c.style,
|
|
3409
|
+
props: c.props,
|
|
3410
|
+
contextProps: c.contextProps,
|
|
3411
|
+
slotProps: c.slotProps,
|
|
3412
|
+
needsContext: c.needsContext
|
|
3413
|
+
};
|
|
3414
|
+
})
|
|
3415
|
+
} : void 0
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
function parseComponentStart(status) {
|
|
3419
|
+
let source = status.source.substring(0, status.i);
|
|
3420
|
+
let def = /export\s+default\s+(function\s+|const\s+)*$/.test(source);
|
|
3421
|
+
let isConst = /const\s+$/.test(source);
|
|
3422
|
+
let isAnon = /export default\s+$/.test(source);
|
|
3423
|
+
status.components.push({
|
|
3424
|
+
start: status.i,
|
|
3425
|
+
name: consumeAlphaNumeric(status),
|
|
3426
|
+
default: def
|
|
3427
|
+
});
|
|
3428
|
+
const current = status.components.at(-1);
|
|
3429
|
+
if (!current) return;
|
|
3430
|
+
let start = -1;
|
|
3431
|
+
let end = -1;
|
|
3432
|
+
let level = 0;
|
|
3433
|
+
for (status.i; status.i < status.source.length; status.i++) {
|
|
3434
|
+
const char = status.source[status.i];
|
|
3435
|
+
if (consumeScriptComments(status)) {} else if (char === "(") {
|
|
3436
|
+
level += 1;
|
|
3437
|
+
if (start === -1) start = status.i + 1;
|
|
3438
|
+
} else if (char === ")") {
|
|
3439
|
+
level -= 1;
|
|
3440
|
+
if (level === 0) {
|
|
3441
|
+
end = status.i;
|
|
3442
|
+
break;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
status.script.push({
|
|
3447
|
+
script: status.source.substring(status.marker, start),
|
|
3448
|
+
range: {
|
|
3449
|
+
start: status.marker,
|
|
3450
|
+
end: start
|
|
3451
|
+
}
|
|
3452
|
+
});
|
|
3453
|
+
status.script.push({
|
|
3454
|
+
script: "/* @params */",
|
|
3455
|
+
range: {
|
|
3456
|
+
start: 0,
|
|
3457
|
+
end: 0
|
|
3458
|
+
}
|
|
3459
|
+
});
|
|
3460
|
+
current.params = status.source.substring(start, end).trim() || void 0;
|
|
3461
|
+
status.marker = end;
|
|
3462
|
+
status.i = status.marker;
|
|
3463
|
+
accept(")", status);
|
|
3464
|
+
consumeSpace(status);
|
|
3465
|
+
if (isConst || isAnon) {
|
|
3466
|
+
accept("=>", status);
|
|
3467
|
+
consumeSpace(status);
|
|
3468
|
+
}
|
|
3469
|
+
accept("{", status);
|
|
3470
|
+
status.level += 1;
|
|
3471
|
+
const postFunctionChunk = {
|
|
3472
|
+
script: status.source.substring(status.marker, status.i),
|
|
3473
|
+
range: {
|
|
3474
|
+
start: status.marker,
|
|
3475
|
+
end: status.i
|
|
3476
|
+
}
|
|
3477
|
+
};
|
|
3478
|
+
if (/\)\s+\{/.test(postFunctionChunk.script)) postFunctionChunk.script = ") /* @return_type */ {";
|
|
3479
|
+
status.script.push(postFunctionChunk);
|
|
3480
|
+
status.script.push({
|
|
3481
|
+
script: "/* @start */",
|
|
3482
|
+
range: {
|
|
3483
|
+
start: 0,
|
|
3484
|
+
end: 0
|
|
3485
|
+
}
|
|
3486
|
+
});
|
|
3487
|
+
status.marker = status.i;
|
|
3488
|
+
const space = consumeSpace(status);
|
|
3489
|
+
status.script.push({
|
|
3490
|
+
script: space,
|
|
3491
|
+
range: {
|
|
3492
|
+
start: status.marker,
|
|
3493
|
+
end: status.i
|
|
3494
|
+
}
|
|
3495
|
+
});
|
|
3496
|
+
status.marker = status.i;
|
|
3497
|
+
status.i -= 1;
|
|
3498
|
+
}
|
|
3499
|
+
function parseComponentRender(status) {
|
|
3500
|
+
const current = status.components.at(-1);
|
|
3501
|
+
if (!current) return;
|
|
3502
|
+
if (current.markup) addError(status, `Multiple @render sections`, status.i, status.i + 7);
|
|
3503
|
+
status.script.push({
|
|
3504
|
+
script: status.source.substring(status.marker, status.i),
|
|
3505
|
+
range: {
|
|
3506
|
+
start: status.marker,
|
|
3507
|
+
end: status.i
|
|
3508
|
+
}
|
|
3509
|
+
});
|
|
3510
|
+
status.script.push({
|
|
3511
|
+
script: "/* @render */",
|
|
3512
|
+
range: {
|
|
3513
|
+
start: 0,
|
|
3514
|
+
end: 0
|
|
3515
|
+
}
|
|
3516
|
+
});
|
|
3517
|
+
accept("@render", status);
|
|
3518
|
+
consumeSpace(status);
|
|
3519
|
+
if (accept("{", status)) {
|
|
3520
|
+
parseMarkup(status, status.source);
|
|
3521
|
+
accept("}", status);
|
|
3522
|
+
}
|
|
3523
|
+
status.marker = status.i;
|
|
3524
|
+
}
|
|
3525
|
+
function parseComponentHead(status) {
|
|
3526
|
+
const current = status.components.at(-1);
|
|
3527
|
+
if (!current) return;
|
|
3528
|
+
if (current.head) addError(status, `Multiple @head sections`, status.i, status.i + 5);
|
|
3529
|
+
status.script.push({
|
|
3530
|
+
script: status.source.substring(status.marker, status.i),
|
|
3531
|
+
range: {
|
|
3532
|
+
start: status.marker,
|
|
3533
|
+
end: status.i
|
|
3534
|
+
}
|
|
3535
|
+
});
|
|
3536
|
+
status.script.push({
|
|
3537
|
+
script: "/* @head */",
|
|
3538
|
+
range: {
|
|
3539
|
+
start: 0,
|
|
3540
|
+
end: 0
|
|
3541
|
+
}
|
|
3542
|
+
});
|
|
3543
|
+
status.components.push({ start: status.i });
|
|
3544
|
+
accept("@head", status);
|
|
3545
|
+
consumeSpace(status);
|
|
3546
|
+
if (accept("{", status)) {
|
|
3547
|
+
parseMarkup(status, status.source);
|
|
3548
|
+
accept("}", status);
|
|
3549
|
+
}
|
|
3550
|
+
current.head = status.components.pop()?.markup;
|
|
3551
|
+
status.marker = status.i;
|
|
3552
|
+
}
|
|
3553
|
+
function parseComponentStyle(status) {
|
|
3554
|
+
const current = status.components.at(-1);
|
|
3555
|
+
if (!current) return;
|
|
3556
|
+
if (current.style) addError(status, `Multiple @style sections`, status.i, status.i + 6);
|
|
3557
|
+
status.script.push({
|
|
3558
|
+
script: status.source.substring(status.marker, status.i),
|
|
3559
|
+
range: {
|
|
3560
|
+
start: status.marker,
|
|
3561
|
+
end: status.i
|
|
3562
|
+
}
|
|
3563
|
+
});
|
|
3564
|
+
status.script.push({
|
|
3565
|
+
script: "/* @style */",
|
|
3566
|
+
range: {
|
|
3567
|
+
start: 0,
|
|
3568
|
+
end: 0
|
|
3569
|
+
}
|
|
3570
|
+
});
|
|
3571
|
+
let start = -1;
|
|
3572
|
+
let end = status.source.length;
|
|
3573
|
+
let level = 0;
|
|
3574
|
+
let styleSource = "";
|
|
3575
|
+
for (; status.i < status.source.length; status.i++) {
|
|
3576
|
+
const char = status.source[status.i];
|
|
3577
|
+
const nextChar = status.source[status.i + 1];
|
|
3578
|
+
if (char === "/" && nextChar === "/") status.i = status.source.indexOf("\n", status.i);
|
|
3579
|
+
else if (char === "/" && nextChar === "*") status.i = status.source.indexOf("*/", status.i) + 1;
|
|
3580
|
+
else if (char === "\"" || char === "'") {
|
|
3581
|
+
let start$1 = status.i;
|
|
3582
|
+
status.i = endOfString(char, status.source, start$1);
|
|
3583
|
+
styleSource += status.source.substring(start$1, status.i + 1);
|
|
3584
|
+
} else if (char === "`") {
|
|
3585
|
+
let start$1 = status.i;
|
|
3586
|
+
status.i = endOfTemplateString(status.source, start$1);
|
|
3587
|
+
styleSource += status.source.substring(start$1, status.i + 1);
|
|
3588
|
+
} else if (char === "{") {
|
|
3589
|
+
level += 1;
|
|
3590
|
+
if (start === -1) start = status.i + 1;
|
|
3591
|
+
else styleSource += char;
|
|
3592
|
+
} else if (char === "}") {
|
|
3593
|
+
level -= 1;
|
|
3594
|
+
if (level === 0) {
|
|
3595
|
+
end = status.i - 1;
|
|
3596
|
+
break;
|
|
3597
|
+
} else styleSource += char;
|
|
3598
|
+
} else if (start !== -1) styleSource += char;
|
|
3599
|
+
}
|
|
3600
|
+
status.i = start;
|
|
3601
|
+
parseStyles(styleSource.trim(), status);
|
|
3602
|
+
status.i = end + 2;
|
|
3603
|
+
status.marker = status.i;
|
|
3604
|
+
}
|
|
3605
|
+
function parseComponentEnd(status) {
|
|
3606
|
+
const current = status.components.at(-1);
|
|
3607
|
+
if (!current) return;
|
|
3608
|
+
status.script.push({
|
|
3609
|
+
script: status.source.substring(status.marker, status.i),
|
|
3610
|
+
range: {
|
|
3611
|
+
start: status.marker,
|
|
3612
|
+
end: status.i
|
|
3613
|
+
}
|
|
3614
|
+
});
|
|
3615
|
+
status.script.push({
|
|
3616
|
+
script: "/* @end */",
|
|
3617
|
+
range: {
|
|
3618
|
+
start: 0,
|
|
3619
|
+
end: 0
|
|
3620
|
+
}
|
|
3621
|
+
});
|
|
3622
|
+
const componentSource = status.source.substring(current.start || 0, status.i);
|
|
3623
|
+
current.props = getPropsUsage(componentSource);
|
|
3624
|
+
current.contextProps = getContextUsage(componentSource);
|
|
3625
|
+
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);
|
|
3626
|
+
status.marker = status.i;
|
|
3627
|
+
}
|
|
3628
|
+
function getPropsUsage(source) {
|
|
3629
|
+
const propsMatches = source.matchAll(/\$props\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g);
|
|
3630
|
+
const props = [];
|
|
3631
|
+
for (let match of propsMatches) {
|
|
3632
|
+
const name = trimQuotes(match[1] || match[2]);
|
|
3633
|
+
if (!props.includes(name)) props.push(name);
|
|
3634
|
+
}
|
|
3635
|
+
return props.length ? props : void 0;
|
|
3636
|
+
}
|
|
3637
|
+
function getContextUsage(source) {
|
|
3638
|
+
const contextsMatches = source.matchAll(/\$context\s*(?:\.([\d\w]+)|\[([^\]]+)\])/g);
|
|
3639
|
+
const contexts = [];
|
|
3640
|
+
for (let match of contextsMatches) {
|
|
3641
|
+
const name = trimQuotes(match[1] || match[2]);
|
|
3642
|
+
if (!contexts.includes(name)) contexts.push(name);
|
|
3643
|
+
}
|
|
3644
|
+
return contexts.length ? contexts : void 0;
|
|
3645
|
+
}
|
|
3646
|
+
function consumeScriptComments(status) {
|
|
3647
|
+
const char = status.source[status.i];
|
|
3648
|
+
const nextChar = status.source[status.i + 1];
|
|
3649
|
+
if (char === "/" && nextChar === "/") {
|
|
3650
|
+
status.i = status.source.indexOf("\n", status.i);
|
|
3651
|
+
return true;
|
|
3652
|
+
} else if (char === "/" && nextChar === "*") {
|
|
3653
|
+
status.i = status.source.indexOf("*/", status.i) + 1;
|
|
3654
|
+
return true;
|
|
3655
|
+
} else if (char === "\"" || char === "'") {
|
|
3656
|
+
status.i = endOfString(char, status.source, status.i);
|
|
3657
|
+
return true;
|
|
3658
|
+
} else if (char === "`") {
|
|
3659
|
+
status.i = endOfTemplateString(status.source, status.i);
|
|
3660
|
+
return true;
|
|
3661
|
+
}
|
|
3662
|
+
return false;
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
//#endregion
|
|
3666
|
+
//#region src/compile/parse.ts
|
|
3667
|
+
/**
|
|
3668
|
+
* Parses source code into a component template
|
|
3669
|
+
*
|
|
3670
|
+
* @param source The source code
|
|
3671
|
+
*
|
|
3672
|
+
* @returns A result indicating whether parsing was ok, and if so, containing
|
|
3673
|
+
* the component template's parts such as script, markup and styles
|
|
3674
|
+
*/
|
|
3675
|
+
function parse(source) {
|
|
3676
|
+
return parseCode(source);
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
//#endregion
|
|
3680
|
+
exports.build = build;
|
|
3681
|
+
exports.buildType = buildType;
|
|
3682
|
+
exports.parse = parse;
|
|
3683
|
+
//# sourceMappingURL=compile.cjs.map
|