tachyon-dom 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +339 -0
- package/dist/adapters/lambda.d.ts +65 -0
- package/dist/adapters/lambda.js +211 -0
- package/dist/adapters/node.d.ts +25 -0
- package/dist/adapters/node.js +360 -0
- package/dist/adapters/workers.d.ts +149 -0
- package/dist/adapters/workers.js +272 -0
- package/dist/adapters.d.ts +3 -0
- package/dist/adapters.js +3 -0
- package/dist/app.d.ts +98 -0
- package/dist/app.js +306 -0
- package/dist/cli.d.ts +55 -0
- package/dist/cli.js +711 -0
- package/dist/compiler/expression.d.ts +55 -0
- package/dist/compiler/expression.js +858 -0
- package/dist/compiler/index.d.ts +8 -0
- package/dist/compiler/index.js +50 -0
- package/dist/compiler/ir.d.ts +4 -0
- package/dist/compiler/ir.js +255 -0
- package/dist/compiler/parser.d.ts +3 -0
- package/dist/compiler/parser.js +236 -0
- package/dist/compiler/sfc.d.ts +33 -0
- package/dist/compiler/sfc.js +439 -0
- package/dist/compiler/targets/client.d.ts +3 -0
- package/dist/compiler/targets/client.js +568 -0
- package/dist/compiler/targets/server.d.ts +7 -0
- package/dist/compiler/targets/server.js +396 -0
- package/dist/compiler/targets/stream.d.ts +2 -0
- package/dist/compiler/targets/stream.js +204 -0
- package/dist/compiler/types.d.ts +185 -0
- package/dist/compiler/types.js +1 -0
- package/dist/compiler/utils.d.ts +40 -0
- package/dist/compiler/utils.js +185 -0
- package/dist/compiler/whitespace.d.ts +2 -0
- package/dist/compiler/whitespace.js +66 -0
- package/dist/compiler.d.ts +1 -0
- package/dist/compiler.js +1 -0
- package/dist/constant-time.d.ts +1 -0
- package/dist/constant-time.js +26 -0
- package/dist/cookies.d.ts +44 -0
- package/dist/cookies.js +199 -0
- package/dist/diagnostics.d.ts +22 -0
- package/dist/diagnostics.js +45 -0
- package/dist/env.d.ts +27 -0
- package/dist/env.js +37 -0
- package/dist/html-escape.d.ts +2 -0
- package/dist/html-escape.js +20 -0
- package/dist/html-whitespace.d.ts +6 -0
- package/dist/html-whitespace.js +83 -0
- package/dist/i18n.d.ts +19 -0
- package/dist/i18n.js +93 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +18 -0
- package/dist/language-server.d.ts +17 -0
- package/dist/language-server.js +79 -0
- package/dist/package-integrity.d.ts +9 -0
- package/dist/package-integrity.js +64 -0
- package/dist/result.d.ts +13 -0
- package/dist/result.js +4 -0
- package/dist/router-node.d.ts +2 -0
- package/dist/router-node.js +12 -0
- package/dist/router.d.ts +324 -0
- package/dist/router.js +1024 -0
- package/dist/runtime/attr.d.ts +3 -0
- package/dist/runtime/attr.js +66 -0
- package/dist/runtime/class.d.ts +2 -0
- package/dist/runtime/class.js +10 -0
- package/dist/runtime/conditional.d.ts +78 -0
- package/dist/runtime/conditional.js +140 -0
- package/dist/runtime/enhancement.d.ts +11 -0
- package/dist/runtime/enhancement.js +70 -0
- package/dist/runtime/error-boundary.d.ts +8 -0
- package/dist/runtime/error-boundary.js +27 -0
- package/dist/runtime/event.d.ts +1 -0
- package/dist/runtime/event.js +11 -0
- package/dist/runtime/form.d.ts +41 -0
- package/dist/runtime/form.js +167 -0
- package/dist/runtime/fragment.d.ts +6 -0
- package/dist/runtime/fragment.js +18 -0
- package/dist/runtime/html.d.ts +8 -0
- package/dist/runtime/html.js +6 -0
- package/dist/runtime/hydrate.d.ts +68 -0
- package/dist/runtime/hydrate.js +205 -0
- package/dist/runtime/keyed-rows.d.ts +61 -0
- package/dist/runtime/keyed-rows.js +192 -0
- package/dist/runtime/list.d.ts +84 -0
- package/dist/runtime/list.js +424 -0
- package/dist/runtime/portal.d.ts +6 -0
- package/dist/runtime/portal.js +12 -0
- package/dist/runtime/router.d.ts +102 -0
- package/dist/runtime/router.js +649 -0
- package/dist/runtime/signal.d.ts +30 -0
- package/dist/runtime/signal.js +275 -0
- package/dist/runtime/store.d.ts +1 -0
- package/dist/runtime/store.js +1 -0
- package/dist/runtime/stream-client.d.ts +8 -0
- package/dist/runtime/stream-client.js +33 -0
- package/dist/runtime/text.d.ts +2 -0
- package/dist/runtime/text.js +10 -0
- package/dist/runtime/virtual-list.d.ts +15 -0
- package/dist/runtime/virtual-list.js +89 -0
- package/dist/security.d.ts +27 -0
- package/dist/security.js +173 -0
- package/dist/server/form-action.d.ts +52 -0
- package/dist/server/form-action.js +102 -0
- package/dist/server/html.d.ts +19 -0
- package/dist/server/html.js +101 -0
- package/dist/server/stream.d.ts +4 -0
- package/dist/server/stream.js +72 -0
- package/dist/source-map.d.ts +17 -0
- package/dist/source-map.js +18 -0
- package/dist/tachyon-html.d.ts +110 -0
- package/dist/testing.d.ts +14 -0
- package/dist/testing.js +36 -0
- package/dist/typed.d.ts +9 -0
- package/dist/typed.js +4 -0
- package/dist/vite.d.ts +84 -0
- package/dist/vite.js +426 -0
- package/package.json +604 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { generatedEscapeHtmlHelperLines } from "../../html-escape.js";
|
|
2
|
+
import { attrExpression, attrString, childPathEntries, escapeHtml, escapeMarker, expressionToScopeAccess, hydrationBoundaryFor, isHydrationAttribute, isVoidElement, jsOptionalPropertyAccess, jsString, itemNameFromKey, readExpressionAttribute, readPath, renderableChildren, serializeStaticAttr, textExpressionSegments, } from "../utils.js";
|
|
3
|
+
const componentScope = (node, scope) => {
|
|
4
|
+
const next = { ...scope };
|
|
5
|
+
for (const attr of node.attrs) {
|
|
6
|
+
if (attr.name === "name") {
|
|
7
|
+
continue;
|
|
8
|
+
}
|
|
9
|
+
const expression = readExpressionAttribute(attr.value);
|
|
10
|
+
if (expression) {
|
|
11
|
+
next[attr.name] = readPath(next, expression);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const applyStores = (child) => {
|
|
15
|
+
if (child.type !== "element" || child.tagName !== "store") {
|
|
16
|
+
if (child.type === "element" && child.tagName !== "component") {
|
|
17
|
+
child.children.forEach(applyStores);
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
for (const attr of child.attrs) {
|
|
22
|
+
const initial = readExpressionAttribute(attr.value);
|
|
23
|
+
if (initial) {
|
|
24
|
+
next[attr.name] = readPath(next, initial);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
node.children.forEach(applyStores);
|
|
29
|
+
return next;
|
|
30
|
+
};
|
|
31
|
+
const renderText = (node, scope) => {
|
|
32
|
+
let output = "";
|
|
33
|
+
let lastEmittedWasText = false;
|
|
34
|
+
const separateTextNode = () => {
|
|
35
|
+
if (lastEmittedWasText) {
|
|
36
|
+
output += "<!---->";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
for (const segment of textExpressionSegments(node.value)) {
|
|
40
|
+
if (segment.kind === "text") {
|
|
41
|
+
if (!segment.value) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
separateTextNode();
|
|
45
|
+
output += segment.value;
|
|
46
|
+
lastEmittedWasText = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
separateTextNode();
|
|
50
|
+
output += escapeHtml(readPath(scope, segment.value));
|
|
51
|
+
lastEmittedWasText = true;
|
|
52
|
+
}
|
|
53
|
+
return output;
|
|
54
|
+
};
|
|
55
|
+
const renderFor = (node, scope, path) => {
|
|
56
|
+
const each = attrExpression(node, "each");
|
|
57
|
+
const key = attrExpression(node, "key") ?? "item";
|
|
58
|
+
const itemName = itemNameFromKey(key);
|
|
59
|
+
const items = each ? readPath(scope, each) : undefined;
|
|
60
|
+
if (!Array.isArray(items)) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
return items
|
|
64
|
+
.map((item) => {
|
|
65
|
+
const childScope = { ...scope, [itemName]: item };
|
|
66
|
+
return childPathEntries(node.children, path)
|
|
67
|
+
.map((entry) => renderNode(entry.child, childScope, entry.path))
|
|
68
|
+
.join("");
|
|
69
|
+
})
|
|
70
|
+
.join("");
|
|
71
|
+
};
|
|
72
|
+
const renderElement = (node, scope, path = []) => {
|
|
73
|
+
if (node.tagName === "outlet") {
|
|
74
|
+
return String(scope.outlet ?? "");
|
|
75
|
+
}
|
|
76
|
+
if (node.tagName === "slot") {
|
|
77
|
+
const slots = scope.slots;
|
|
78
|
+
const name = attrString(node, "name") ?? "default";
|
|
79
|
+
return slots && typeof slots === "object" ? String(slots[name] ?? "") : "";
|
|
80
|
+
}
|
|
81
|
+
if (node.tagName === "for") {
|
|
82
|
+
return renderFor(node, scope, path);
|
|
83
|
+
}
|
|
84
|
+
if (node.tagName === "if") {
|
|
85
|
+
return readPath(scope, attrExpression(node, "test") ?? "false")
|
|
86
|
+
? node.children.map((child) => renderNode(child, scope)).join("")
|
|
87
|
+
: "";
|
|
88
|
+
}
|
|
89
|
+
if (node.tagName === "store") {
|
|
90
|
+
return "";
|
|
91
|
+
}
|
|
92
|
+
if (node.tagName === "component") {
|
|
93
|
+
const next = componentScope(node, scope);
|
|
94
|
+
const children = renderableChildren(node);
|
|
95
|
+
if (children.length === 1) {
|
|
96
|
+
return renderNode(children[0], next, path);
|
|
97
|
+
}
|
|
98
|
+
return children.map((child, index) => renderNode(child, next, [...path, index])).join("");
|
|
99
|
+
}
|
|
100
|
+
if (node.tagName === "await") {
|
|
101
|
+
const thenName = attrString(node, "then") ?? "value";
|
|
102
|
+
const value = readPath(scope, attrExpression(node, "value") ?? "undefined");
|
|
103
|
+
return node.children.map((child) => renderNode(child, { ...scope, [thenName]: value })).join("");
|
|
104
|
+
}
|
|
105
|
+
const attrs = [];
|
|
106
|
+
const classes = [];
|
|
107
|
+
const styles = [];
|
|
108
|
+
const hydrateBoundary = hydrationBoundaryFor(node, path);
|
|
109
|
+
for (const attr of node.attrs) {
|
|
110
|
+
if (attr.name.startsWith("on:") ||
|
|
111
|
+
attr.name.startsWith("bind:") ||
|
|
112
|
+
attr.name === "ref" ||
|
|
113
|
+
isHydrationAttribute(attr.name)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (attr.name.startsWith("style:")) {
|
|
117
|
+
const expression = readExpressionAttribute(attr.value);
|
|
118
|
+
const value = expression ? readPath(scope, expression) : undefined;
|
|
119
|
+
if (value != null && value !== false) {
|
|
120
|
+
styles.push(`${attr.name.slice(6)}:${String(value)}`);
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (attr.name.startsWith("class:")) {
|
|
125
|
+
const expression = readExpressionAttribute(attr.value);
|
|
126
|
+
if (expression && readPath(scope, expression)) {
|
|
127
|
+
classes.push(attr.name.slice(6));
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (attr.name === "class" && attr.value !== true) {
|
|
132
|
+
classes.push(attr.value);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const expression = readExpressionAttribute(attr.value);
|
|
136
|
+
if (expression) {
|
|
137
|
+
const value = readPath(scope, expression);
|
|
138
|
+
if (value != null && value !== false) {
|
|
139
|
+
attrs.push(` ${attr.name}="${escapeHtml(value)}"`);
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
attrs.push(serializeStaticAttr(attr));
|
|
144
|
+
}
|
|
145
|
+
if (classes.length > 0) {
|
|
146
|
+
attrs.unshift(` class="${escapeHtml(classes.join(" "))}"`);
|
|
147
|
+
}
|
|
148
|
+
if (styles.length > 0) {
|
|
149
|
+
attrs.push(` style="${escapeHtml(styles.join(";"))}"`);
|
|
150
|
+
}
|
|
151
|
+
const html = isVoidElement(node)
|
|
152
|
+
? `<${node.tagName}${attrs.join("")}>`
|
|
153
|
+
: `<${node.tagName}${attrs.join("")}>${childPathEntries(node.children, path)
|
|
154
|
+
.map((entry) => renderNode(entry.child, scope, entry.path))
|
|
155
|
+
.join("")}</${node.tagName}>`;
|
|
156
|
+
if (!hydrateBoundary) {
|
|
157
|
+
return html;
|
|
158
|
+
}
|
|
159
|
+
const marker = escapeMarker(hydrateBoundary.idKind === "static" ? hydrateBoundary.id : readPath(scope, hydrateBoundary.id));
|
|
160
|
+
return `<!--tachyon-hydrate:${marker}:start-->${html}<!--tachyon-hydrate:${marker}:end-->`;
|
|
161
|
+
};
|
|
162
|
+
const renderNode = (node, scope, path = []) => {
|
|
163
|
+
if (node.type === "text") {
|
|
164
|
+
return renderText(node, scope);
|
|
165
|
+
}
|
|
166
|
+
return renderElement(node, scope, path);
|
|
167
|
+
};
|
|
168
|
+
const renderTextExpression = (node, locals = new Set()) => {
|
|
169
|
+
const parts = [];
|
|
170
|
+
let lastEmittedWasText = false;
|
|
171
|
+
const separateTextNode = () => {
|
|
172
|
+
if (lastEmittedWasText) {
|
|
173
|
+
parts.push(jsString("<!---->"));
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
for (const segment of textExpressionSegments(node.value)) {
|
|
177
|
+
if (segment.kind === "text") {
|
|
178
|
+
if (segment.value) {
|
|
179
|
+
separateTextNode();
|
|
180
|
+
parts.push(jsString(segment.value));
|
|
181
|
+
lastEmittedWasText = true;
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
separateTextNode();
|
|
186
|
+
parts.push(`escapeHtml(${expressionToScopeAccess(segment.value, locals)})`);
|
|
187
|
+
lastEmittedWasText = true;
|
|
188
|
+
}
|
|
189
|
+
return parts.length > 0 ? parts.join(" + ") : `""`;
|
|
190
|
+
};
|
|
191
|
+
const foldStaticExpressionParts = (parts) => {
|
|
192
|
+
const folded = [];
|
|
193
|
+
let pending = "";
|
|
194
|
+
const flush = () => {
|
|
195
|
+
if (pending) {
|
|
196
|
+
folded.push(jsString(pending));
|
|
197
|
+
pending = "";
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
for (const part of parts) {
|
|
201
|
+
if (part.startsWith(`"`) && part.endsWith(`"`)) {
|
|
202
|
+
pending += JSON.parse(part);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
flush();
|
|
206
|
+
folded.push(part);
|
|
207
|
+
}
|
|
208
|
+
flush();
|
|
209
|
+
return folded;
|
|
210
|
+
};
|
|
211
|
+
export const renderOpenTagExpression = (node, locals) => {
|
|
212
|
+
const parts = [jsString(`<${node.tagName}`)];
|
|
213
|
+
const staticClasses = [];
|
|
214
|
+
const dynamicClasses = [];
|
|
215
|
+
const dynamicStyles = [];
|
|
216
|
+
for (const attr of node.attrs) {
|
|
217
|
+
if (attr.name.startsWith("on:") ||
|
|
218
|
+
attr.name.startsWith("bind:") ||
|
|
219
|
+
attr.name === "ref" ||
|
|
220
|
+
isHydrationAttribute(attr.name)) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (attr.name.startsWith("style:")) {
|
|
224
|
+
const expression = readExpressionAttribute(attr.value);
|
|
225
|
+
if (expression) {
|
|
226
|
+
const value = expressionToScopeAccess(expression, locals);
|
|
227
|
+
dynamicStyles.push(`(${value} == null || ${value} === false ? "" : ${jsString(`${attr.name.slice(6)}:`)} + escapeHtml(${value}) + ${jsString(";")})`);
|
|
228
|
+
}
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (attr.name.startsWith("class:")) {
|
|
232
|
+
const expression = readExpressionAttribute(attr.value);
|
|
233
|
+
if (expression) {
|
|
234
|
+
dynamicClasses.push(`(${expressionToScopeAccess(expression, locals)} ? ${jsString(` ${attr.name.slice(6)}`)} : "")`);
|
|
235
|
+
}
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (attr.name === "class" && attr.value !== true) {
|
|
239
|
+
staticClasses.push(attr.value);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const expression = readExpressionAttribute(attr.value);
|
|
243
|
+
if (expression) {
|
|
244
|
+
const value = expressionToScopeAccess(expression, locals);
|
|
245
|
+
parts.push(`(${value} == null || ${value} === false ? "" : ${jsString(` ${attr.name}="`)} + escapeHtml(${value}) + ${jsString(`"`)} )`);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
parts.push(jsString(serializeStaticAttr(attr)));
|
|
249
|
+
}
|
|
250
|
+
if (staticClasses.length > 0 || dynamicClasses.length > 0) {
|
|
251
|
+
const classExpression = `${jsString(staticClasses.join(" "))}${dynamicClasses.length > 0 ? ` + ${dynamicClasses.join(" + ")}` : ""}`;
|
|
252
|
+
parts.splice(1, 0, dynamicClasses.length === 0
|
|
253
|
+
? jsString(` class="${staticClasses.join(" ")}"`)
|
|
254
|
+
: `(${classExpression} ? ${jsString(` class="`)} + (${classExpression}).trim() + ${jsString(`"`)} : "")`);
|
|
255
|
+
}
|
|
256
|
+
if (dynamicStyles.length > 0) {
|
|
257
|
+
const styleExpression = dynamicStyles.join(" + ");
|
|
258
|
+
parts.push(`(${styleExpression} ? ${jsString(` style="`)} + (${styleExpression}).replace(/;$/, "") + ${jsString(`"`)} : "")`);
|
|
259
|
+
}
|
|
260
|
+
parts.push(jsString(">"));
|
|
261
|
+
return foldStaticExpressionParts(parts).join(" + ");
|
|
262
|
+
};
|
|
263
|
+
export const renderNodeExpression = (node, locals = new Set(), path = []) => {
|
|
264
|
+
if (node.type === "text") {
|
|
265
|
+
return renderTextExpression(node, locals);
|
|
266
|
+
}
|
|
267
|
+
return renderElementExpression(node, locals, path);
|
|
268
|
+
};
|
|
269
|
+
const renderForExpression = (node, locals, path) => {
|
|
270
|
+
const each = attrExpression(node, "each") ?? "[]";
|
|
271
|
+
const key = attrExpression(node, "key") ?? "item";
|
|
272
|
+
const itemName = itemNameFromKey(key);
|
|
273
|
+
const eachAccess = expressionToScopeAccess(each, locals);
|
|
274
|
+
const childLocals = new Set(locals);
|
|
275
|
+
childLocals.add(itemName);
|
|
276
|
+
const childExpression = childPathEntries(node.children, path)
|
|
277
|
+
.map((entry) => renderNodeExpression(entry.child, childLocals, entry.path))
|
|
278
|
+
.join(" + ");
|
|
279
|
+
return `(Array.isArray(${eachAccess}) ? ${eachAccess}.map((${itemName}) => ${childExpression || `""`}).join("") : "")`;
|
|
280
|
+
};
|
|
281
|
+
const renderElementExpression = (node, locals = new Set(), path = []) => {
|
|
282
|
+
if (node.tagName === "outlet") {
|
|
283
|
+
return `String(scope.outlet ?? "")`;
|
|
284
|
+
}
|
|
285
|
+
if (node.tagName === "slot") {
|
|
286
|
+
const name = attrString(node, "name") ?? "default";
|
|
287
|
+
return `String(${jsOptionalPropertyAccess("scope.slots", name)} ?? "")`;
|
|
288
|
+
}
|
|
289
|
+
if (node.tagName === "for") {
|
|
290
|
+
return renderForExpression(node, locals, path);
|
|
291
|
+
}
|
|
292
|
+
if (node.tagName === "if") {
|
|
293
|
+
const test = expressionToScopeAccess(attrExpression(node, "test") ?? "false", locals);
|
|
294
|
+
const childExpression = childPathEntries(node.children, path)
|
|
295
|
+
.map((entry) => renderNodeExpression(entry.child, locals, entry.path))
|
|
296
|
+
.join(" + ");
|
|
297
|
+
return `(${test} ? ${childExpression || `""`} : "")`;
|
|
298
|
+
}
|
|
299
|
+
if (node.tagName === "store") {
|
|
300
|
+
return `""`;
|
|
301
|
+
}
|
|
302
|
+
if (node.tagName === "component") {
|
|
303
|
+
return renderComponentExpression(node, locals, path);
|
|
304
|
+
}
|
|
305
|
+
if (node.tagName === "await") {
|
|
306
|
+
const value = expressionToScopeAccess(attrExpression(node, "value") ?? "undefined", locals);
|
|
307
|
+
const thenName = attrString(node, "then") ?? "value";
|
|
308
|
+
const childLocals = new Set(locals);
|
|
309
|
+
childLocals.add(thenName);
|
|
310
|
+
const childExpression = childPathEntries(node.children, path)
|
|
311
|
+
.map((entry) => renderNodeExpression(entry.child, childLocals, entry.path))
|
|
312
|
+
.join(" + ");
|
|
313
|
+
return `(((${thenName}) => ${childExpression || `""`})(${value}))`;
|
|
314
|
+
}
|
|
315
|
+
const parts = [renderOpenTagExpression(node, locals)];
|
|
316
|
+
parts.push(...childPathEntries(node.children, path).map((entry) => renderNodeExpression(entry.child, locals, entry.path)));
|
|
317
|
+
if (!isVoidElement(node)) {
|
|
318
|
+
parts.push(jsString(`</${node.tagName}>`));
|
|
319
|
+
}
|
|
320
|
+
const expression = parts.join(" + ");
|
|
321
|
+
const hydrateBoundary = hydrationBoundaryFor(node, path);
|
|
322
|
+
if (!hydrateBoundary) {
|
|
323
|
+
return expression;
|
|
324
|
+
}
|
|
325
|
+
const marker = hydrateBoundary.idKind === "static"
|
|
326
|
+
? jsString(hydrateBoundary.id)
|
|
327
|
+
: expressionToScopeAccess(hydrateBoundary.id, locals);
|
|
328
|
+
return `${jsString("<!--tachyon-hydrate:")} + escapeMarker(${marker}) + ${jsString(":start-->")} + ${expression} + ${jsString("<!--tachyon-hydrate:")} + escapeMarker(${marker}) + ${jsString(":end-->")}`;
|
|
329
|
+
};
|
|
330
|
+
const renderComponentExpression = (node, locals, path) => {
|
|
331
|
+
const localNames = new Set(locals);
|
|
332
|
+
const declarations = [];
|
|
333
|
+
for (const attr of node.attrs) {
|
|
334
|
+
if (attr.name === "name") {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const expression = readExpressionAttribute(attr.value);
|
|
338
|
+
if (expression) {
|
|
339
|
+
declarations.push(`const ${attr.name} = ${expressionToScopeAccess(expression, localNames)};`);
|
|
340
|
+
localNames.add(attr.name);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const applyStoreDeclarations = (child) => {
|
|
344
|
+
if (child.type !== "element" || child.tagName !== "store") {
|
|
345
|
+
if (child.type === "element" && child.tagName !== "component") {
|
|
346
|
+
child.children.forEach(applyStoreDeclarations);
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
for (const attr of child.attrs) {
|
|
351
|
+
const initial = readExpressionAttribute(attr.value);
|
|
352
|
+
if (initial) {
|
|
353
|
+
declarations.push(`const ${attr.name} = ${expressionToScopeAccess(initial, localNames)};`);
|
|
354
|
+
localNames.add(attr.name);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
node.children.forEach(applyStoreDeclarations);
|
|
359
|
+
const children = renderableChildren(node);
|
|
360
|
+
const expression = children.length === 1
|
|
361
|
+
? renderNodeExpression(children[0], localNames, path)
|
|
362
|
+
: children.map((child, index) => renderNodeExpression(child, localNames, [...path, index])).join(" + ") || `""`;
|
|
363
|
+
return `(() => { ${declarations.join(" ")} return ${expression}; })()`;
|
|
364
|
+
};
|
|
365
|
+
const serverModuleCache = new WeakMap();
|
|
366
|
+
export const generateServerModule = (template) => {
|
|
367
|
+
const cached = serverModuleCache.get(template);
|
|
368
|
+
if (cached) {
|
|
369
|
+
return cached;
|
|
370
|
+
}
|
|
371
|
+
const lines = [
|
|
372
|
+
...generatedEscapeHtmlHelperLines,
|
|
373
|
+
`const escapeMarker = (value) => String(value ?? "").replaceAll("--", "- -").replaceAll(">", ">");`,
|
|
374
|
+
`const escapeScriptJson = (value) => value.replaceAll("<", "\\\\u003c").replaceAll(">", "\\\\u003e");`,
|
|
375
|
+
`const ATTRIBUTE_ESCAPE = { "&": "&", '"': """, "<": "<" };`,
|
|
376
|
+
`const escapeAttribute = (value) => String(value).replace(/[&"<]/g, (char) => ATTRIBUTE_ESCAPE[char]);`,
|
|
377
|
+
`export const hydrationBoundaries = ${JSON.stringify(template.client.hydrationBoundaries)};`,
|
|
378
|
+
`export const renderHydrationState = (id, state) => '<script type="application/json" data-tachyon-state="' + escapeAttribute(id) + '">' + escapeScriptJson(JSON.stringify(state) ?? "null") + '</script>';`,
|
|
379
|
+
`export const render = (scope) => ${renderElementExpression(template.root)};`,
|
|
380
|
+
];
|
|
381
|
+
const code = `${lines.join("\n")}\n`;
|
|
382
|
+
serverModuleCache.set(template, code);
|
|
383
|
+
return code;
|
|
384
|
+
};
|
|
385
|
+
const serverRendererCache = new WeakMap();
|
|
386
|
+
export const compileServerTemplate = (template) => {
|
|
387
|
+
const cached = serverRendererCache.get(template);
|
|
388
|
+
if (cached) {
|
|
389
|
+
return cached;
|
|
390
|
+
}
|
|
391
|
+
const root = structuredClone(template.root);
|
|
392
|
+
const renderer = (scope) => renderNode(root, scope);
|
|
393
|
+
serverRendererCache.set(template, renderer);
|
|
394
|
+
return renderer;
|
|
395
|
+
};
|
|
396
|
+
export const renderServerTemplate = (template, scope) => compileServerTemplate(template)(scope);
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { generatedEscapeHtmlHelperLines } from "../../html-escape.js";
|
|
2
|
+
import { attrExpression, attrString, childPathEntries, expressionToScopeAccess, hydrationBoundaryFor, isVoidElement, itemNameFromKey, jsOptionalPropertyAccess, jsString, readExpressionAttribute, renderableChildren, textExpressionSegments, } from "../utils.js";
|
|
3
|
+
import { renderOpenTagExpression } from "./server.js";
|
|
4
|
+
const renderTextYieldStatements = (node, locals, indent) => {
|
|
5
|
+
const statements = [];
|
|
6
|
+
let lastEmittedWasText = false;
|
|
7
|
+
const separateTextNode = () => {
|
|
8
|
+
if (lastEmittedWasText) {
|
|
9
|
+
statements.push(`${indent}yield "<!---->";`);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
for (const segment of textExpressionSegments(node.value)) {
|
|
13
|
+
if (segment.kind === "text") {
|
|
14
|
+
if (segment.value) {
|
|
15
|
+
separateTextNode();
|
|
16
|
+
statements.push(`${indent}yield ${jsString(segment.value)};`);
|
|
17
|
+
lastEmittedWasText = true;
|
|
18
|
+
}
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
separateTextNode();
|
|
22
|
+
statements.push(`${indent}yield escapeHtml(${expressionToScopeAccess(segment.value, locals)});`);
|
|
23
|
+
lastEmittedWasText = true;
|
|
24
|
+
}
|
|
25
|
+
return statements;
|
|
26
|
+
};
|
|
27
|
+
const renderForYieldStatements = (node, locals, indent, path) => {
|
|
28
|
+
const each = attrExpression(node, "each") ?? "[]";
|
|
29
|
+
const key = attrExpression(node, "key") ?? "item";
|
|
30
|
+
const itemName = itemNameFromKey(key);
|
|
31
|
+
const eachAccess = expressionToScopeAccess(each, locals);
|
|
32
|
+
const childLocals = new Set(locals);
|
|
33
|
+
childLocals.add(itemName);
|
|
34
|
+
const statements = [
|
|
35
|
+
`${indent}if (Array.isArray(${eachAccess})) {`,
|
|
36
|
+
`${indent} for (const ${itemName} of ${eachAccess}) {`,
|
|
37
|
+
];
|
|
38
|
+
for (const entry of childPathEntries(node.children, path)) {
|
|
39
|
+
statements.push(...renderNodeYieldStatements(entry.child, childLocals, `${indent} `, entry.path));
|
|
40
|
+
}
|
|
41
|
+
statements.push(`${indent} }`, `${indent}}`);
|
|
42
|
+
return statements;
|
|
43
|
+
};
|
|
44
|
+
const renderElementYieldStatements = (node, locals, indent, path) => {
|
|
45
|
+
if (node.tagName === "outlet") {
|
|
46
|
+
return [`${indent}yield String(scope.outlet ?? "");`];
|
|
47
|
+
}
|
|
48
|
+
if (node.tagName === "slot") {
|
|
49
|
+
return [
|
|
50
|
+
`${indent}yield String(${jsOptionalPropertyAccess("scope.slots", attrString(node, "name") ?? "default")} ?? "");`,
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
if (node.tagName === "for") {
|
|
54
|
+
return renderForYieldStatements(node, locals, indent, path);
|
|
55
|
+
}
|
|
56
|
+
if (node.tagName === "if") {
|
|
57
|
+
const statements = [`${indent}if (${expressionToScopeAccess(attrExpression(node, "test") ?? "false", locals)}) {`];
|
|
58
|
+
for (const entry of childPathEntries(node.children, path)) {
|
|
59
|
+
statements.push(...renderNodeYieldStatements(entry.child, locals, `${indent} `, entry.path));
|
|
60
|
+
}
|
|
61
|
+
statements.push(`${indent}}`);
|
|
62
|
+
return statements;
|
|
63
|
+
}
|
|
64
|
+
if (node.tagName === "store") {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
if (node.tagName === "component") {
|
|
68
|
+
return renderComponentYieldStatements(node, locals, indent, path);
|
|
69
|
+
}
|
|
70
|
+
if (node.tagName === "await") {
|
|
71
|
+
const thenName = attrString(node, "then") ?? "value";
|
|
72
|
+
const fallback = attrString(node, "fallback");
|
|
73
|
+
const errorText = attrString(node, "error");
|
|
74
|
+
const childLocals = new Set(locals);
|
|
75
|
+
childLocals.add(thenName);
|
|
76
|
+
const statements = [`${indent}{`];
|
|
77
|
+
if (fallback) {
|
|
78
|
+
statements.push(`${indent} yield ${jsString(fallback)};`);
|
|
79
|
+
}
|
|
80
|
+
if (errorText) {
|
|
81
|
+
statements.push(`${indent} try {`);
|
|
82
|
+
statements.push(`${indent} const ${thenName} = await ${expressionToScopeAccess(attrExpression(node, "value") ?? "undefined", locals)};`);
|
|
83
|
+
for (const entry of childPathEntries(node.children, path)) {
|
|
84
|
+
statements.push(...renderNodeYieldStatements(entry.child, childLocals, `${indent} `, entry.path));
|
|
85
|
+
}
|
|
86
|
+
statements.push(`${indent} } catch {`);
|
|
87
|
+
statements.push(`${indent} yield ${jsString(errorText)};`);
|
|
88
|
+
statements.push(`${indent} }`);
|
|
89
|
+
statements.push(`${indent}}`);
|
|
90
|
+
return statements;
|
|
91
|
+
}
|
|
92
|
+
statements.push(`${indent} const ${thenName} = await ${expressionToScopeAccess(attrExpression(node, "value") ?? "undefined", locals)};`);
|
|
93
|
+
for (const entry of childPathEntries(node.children, path)) {
|
|
94
|
+
statements.push(...renderNodeYieldStatements(entry.child, childLocals, `${indent} `, entry.path));
|
|
95
|
+
}
|
|
96
|
+
statements.push(`${indent}}`);
|
|
97
|
+
return statements;
|
|
98
|
+
}
|
|
99
|
+
const hydrateBoundary = hydrationBoundaryFor(node, path);
|
|
100
|
+
const statements = [];
|
|
101
|
+
if (hydrateBoundary) {
|
|
102
|
+
const marker = hydrateBoundary.idKind === "static"
|
|
103
|
+
? jsString(hydrateBoundary.id)
|
|
104
|
+
: expressionToScopeAccess(hydrateBoundary.id, locals);
|
|
105
|
+
statements.push(`${indent}yield ${jsString("<!--tachyon-hydrate:")} + escapeMarker(${marker}) + ${jsString(":start-->")};`);
|
|
106
|
+
}
|
|
107
|
+
statements.push(`${indent}yield ${renderOpenTagExpression(node, locals)};`);
|
|
108
|
+
for (const entry of childPathEntries(node.children, path)) {
|
|
109
|
+
statements.push(...renderNodeYieldStatements(entry.child, locals, indent, entry.path));
|
|
110
|
+
}
|
|
111
|
+
if (!isVoidElement(node)) {
|
|
112
|
+
statements.push(`${indent}yield ${jsString(`</${node.tagName}>`)};`);
|
|
113
|
+
}
|
|
114
|
+
if (hydrateBoundary) {
|
|
115
|
+
const marker = hydrateBoundary.idKind === "static"
|
|
116
|
+
? jsString(hydrateBoundary.id)
|
|
117
|
+
: expressionToScopeAccess(hydrateBoundary.id, locals);
|
|
118
|
+
statements.push(`${indent}yield ${jsString("<!--tachyon-hydrate:")} + escapeMarker(${marker}) + ${jsString(":end-->")};`);
|
|
119
|
+
}
|
|
120
|
+
return statements;
|
|
121
|
+
};
|
|
122
|
+
const renderComponentYieldStatements = (node, locals, indent, path) => {
|
|
123
|
+
const localNames = new Set(locals);
|
|
124
|
+
const statements = [`${indent}{`];
|
|
125
|
+
for (const attr of node.attrs) {
|
|
126
|
+
if (attr.name === "name") {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const expression = readExpressionAttribute(attr.value);
|
|
130
|
+
if (expression) {
|
|
131
|
+
statements.push(`${indent} const ${attr.name} = ${expressionToScopeAccess(expression, localNames)};`);
|
|
132
|
+
localNames.add(attr.name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const child of node.children) {
|
|
136
|
+
if (child.type !== "element" || child.tagName !== "store") {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
for (const attr of child.attrs) {
|
|
140
|
+
const initial = readExpressionAttribute(attr.value);
|
|
141
|
+
if (initial) {
|
|
142
|
+
statements.push(`${indent} const ${attr.name} = ${expressionToScopeAccess(initial, localNames)};`);
|
|
143
|
+
localNames.add(attr.name);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const children = renderableChildren(node);
|
|
148
|
+
if (children.length === 1) {
|
|
149
|
+
statements.push(...renderNodeYieldStatements(children[0], localNames, `${indent} `, path));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
for (const [index, child] of children.entries()) {
|
|
153
|
+
statements.push(...renderNodeYieldStatements(child, localNames, `${indent} `, [...path, index]));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
statements.push(`${indent}}`);
|
|
157
|
+
return statements;
|
|
158
|
+
};
|
|
159
|
+
const renderNodeYieldStatements = (node, locals, indent, path = []) => {
|
|
160
|
+
if (node.type === "text") {
|
|
161
|
+
return renderTextYieldStatements(node, locals, indent);
|
|
162
|
+
}
|
|
163
|
+
return renderElementYieldStatements(node, locals, indent, path);
|
|
164
|
+
};
|
|
165
|
+
const streamModuleCache = new WeakMap();
|
|
166
|
+
export const generateServerStreamModule = (template) => {
|
|
167
|
+
const cached = streamModuleCache.get(template);
|
|
168
|
+
if (cached) {
|
|
169
|
+
return cached;
|
|
170
|
+
}
|
|
171
|
+
const coalescedStatements = renderNodeYieldStatements(template.root, new Set(), " ").flatMap((line) => {
|
|
172
|
+
const yieldMatch = /^(\s*)yield (.*);$/.exec(line);
|
|
173
|
+
if (yieldMatch) {
|
|
174
|
+
return [
|
|
175
|
+
`${yieldMatch[1]}__tachyonPush(${yieldMatch[2]});`,
|
|
176
|
+
`${yieldMatch[1]}if (__tachyonBufferBytes >= __tachyonFlushBytes) { yield __tachyonBuffer; __tachyonBuffer = ""; __tachyonBufferBytes = 0; }`,
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
if (line.includes(" await ")) {
|
|
180
|
+
const indent = line.match(/^\s*/)?.[0] ?? "";
|
|
181
|
+
return [
|
|
182
|
+
`${indent}if (__tachyonBuffer) { yield __tachyonBuffer; __tachyonBuffer = ""; __tachyonBufferBytes = 0; }`,
|
|
183
|
+
line,
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
return [line];
|
|
187
|
+
});
|
|
188
|
+
const lines = [
|
|
189
|
+
...generatedEscapeHtmlHelperLines,
|
|
190
|
+
`const escapeMarker = (value) => String(value ?? "").replaceAll("--", "- -").replaceAll(">", ">");`,
|
|
191
|
+
`export const stream = async function* (scope) {`,
|
|
192
|
+
` const __tachyonFlushBytes = 8192;`,
|
|
193
|
+
` const __tachyonTextEncoder = new TextEncoder();`,
|
|
194
|
+
` let __tachyonBuffer = "";`,
|
|
195
|
+
` let __tachyonBufferBytes = 0;`,
|
|
196
|
+
` const __tachyonPush = (chunk) => { const text = String(chunk ?? ""); __tachyonBuffer += text; __tachyonBufferBytes += __tachyonTextEncoder.encode(text).byteLength; };`,
|
|
197
|
+
...coalescedStatements,
|
|
198
|
+
` if (__tachyonBuffer) { yield __tachyonBuffer; }`,
|
|
199
|
+
`};`,
|
|
200
|
+
];
|
|
201
|
+
const code = `${lines.join("\n")}\n`;
|
|
202
|
+
streamModuleCache.set(template, code);
|
|
203
|
+
return code;
|
|
204
|
+
};
|