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,568 @@
|
|
|
1
|
+
import { storeDefinitionsFor } from "../ir.js";
|
|
2
|
+
import { attrExpression, expressionToScopeAccess, hydrationBoundaryFor, isHydrationAttribute, isForNode, isVoidElement, isStoreNode, itemNameFromKey, attrString, readExpressionAttribute, renderableChildren, serializeStaticAttr, textExpressionSegments, } from "../utils.js";
|
|
3
|
+
import { isAssignableExpression } from "../expression.js";
|
|
4
|
+
const lowerTextNode = (node, path, context) => {
|
|
5
|
+
let output = "";
|
|
6
|
+
let nodeOffset = 0;
|
|
7
|
+
let lastEmittedWasText = false;
|
|
8
|
+
const separateTextNode = () => {
|
|
9
|
+
if (lastEmittedWasText) {
|
|
10
|
+
output += "<!---->";
|
|
11
|
+
nodeOffset++;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
for (const segment of textExpressionSegments(node.value)) {
|
|
15
|
+
if (segment.kind === "text") {
|
|
16
|
+
if (!segment.value) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
separateTextNode();
|
|
20
|
+
output += segment.value;
|
|
21
|
+
nodeOffset++;
|
|
22
|
+
lastEmittedWasText = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
separateTextNode();
|
|
26
|
+
context.bindings.push({
|
|
27
|
+
kind: "text",
|
|
28
|
+
path: [...path.slice(0, -1), (path.at(-1) ?? 0) + nodeOffset],
|
|
29
|
+
expression: segment.value,
|
|
30
|
+
});
|
|
31
|
+
output += " ";
|
|
32
|
+
nodeOffset++;
|
|
33
|
+
lastEmittedWasText = true;
|
|
34
|
+
}
|
|
35
|
+
return { html: output, nodeCount: nodeOffset };
|
|
36
|
+
};
|
|
37
|
+
const addStoreDefinitions = (node, context) => {
|
|
38
|
+
context.stores.push(...storeDefinitionsFor(node));
|
|
39
|
+
};
|
|
40
|
+
const componentStores = (node) => {
|
|
41
|
+
const stores = [];
|
|
42
|
+
const visit = (child) => {
|
|
43
|
+
if (child.type !== "element" || child.tagName === "component") {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (child.tagName === "store") {
|
|
47
|
+
stores.push(...storeDefinitionsFor(child));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
child.children.forEach(visit);
|
|
51
|
+
};
|
|
52
|
+
node.children.forEach(visit);
|
|
53
|
+
return stores;
|
|
54
|
+
};
|
|
55
|
+
const lowerComponent = (node, path, context) => {
|
|
56
|
+
context.components.push({
|
|
57
|
+
path: [...path],
|
|
58
|
+
name: attrString(node, "name") ?? "Anonymous",
|
|
59
|
+
props: node.attrs.flatMap((attr) => {
|
|
60
|
+
if (attr.name === "name") {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const expression = readExpressionAttribute(attr.value);
|
|
64
|
+
return expression ? [{ name: attr.name, expression }] : [];
|
|
65
|
+
}),
|
|
66
|
+
stores: componentStores(node),
|
|
67
|
+
});
|
|
68
|
+
const children = renderableChildren(node);
|
|
69
|
+
if (children.length === 0) {
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
if (children.length === 1) {
|
|
73
|
+
return lowerNode(children[0], path, context).html;
|
|
74
|
+
}
|
|
75
|
+
let html = "";
|
|
76
|
+
let domIndex = 0;
|
|
77
|
+
for (const child of children) {
|
|
78
|
+
const lowered = lowerNode(child, [...path, domIndex], context);
|
|
79
|
+
html += lowered.html;
|
|
80
|
+
domIndex += lowered.nodeCount;
|
|
81
|
+
}
|
|
82
|
+
return html;
|
|
83
|
+
};
|
|
84
|
+
const lowerIf = (node, path, context) => {
|
|
85
|
+
const childContext = {
|
|
86
|
+
bindings: [],
|
|
87
|
+
stores: [],
|
|
88
|
+
hydrationBoundaries: [],
|
|
89
|
+
components: [],
|
|
90
|
+
};
|
|
91
|
+
const children = renderableChildren(node);
|
|
92
|
+
let templateHtml = "";
|
|
93
|
+
let domIndex = 0;
|
|
94
|
+
for (const child of children) {
|
|
95
|
+
const lowered = lowerNode(child, children.length === 1 ? [] : [domIndex], childContext);
|
|
96
|
+
templateHtml += lowered.html;
|
|
97
|
+
domIndex += lowered.nodeCount;
|
|
98
|
+
}
|
|
99
|
+
context.bindings.push({
|
|
100
|
+
kind: "if",
|
|
101
|
+
path: [...path],
|
|
102
|
+
test: attrExpression(node, "test") ?? "false",
|
|
103
|
+
templateHtml,
|
|
104
|
+
bindings: childContext.bindings,
|
|
105
|
+
});
|
|
106
|
+
return "<!---->";
|
|
107
|
+
};
|
|
108
|
+
const lowerList = (node, containerPath) => {
|
|
109
|
+
const key = attrExpression(node, "key") ?? "item";
|
|
110
|
+
const childContext = {
|
|
111
|
+
bindings: [],
|
|
112
|
+
stores: [],
|
|
113
|
+
hydrationBoundaries: [],
|
|
114
|
+
components: [],
|
|
115
|
+
};
|
|
116
|
+
const children = renderableChildren(node);
|
|
117
|
+
let templateHtml = "";
|
|
118
|
+
let domIndex = 0;
|
|
119
|
+
for (const child of children) {
|
|
120
|
+
const lowered = lowerNode(child, children.length === 1 ? [] : [domIndex], childContext);
|
|
121
|
+
templateHtml += lowered.html;
|
|
122
|
+
domIndex += lowered.nodeCount;
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
kind: "list",
|
|
126
|
+
path: [...containerPath],
|
|
127
|
+
each: attrExpression(node, "each") ?? "[]",
|
|
128
|
+
itemName: itemNameFromKey(key),
|
|
129
|
+
key,
|
|
130
|
+
templateHtml,
|
|
131
|
+
bindings: childContext.bindings,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
const lowerElement = (node, path, context) => {
|
|
135
|
+
if (node.tagName === "outlet") {
|
|
136
|
+
return "<!--tachyon-outlet-->";
|
|
137
|
+
}
|
|
138
|
+
if (node.tagName === "slot") {
|
|
139
|
+
return `<!--tachyon-slot:${attrString(node, "name") ?? "default"}-->`;
|
|
140
|
+
}
|
|
141
|
+
if (node.tagName === "for") {
|
|
142
|
+
context.bindings.push(lowerList(node, path));
|
|
143
|
+
return "";
|
|
144
|
+
}
|
|
145
|
+
if (node.tagName === "if") {
|
|
146
|
+
return lowerIf(node, path, context);
|
|
147
|
+
}
|
|
148
|
+
if (node.tagName === "store") {
|
|
149
|
+
addStoreDefinitions(node, context);
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
if (node.tagName === "component") {
|
|
153
|
+
return lowerComponent(node, path, context);
|
|
154
|
+
}
|
|
155
|
+
const attrs = [];
|
|
156
|
+
const staticClassNames = [];
|
|
157
|
+
const hydrateBoundary = hydrationBoundaryFor(node, path);
|
|
158
|
+
if (hydrateBoundary) {
|
|
159
|
+
context.hydrationBoundaries.push(hydrateBoundary);
|
|
160
|
+
}
|
|
161
|
+
for (const attr of node.attrs) {
|
|
162
|
+
if (isHydrationAttribute(attr.name)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (attr.name.startsWith("on:")) {
|
|
166
|
+
const handler = readExpressionAttribute(attr.value);
|
|
167
|
+
if (handler) {
|
|
168
|
+
context.bindings.push({ kind: "event", path: [...path], eventName: attr.name.slice(3), handler });
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (attr.name.startsWith("bind:")) {
|
|
173
|
+
const expression = readExpressionAttribute(attr.value);
|
|
174
|
+
if (expression && isAssignableExpression(expression)) {
|
|
175
|
+
const property = attr.name.slice(5) === "checked" ? "checked" : "value";
|
|
176
|
+
context.bindings.push({ kind: "model", path: [...path], property, expression });
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (attr.name === "ref") {
|
|
181
|
+
const expression = readExpressionAttribute(attr.value);
|
|
182
|
+
if (expression) {
|
|
183
|
+
context.bindings.push({ kind: "ref", path: [...path], expression });
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (attr.name.startsWith("style:")) {
|
|
188
|
+
const expression = readExpressionAttribute(attr.value);
|
|
189
|
+
if (expression) {
|
|
190
|
+
context.bindings.push({ kind: "style", path: [...path], name: attr.name.slice(6), expression });
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (attr.name.startsWith("class:")) {
|
|
195
|
+
const expression = readExpressionAttribute(attr.value);
|
|
196
|
+
if (expression) {
|
|
197
|
+
context.bindings.push({ kind: "class", path: [...path], className: attr.name.slice(6), expression });
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const expression = readExpressionAttribute(attr.value);
|
|
202
|
+
if (expression) {
|
|
203
|
+
context.bindings.push({ kind: "attr", path: [...path], name: attr.name, expression });
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (attr.name === "class" && attr.value !== true) {
|
|
207
|
+
staticClassNames.push(attr.value);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
attrs.push(serializeStaticAttr(attr));
|
|
211
|
+
}
|
|
212
|
+
if (staticClassNames.length > 0) {
|
|
213
|
+
attrs.unshift(` class="${staticClassNames.join(" ")}"`);
|
|
214
|
+
}
|
|
215
|
+
let children = "";
|
|
216
|
+
let domIndex = 0;
|
|
217
|
+
for (const child of node.children) {
|
|
218
|
+
if (isForNode(child)) {
|
|
219
|
+
context.bindings.push(lowerList(child, path));
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (isStoreNode(child)) {
|
|
223
|
+
addStoreDefinitions(child, context);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const lowered = lowerNode(child, [...path, domIndex], context);
|
|
227
|
+
children += lowered.html;
|
|
228
|
+
domIndex += lowered.nodeCount;
|
|
229
|
+
}
|
|
230
|
+
return isVoidElement(node)
|
|
231
|
+
? `<${node.tagName}${attrs.join("")}>`
|
|
232
|
+
: `<${node.tagName}${attrs.join("")}>${children}</${node.tagName}>`;
|
|
233
|
+
};
|
|
234
|
+
const lowerNode = (node, path, context) => {
|
|
235
|
+
if (node.type === "text") {
|
|
236
|
+
return lowerTextNode(node, path, context);
|
|
237
|
+
}
|
|
238
|
+
return { html: lowerElement(node, path, context), nodeCount: 1 };
|
|
239
|
+
};
|
|
240
|
+
export const lowerClientTemplate = (root) => {
|
|
241
|
+
const context = {
|
|
242
|
+
bindings: [],
|
|
243
|
+
stores: [],
|
|
244
|
+
hydrationBoundaries: [],
|
|
245
|
+
components: [],
|
|
246
|
+
};
|
|
247
|
+
const templateHtml = lowerElement(root, [], context);
|
|
248
|
+
return {
|
|
249
|
+
templateHtml,
|
|
250
|
+
bindings: context.bindings,
|
|
251
|
+
stores: context.stores,
|
|
252
|
+
hydrationBoundaries: context.hydrationBoundaries,
|
|
253
|
+
components: context.components,
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
const scopeName = (usesStore) => (usesStore ? "state" : "scope");
|
|
257
|
+
const runtimeNames = {
|
|
258
|
+
bindControl: "__tachyonBindControl",
|
|
259
|
+
createStore: "__tachyonCreateStore",
|
|
260
|
+
delegate: "__tachyonDelegate",
|
|
261
|
+
effect: "__tachyonEffect",
|
|
262
|
+
elementAt: "__tachyonElementAt",
|
|
263
|
+
mountConditional: "__tachyonMountConditional",
|
|
264
|
+
mountKeyedList: "__tachyonMountKeyedList",
|
|
265
|
+
nodeAt: "__tachyonNodeAt",
|
|
266
|
+
read: "__tachyonRead",
|
|
267
|
+
setAttributeValue: "__tachyonSetAttributeValue",
|
|
268
|
+
setClassPresence: "__tachyonSetClassPresence",
|
|
269
|
+
setControlValue: "__tachyonSetControlValue",
|
|
270
|
+
setRef: "__tachyonSetRef",
|
|
271
|
+
setStyleValue: "__tachyonSetStyleValue",
|
|
272
|
+
setText: "__tachyonSetText",
|
|
273
|
+
textAt: "__tachyonTextAt",
|
|
274
|
+
};
|
|
275
|
+
const elementExpression = (path) => path.length === 0 ? "root" : `${runtimeNames.elementAt}(root, ${JSON.stringify(path)})`;
|
|
276
|
+
const nodeExpression = (path) => path.length === 0 ? "root" : `${runtimeNames.nodeAt}(root, ${JSON.stringify(path)})`;
|
|
277
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
278
|
+
const simpleItemKeyExpression = (expression, itemName) => {
|
|
279
|
+
const itemPattern = new RegExp(`^${escapeRegExp(itemName)}(?:\\.[A-Za-z_$][\\w$]*)*$`);
|
|
280
|
+
return itemPattern.test(expression) ? expressionToScopeAccess(expression, new Set([itemName])) : undefined;
|
|
281
|
+
};
|
|
282
|
+
const runtimeValueExpression = (expression, reactive, sourceName) => {
|
|
283
|
+
const value = expressionToScopeAccess(expression, new Set(), sourceName);
|
|
284
|
+
return reactive ? `${runtimeNames.read}(${value})` : value;
|
|
285
|
+
};
|
|
286
|
+
const clientModuleCache = new WeakMap();
|
|
287
|
+
const clientModuleCacheKey = (options) => `${options.reactive === true ? "1" : "0"}\0${options.defaultScopeName ?? ""}`;
|
|
288
|
+
export const generateClientModule = (template, options = {}) => {
|
|
289
|
+
const cacheKey = clientModuleCacheKey(options);
|
|
290
|
+
const cachedByOptions = clientModuleCache.get(template);
|
|
291
|
+
const cached = cachedByOptions?.get(cacheKey);
|
|
292
|
+
if (cached) {
|
|
293
|
+
return cached;
|
|
294
|
+
}
|
|
295
|
+
const bindings = template.client.bindings;
|
|
296
|
+
const reactive = options.reactive === true;
|
|
297
|
+
const needsStore = template.client.stores.length > 0;
|
|
298
|
+
const hasDefaultScope = typeof options.defaultScopeName === "string" && options.defaultScopeName.length > 0;
|
|
299
|
+
const sourceName = scopeName(needsStore);
|
|
300
|
+
const needsText = bindings.some((binding) => binding.kind === "text");
|
|
301
|
+
const needsClass = bindings.some((binding) => binding.kind === "class");
|
|
302
|
+
const needsAttr = bindings.some((binding) => binding.kind === "attr" || binding.kind === "style" || binding.kind === "ref");
|
|
303
|
+
const needsModel = bindings.some((binding) => binding.kind === "model");
|
|
304
|
+
const needsEvent = bindings.some((binding) => binding.kind === "event");
|
|
305
|
+
const needsList = bindings.some((binding) => binding.kind === "list");
|
|
306
|
+
const needsConditional = bindings.some((binding) => binding.kind === "if");
|
|
307
|
+
const needsSignal = reactive && bindings.some((binding) => binding.kind !== "event");
|
|
308
|
+
const needsElementAt = needsClass || needsAttr || needsModel || (reactive && needsList);
|
|
309
|
+
const needsNodeAt = reactive && needsConditional;
|
|
310
|
+
const lines = [];
|
|
311
|
+
if (needsText) {
|
|
312
|
+
lines.push(`import { setText as ${runtimeNames.setText}, textAt as ${runtimeNames.textAt} } from "tachyon-dom/runtime/text";`);
|
|
313
|
+
}
|
|
314
|
+
if (needsElementAt) {
|
|
315
|
+
lines.push(needsClass
|
|
316
|
+
? `import { elementAt as ${runtimeNames.elementAt}, setClassPresence as ${runtimeNames.setClassPresence} } from "tachyon-dom/runtime/class";`
|
|
317
|
+
: `import { elementAt as ${runtimeNames.elementAt} } from "tachyon-dom/runtime/class";`);
|
|
318
|
+
}
|
|
319
|
+
if (needsAttr) {
|
|
320
|
+
lines.push(`import { setAttributeValue as ${runtimeNames.setAttributeValue}, setRef as ${runtimeNames.setRef}, setStyleValue as ${runtimeNames.setStyleValue} } from "tachyon-dom/runtime/attr";`);
|
|
321
|
+
}
|
|
322
|
+
if (needsModel) {
|
|
323
|
+
lines.push(`import { bindControl as ${runtimeNames.bindControl}, setControlValue as ${runtimeNames.setControlValue} } from "tachyon-dom/runtime/form";`);
|
|
324
|
+
}
|
|
325
|
+
if (needsEvent) {
|
|
326
|
+
lines.push(`import { delegate as ${runtimeNames.delegate} } from "tachyon-dom/runtime/event";`);
|
|
327
|
+
}
|
|
328
|
+
if (needsList) {
|
|
329
|
+
lines.push(`import { mountKeyedList as ${runtimeNames.mountKeyedList} } from "tachyon-dom/runtime/list";`);
|
|
330
|
+
}
|
|
331
|
+
if (needsConditional) {
|
|
332
|
+
lines.push(needsNodeAt
|
|
333
|
+
? `import { mountConditional as ${runtimeNames.mountConditional}, nodeAt as ${runtimeNames.nodeAt} } from "tachyon-dom/runtime/conditional";`
|
|
334
|
+
: `import { mountConditional as ${runtimeNames.mountConditional} } from "tachyon-dom/runtime/conditional";`);
|
|
335
|
+
}
|
|
336
|
+
if (needsSignal) {
|
|
337
|
+
lines.push(`import { effect as ${runtimeNames.effect}, read as ${runtimeNames.read} } from "tachyon-dom/runtime/signal";`);
|
|
338
|
+
}
|
|
339
|
+
if (needsStore) {
|
|
340
|
+
lines.push(`import { createStore as ${runtimeNames.createStore} } from "tachyon-dom/runtime/store";`);
|
|
341
|
+
}
|
|
342
|
+
lines.push(`export const templateHtml = ${JSON.stringify(template.client.templateHtml)};`);
|
|
343
|
+
lines.push(`export const hydrationBoundaries = ${JSON.stringify(template.client.hydrationBoundaries)};`);
|
|
344
|
+
lines.push(`export const componentBoundaries = ${JSON.stringify(template.client.components)};`);
|
|
345
|
+
if (hasDefaultScope) {
|
|
346
|
+
lines.push(`const __tachyonCreateScope = (inputScope = {}) => {`);
|
|
347
|
+
lines.push(` const localScope = typeof ${options.defaultScopeName} === "function" ? ${options.defaultScopeName}(inputScope) : ${options.defaultScopeName};`);
|
|
348
|
+
lines.push(` return localScope && typeof localScope === "object" ? { ...localScope, ...inputScope } : inputScope;`);
|
|
349
|
+
lines.push(`};`);
|
|
350
|
+
}
|
|
351
|
+
lines.push(hasDefaultScope ? `export const bind = (root, inputScope = {}) => {` : `export const bind = (root, scope) => {`);
|
|
352
|
+
if (hasDefaultScope) {
|
|
353
|
+
lines.push(` const scope = __tachyonCreateScope(inputScope);`);
|
|
354
|
+
}
|
|
355
|
+
if (needsStore) {
|
|
356
|
+
const fields = template.client.stores
|
|
357
|
+
.map((store) => `${store.name}: ${expressionToScopeAccess(store.initial)}`)
|
|
358
|
+
.join(", ");
|
|
359
|
+
lines.push(` const state = ${runtimeNames.createStore}({ ...scope, ${fields} });`);
|
|
360
|
+
}
|
|
361
|
+
if (reactive || needsEvent || needsModel) {
|
|
362
|
+
lines.push(` const cleanups = [];`);
|
|
363
|
+
}
|
|
364
|
+
let listIndex = 0;
|
|
365
|
+
let conditionalIndex = 0;
|
|
366
|
+
let targetIndex = 0;
|
|
367
|
+
for (const binding of bindings) {
|
|
368
|
+
if (binding.kind === "text") {
|
|
369
|
+
const target = `${runtimeNames.textAt}(root, ${JSON.stringify(binding.path)})`;
|
|
370
|
+
if (reactive) {
|
|
371
|
+
const targetName = `__tachyonTarget${targetIndex++}`;
|
|
372
|
+
lines.push(` const ${targetName} = ${target};`);
|
|
373
|
+
lines.push(` cleanups.push(${runtimeNames.effect}(() => ${runtimeNames.setText}(${targetName}, ${runtimeValueExpression(binding.expression, reactive, sourceName)})));`);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
lines.push(` ${runtimeNames.setText}(${target}, ${runtimeValueExpression(binding.expression, reactive, sourceName)});`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else if (binding.kind === "class") {
|
|
380
|
+
const target = elementExpression(binding.path);
|
|
381
|
+
if (reactive) {
|
|
382
|
+
const targetName = `__tachyonTarget${targetIndex++}`;
|
|
383
|
+
lines.push(` const ${targetName} = ${target};`);
|
|
384
|
+
lines.push(` cleanups.push(${runtimeNames.effect}(() => ${runtimeNames.setClassPresence}(${targetName}, ${JSON.stringify(binding.className)}, ${runtimeValueExpression(binding.expression, reactive, sourceName)})));`);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
lines.push(` ${runtimeNames.setClassPresence}(${target}, ${JSON.stringify(binding.className)}, ${runtimeValueExpression(binding.expression, reactive, sourceName)});`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
else if (binding.kind === "event") {
|
|
391
|
+
const statement = `${runtimeNames.delegate}(root, ${JSON.stringify(binding.eventName)}, ${JSON.stringify(binding.path)}, ${expressionToScopeAccess(binding.handler, new Set(), scopeName(needsStore))})`;
|
|
392
|
+
lines.push(` cleanups.push(${statement});`);
|
|
393
|
+
}
|
|
394
|
+
else if (binding.kind === "attr") {
|
|
395
|
+
const target = elementExpression(binding.path);
|
|
396
|
+
if (reactive) {
|
|
397
|
+
const targetName = `__tachyonTarget${targetIndex++}`;
|
|
398
|
+
lines.push(` const ${targetName} = ${target};`);
|
|
399
|
+
lines.push(` cleanups.push(${runtimeNames.effect}(() => ${runtimeNames.setAttributeValue}(${targetName}, ${JSON.stringify(binding.name)}, ${runtimeValueExpression(binding.expression, reactive, sourceName)})));`);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
lines.push(` ${runtimeNames.setAttributeValue}(${target}, ${JSON.stringify(binding.name)}, ${runtimeValueExpression(binding.expression, reactive, sourceName)});`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else if (binding.kind === "style") {
|
|
406
|
+
const target = elementExpression(binding.path);
|
|
407
|
+
if (reactive) {
|
|
408
|
+
const targetName = `__tachyonTarget${targetIndex++}`;
|
|
409
|
+
lines.push(` const ${targetName} = ${target};`);
|
|
410
|
+
lines.push(` cleanups.push(${runtimeNames.effect}(() => ${runtimeNames.setStyleValue}(${targetName}, ${JSON.stringify(binding.name)}, ${runtimeValueExpression(binding.expression, reactive, sourceName)})));`);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
lines.push(` ${runtimeNames.setStyleValue}(${target}, ${JSON.stringify(binding.name)}, ${runtimeValueExpression(binding.expression, reactive, sourceName)});`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
else if (binding.kind === "ref") {
|
|
417
|
+
lines.push(` ${runtimeNames.setRef}(${sourceName}, ${JSON.stringify(binding.expression)}, ${elementExpression(binding.path)});`);
|
|
418
|
+
}
|
|
419
|
+
else if (binding.kind === "model") {
|
|
420
|
+
const target = elementExpression(binding.path);
|
|
421
|
+
const value = runtimeValueExpression(binding.expression, false, sourceName);
|
|
422
|
+
if (reactive) {
|
|
423
|
+
const targetName = `__tachyonTarget${targetIndex++}`;
|
|
424
|
+
lines.push(` const ${targetName} = ${target};`);
|
|
425
|
+
lines.push(` cleanups.push(${runtimeNames.bindControl}(${targetName}, ${JSON.stringify(binding.property)}, () => ${value}, (value) => { ${expressionToScopeAccess(binding.expression, new Set(), sourceName)} = value; }));`);
|
|
426
|
+
lines.push(` cleanups.push(${runtimeNames.effect}(() => ${runtimeNames.setControlValue}(${targetName}, ${JSON.stringify(binding.property)}, ${runtimeValueExpression(binding.expression, true, sourceName)})));`);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
lines.push(` cleanups.push(${runtimeNames.bindControl}(${target}, ${JSON.stringify(binding.property)}, () => ${value}, (value) => { ${expressionToScopeAccess(binding.expression, new Set(), sourceName)} = value; }));`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
else if (binding.kind === "list") {
|
|
433
|
+
const targetName = reactive ? `__tachyonTarget${targetIndex++}` : undefined;
|
|
434
|
+
lines.push(emitListBinding(binding, reactive, sourceName, listIndex++, targetName));
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const targetName = reactive ? `__tachyonTarget${targetIndex++}` : undefined;
|
|
438
|
+
lines.push(emitConditionalBinding(binding, reactive, sourceName, conditionalIndex++, targetName));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (reactive || needsEvent || needsModel) {
|
|
442
|
+
lines.push(` return () => {`);
|
|
443
|
+
lines.push(` for (const cleanup of cleanups) cleanup();`);
|
|
444
|
+
lines.push(` };`);
|
|
445
|
+
}
|
|
446
|
+
lines.push(`};`);
|
|
447
|
+
const code = `${lines.join("\n")}\n`;
|
|
448
|
+
const nextCache = cachedByOptions ?? new Map();
|
|
449
|
+
nextCache.set(cacheKey, code);
|
|
450
|
+
clientModuleCache.set(template, nextCache);
|
|
451
|
+
return code;
|
|
452
|
+
};
|
|
453
|
+
const listSignature = (binding) => `list:${JSON.stringify({
|
|
454
|
+
path: binding.path,
|
|
455
|
+
each: binding.each,
|
|
456
|
+
key: binding.key,
|
|
457
|
+
itemName: binding.itemName,
|
|
458
|
+
templateHtml: binding.templateHtml,
|
|
459
|
+
bindings: binding.bindings.map((child) => {
|
|
460
|
+
if (child.kind === "list" || child.kind === "if") {
|
|
461
|
+
return { kind: child.kind };
|
|
462
|
+
}
|
|
463
|
+
return child;
|
|
464
|
+
}),
|
|
465
|
+
})}`;
|
|
466
|
+
const conditionalSignature = (binding) => `if:${JSON.stringify({
|
|
467
|
+
path: binding.path,
|
|
468
|
+
test: binding.test,
|
|
469
|
+
templateHtml: binding.templateHtml,
|
|
470
|
+
bindings: binding.bindings,
|
|
471
|
+
})}`;
|
|
472
|
+
const bindingReadExpression = (expression) => expressionToScopeAccess(expression, new Set(), "scope");
|
|
473
|
+
const serializeListRowBinding = (binding) => {
|
|
474
|
+
const fields = [`kind: ${JSON.stringify(binding.kind)}`, `path: ${JSON.stringify(binding.path)}`];
|
|
475
|
+
if (binding.kind === "text") {
|
|
476
|
+
fields.push(`expression: ${JSON.stringify(binding.expression)}`);
|
|
477
|
+
fields.push(`read: (scope) => ${bindingReadExpression(binding.expression)}`);
|
|
478
|
+
}
|
|
479
|
+
else if (binding.kind === "class") {
|
|
480
|
+
fields.push(`className: ${JSON.stringify(binding.className)}`);
|
|
481
|
+
fields.push(`expression: ${JSON.stringify(binding.expression)}`);
|
|
482
|
+
fields.push(`read: (scope) => ${bindingReadExpression(binding.expression)}`);
|
|
483
|
+
}
|
|
484
|
+
else if (binding.kind === "event") {
|
|
485
|
+
fields.push(`eventName: ${JSON.stringify(binding.eventName)}`);
|
|
486
|
+
fields.push(`handler: ${JSON.stringify(binding.handler)}`);
|
|
487
|
+
fields.push(`read: (scope) => ${bindingReadExpression(binding.handler)}`);
|
|
488
|
+
}
|
|
489
|
+
else if (binding.kind === "attr") {
|
|
490
|
+
fields.push(`name: ${JSON.stringify(binding.name)}`);
|
|
491
|
+
fields.push(`expression: ${JSON.stringify(binding.expression)}`);
|
|
492
|
+
fields.push(`read: (scope) => ${bindingReadExpression(binding.expression)}`);
|
|
493
|
+
}
|
|
494
|
+
else if (binding.kind === "style") {
|
|
495
|
+
fields.push(`name: ${JSON.stringify(binding.name)}`);
|
|
496
|
+
fields.push(`expression: ${JSON.stringify(binding.expression)}`);
|
|
497
|
+
fields.push(`read: (scope) => ${bindingReadExpression(binding.expression)}`);
|
|
498
|
+
}
|
|
499
|
+
else if (binding.kind === "ref") {
|
|
500
|
+
fields.push(`expression: ${JSON.stringify(binding.expression)}`);
|
|
501
|
+
}
|
|
502
|
+
else if (binding.kind === "model") {
|
|
503
|
+
fields.push(`property: ${JSON.stringify(binding.property)}`);
|
|
504
|
+
fields.push(`expression: ${JSON.stringify(binding.expression)}`);
|
|
505
|
+
fields.push(`read: (scope) => ${bindingReadExpression(binding.expression)}`);
|
|
506
|
+
fields.push(`write: (scope, value) => { ${bindingReadExpression(binding.expression)} = value; }`);
|
|
507
|
+
}
|
|
508
|
+
else if (binding.kind === "list") {
|
|
509
|
+
const itemKeyExpression = simpleItemKeyExpression(binding.key, binding.itemName);
|
|
510
|
+
fields.push(`signature: ${JSON.stringify(listSignature(binding))}`);
|
|
511
|
+
fields.push(`each: ${JSON.stringify(binding.each)}`);
|
|
512
|
+
fields.push(`read: (scope) => ${bindingReadExpression(binding.each)}`);
|
|
513
|
+
fields.push(`itemName: ${JSON.stringify(binding.itemName)}`);
|
|
514
|
+
fields.push(`key: ${JSON.stringify(binding.key)}`);
|
|
515
|
+
fields.push(itemKeyExpression
|
|
516
|
+
? `keyReadItem: (${binding.itemName}) => ${itemKeyExpression}`
|
|
517
|
+
: `keyRead: (scope) => ${bindingReadExpression(binding.key)}`);
|
|
518
|
+
fields.push(`templateHtml: ${JSON.stringify(binding.templateHtml)}`);
|
|
519
|
+
fields.push(`bindings: [${binding.bindings.map(serializeListRowBinding).join(", ")}]`);
|
|
520
|
+
}
|
|
521
|
+
else if (binding.kind === "if") {
|
|
522
|
+
fields.push(`signature: ${JSON.stringify(conditionalSignature(binding))}`);
|
|
523
|
+
fields.push(`test: ${JSON.stringify(binding.test)}`);
|
|
524
|
+
fields.push(`read: (scope) => ${bindingReadExpression(binding.test)}`);
|
|
525
|
+
fields.push(`templateHtml: ${JSON.stringify(binding.templateHtml)}`);
|
|
526
|
+
fields.push(`bindings: [${binding.bindings.map(serializeListRowBinding).join(", ")}]`);
|
|
527
|
+
}
|
|
528
|
+
return `{ ${fields.join(", ")} }`;
|
|
529
|
+
};
|
|
530
|
+
const emitListBinding = (binding, reactive, sourceName, index, targetName) => {
|
|
531
|
+
const optionsName = `listOptions${index}`;
|
|
532
|
+
const itemKeyExpression = simpleItemKeyExpression(binding.key, binding.itemName);
|
|
533
|
+
const listOptions = [
|
|
534
|
+
` const ${optionsName} = {`,
|
|
535
|
+
` signature: ${JSON.stringify(listSignature(binding))},`,
|
|
536
|
+
` key: ${JSON.stringify(binding.key)},`,
|
|
537
|
+
itemKeyExpression
|
|
538
|
+
? ` keyReadItem: (${binding.itemName}) => ${itemKeyExpression},`
|
|
539
|
+
: ` keyRead: (scope) => ${bindingReadExpression(binding.key)},`,
|
|
540
|
+
` itemName: ${JSON.stringify(binding.itemName)},`,
|
|
541
|
+
` scope: ${sourceName},`,
|
|
542
|
+
` templateHtml: ${JSON.stringify(binding.templateHtml)},`,
|
|
543
|
+
` bindings: [${binding.bindings.map(serializeListRowBinding).join(", ")}],`,
|
|
544
|
+
` };`,
|
|
545
|
+
].join("\n");
|
|
546
|
+
const target = targetName ?? "root";
|
|
547
|
+
const path = targetName ? [] : binding.path;
|
|
548
|
+
const statement = `${runtimeNames.mountKeyedList}(${target}, ${JSON.stringify(path)}, ${runtimeValueExpression(binding.each, reactive, sourceName)}, ${optionsName})`;
|
|
549
|
+
return reactive
|
|
550
|
+
? ` const ${targetName} = ${elementExpression(binding.path)};\n${listOptions}\n cleanups.push(${runtimeNames.effect}(() => ${statement}));`
|
|
551
|
+
: `${listOptions}\n ${statement};`;
|
|
552
|
+
};
|
|
553
|
+
const emitConditionalBinding = (binding, reactive, sourceName, index, targetName) => {
|
|
554
|
+
const optionsName = `conditionalOptions${index}`;
|
|
555
|
+
const conditionalOptions = [
|
|
556
|
+
` const ${optionsName} = {`,
|
|
557
|
+
` signature: ${JSON.stringify(conditionalSignature(binding))},`,
|
|
558
|
+
` templateHtml: ${JSON.stringify(binding.templateHtml)},`,
|
|
559
|
+
` bindings: [${binding.bindings.map(serializeListRowBinding).join(", ")}],`,
|
|
560
|
+
` };`,
|
|
561
|
+
].join("\n");
|
|
562
|
+
const target = targetName ?? "root";
|
|
563
|
+
const path = targetName ? [] : binding.path;
|
|
564
|
+
const statement = `${runtimeNames.mountConditional}(${target}, ${JSON.stringify(path)}, ${runtimeValueExpression(binding.test, reactive, sourceName)}, ${sourceName}, ${optionsName})`;
|
|
565
|
+
return reactive
|
|
566
|
+
? ` const ${targetName} = ${nodeExpression(binding.path)};\n${conditionalOptions}\n cleanups.push(${runtimeNames.effect}(() => ${statement}));`
|
|
567
|
+
: `${conditionalOptions}\n ${statement};`;
|
|
568
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CompiledTemplate, ElementNode, TemplateNode } from "../types.js";
|
|
2
|
+
export declare const renderOpenTagExpression: (node: ElementNode, locals: ReadonlySet<string>) => string;
|
|
3
|
+
export declare const renderNodeExpression: (node: TemplateNode, locals?: ReadonlySet<string>, path?: number[]) => string;
|
|
4
|
+
export declare const generateServerModule: (template: CompiledTemplate) => string;
|
|
5
|
+
export type ServerRenderer = (scope: Record<string, unknown>) => string;
|
|
6
|
+
export declare const compileServerTemplate: (template: CompiledTemplate) => ServerRenderer;
|
|
7
|
+
export declare const renderServerTemplate: (template: CompiledTemplate, scope: Record<string, unknown>) => string;
|