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,8 @@
|
|
|
1
|
+
import { type Result } from "../result.js";
|
|
2
|
+
import type { CompiledTemplate, CompilerError } from "./types.js";
|
|
3
|
+
import type { CompileTemplateOptions } from "./types.js";
|
|
4
|
+
export declare const compileTemplate: (source: string, options?: CompileTemplateOptions) => Result<CompiledTemplate, CompilerError>;
|
|
5
|
+
export * from "./types.js";
|
|
6
|
+
export { generateClientModule } from "./targets/client.js";
|
|
7
|
+
export { compileServerTemplate, generateServerModule, renderServerTemplate } from "./targets/server.js";
|
|
8
|
+
export { generateServerStreamModule } from "./targets/stream.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { err, ok } from "../result.js";
|
|
2
|
+
import { createTemplateIr } from "./ir.js";
|
|
3
|
+
import { parseTemplate } from "./parser.js";
|
|
4
|
+
import { lowerClientTemplate } from "./targets/client.js";
|
|
5
|
+
import { applyTemplateWhitespace } from "./whitespace.js";
|
|
6
|
+
const compileCacheLimit = 128;
|
|
7
|
+
const compileCache = new Map();
|
|
8
|
+
const rememberCompiledTemplate = (cacheKey, result) => {
|
|
9
|
+
if (compileCache.has(cacheKey)) {
|
|
10
|
+
compileCache.delete(cacheKey);
|
|
11
|
+
}
|
|
12
|
+
compileCache.set(cacheKey, result);
|
|
13
|
+
while (compileCache.size > compileCacheLimit) {
|
|
14
|
+
const oldest = compileCache.keys().next().value;
|
|
15
|
+
if (oldest === undefined) {
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
compileCache.delete(oldest);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
export const compileTemplate = (source, options = {}) => {
|
|
23
|
+
const whitespace = options.whitespace ?? "preserve";
|
|
24
|
+
const cacheKey = `${whitespace}\0${source}`;
|
|
25
|
+
const cached = compileCache.get(cacheKey);
|
|
26
|
+
if (cached) {
|
|
27
|
+
compileCache.delete(cacheKey);
|
|
28
|
+
compileCache.set(cacheKey, cached);
|
|
29
|
+
return cached;
|
|
30
|
+
}
|
|
31
|
+
const rootResult = parseTemplate(source);
|
|
32
|
+
if (!rootResult.ok) {
|
|
33
|
+
return rememberCompiledTemplate(cacheKey, err(rootResult.error));
|
|
34
|
+
}
|
|
35
|
+
const root = applyTemplateWhitespace(rootResult.value, whitespace);
|
|
36
|
+
const irResult = createTemplateIr(root);
|
|
37
|
+
if (!irResult.ok) {
|
|
38
|
+
return rememberCompiledTemplate(cacheKey, err(irResult.error));
|
|
39
|
+
}
|
|
40
|
+
return rememberCompiledTemplate(cacheKey, ok({
|
|
41
|
+
source,
|
|
42
|
+
ir: irResult.value,
|
|
43
|
+
root: irResult.value.root,
|
|
44
|
+
client: lowerClientTemplate(irResult.value.root),
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
export * from "./types.js";
|
|
48
|
+
export { generateClientModule } from "./targets/client.js";
|
|
49
|
+
export { compileServerTemplate, generateServerModule, renderServerTemplate } from "./targets/server.js";
|
|
50
|
+
export { generateServerStreamModule } from "./targets/stream.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Result } from "../result.js";
|
|
2
|
+
import type { CompilerError, ElementNode, StoreDefinition, TemplateIr } from "./types.js";
|
|
3
|
+
export declare const storeDefinitionsFor: (node: ElementNode) => StoreDefinition[];
|
|
4
|
+
export declare const createTemplateIr: (root: ElementNode) => Result<TemplateIr, CompilerError>;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { err, ok } from "../result.js";
|
|
2
|
+
import { isAssignableExpression, parseExpression } from "./expression.js";
|
|
3
|
+
import { attrExpression, attrString, hydrationBoundaryFor, identifierNamePattern, identifierPattern, isKnownHydrationAttribute, itemNameFromKey, readExpressionAttribute, renderableChildren, textExpressionSegments, } from "./utils.js";
|
|
4
|
+
const semanticError = (message, span) => err({
|
|
5
|
+
message,
|
|
6
|
+
offset: span?.start ?? 0,
|
|
7
|
+
...(span?.end === undefined ? {} : { endOffset: span.end }),
|
|
8
|
+
});
|
|
9
|
+
const openingTagSpan = (node) => ({ start: node.start, end: node.openEnd });
|
|
10
|
+
const attributeSpan = (attribute) => ({ start: attribute.start, end: attribute.end });
|
|
11
|
+
export const storeDefinitionsFor = (node) => {
|
|
12
|
+
const stores = [];
|
|
13
|
+
for (const attr of node.attrs) {
|
|
14
|
+
if (!identifierPattern.test(attr.name)) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const initial = readExpressionAttribute(attr.value);
|
|
18
|
+
if (initial) {
|
|
19
|
+
stores.push({ name: attr.name, initial });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return stores;
|
|
23
|
+
};
|
|
24
|
+
const componentName = (node) => attrString(node, "name");
|
|
25
|
+
const componentProps = (node) => node.attrs
|
|
26
|
+
.filter((attr) => attr.name !== "name")
|
|
27
|
+
.flatMap((attr) => {
|
|
28
|
+
const expression = readExpressionAttribute(attr.value);
|
|
29
|
+
return expression ? [{ name: attr.name, expression }] : [];
|
|
30
|
+
});
|
|
31
|
+
const componentStores = (node) => {
|
|
32
|
+
const stores = [];
|
|
33
|
+
const visit = (child) => {
|
|
34
|
+
if (child.type !== "element" || child.tagName === "component") {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (child.tagName === "store") {
|
|
38
|
+
stores.push(...storeDefinitionsFor(child));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
child.children.forEach(visit);
|
|
42
|
+
};
|
|
43
|
+
node.children.forEach(visit);
|
|
44
|
+
return stores;
|
|
45
|
+
};
|
|
46
|
+
const validateExpression = (expression, context, span) => {
|
|
47
|
+
const parsed = parseExpression(expression);
|
|
48
|
+
if (!parsed.ok) {
|
|
49
|
+
return semanticError(`Invalid ${context} expression: ${expression}.`, span);
|
|
50
|
+
}
|
|
51
|
+
return ok(undefined);
|
|
52
|
+
};
|
|
53
|
+
const validateTextExpressions = (node) => {
|
|
54
|
+
if (node.type === "text") {
|
|
55
|
+
for (const segment of textExpressionSegments(node.value)) {
|
|
56
|
+
if (segment.kind === "expression") {
|
|
57
|
+
const result = validateExpression(segment.value, "text", {
|
|
58
|
+
start: (node.start ?? 0) + segment.start,
|
|
59
|
+
end: (node.start ?? 0) + segment.end,
|
|
60
|
+
});
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return ok(undefined);
|
|
67
|
+
}
|
|
68
|
+
for (const attr of node.attrs) {
|
|
69
|
+
if (attr.name === "class" || attr.name === "name") {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const expression = readExpressionAttribute(attr.value);
|
|
73
|
+
if (expression) {
|
|
74
|
+
const result = validateExpression(expression, `${attr.name} attribute`, attributeSpan(attr));
|
|
75
|
+
if (!result.ok) {
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const child of node.children) {
|
|
81
|
+
const result = validateTextExpressions(child);
|
|
82
|
+
if (!result.ok) {
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return ok(undefined);
|
|
87
|
+
};
|
|
88
|
+
const validateSpecialNode = (node) => {
|
|
89
|
+
if (node.tagName === "for") {
|
|
90
|
+
const each = attrExpression(node, "each");
|
|
91
|
+
const key = attrExpression(node, "key");
|
|
92
|
+
if (!each) {
|
|
93
|
+
return semanticError("<for> requires each={items}.", openingTagSpan(node));
|
|
94
|
+
}
|
|
95
|
+
if (!key) {
|
|
96
|
+
return semanticError("<for> requires key={item.id}.", openingTagSpan(node));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (node.tagName === "if" && !attrExpression(node, "test")) {
|
|
100
|
+
return semanticError("<if> requires test={condition}.", openingTagSpan(node));
|
|
101
|
+
}
|
|
102
|
+
if (node.tagName === "component") {
|
|
103
|
+
if (!componentName(node)) {
|
|
104
|
+
return semanticError("<component> requires a string name attribute.", openingTagSpan(node));
|
|
105
|
+
}
|
|
106
|
+
for (const attr of node.attrs) {
|
|
107
|
+
if (attr.name !== "name" && readExpressionAttribute(attr.value) && !identifierNamePattern.test(attr.name)) {
|
|
108
|
+
return semanticError(`Invalid component prop binding name: ${attr.name}.`, attributeSpan(attr));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (renderableChildren(node).length !== 1) {
|
|
112
|
+
return semanticError("<component> requires exactly one renderable root child.", openingTagSpan(node));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (node.tagName === "store") {
|
|
116
|
+
for (const attr of node.attrs) {
|
|
117
|
+
if (readExpressionAttribute(attr.value) && !identifierNamePattern.test(attr.name)) {
|
|
118
|
+
return semanticError(`Invalid store binding name: ${attr.name}.`, attributeSpan(attr));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (node.tagName === "await") {
|
|
123
|
+
if (!attrExpression(node, "value")) {
|
|
124
|
+
return semanticError("<await> requires value={promise}.", openingTagSpan(node));
|
|
125
|
+
}
|
|
126
|
+
const thenName = attrString(node, "then");
|
|
127
|
+
if (!thenName) {
|
|
128
|
+
return semanticError(`<await> requires then="name".`, openingTagSpan(node));
|
|
129
|
+
}
|
|
130
|
+
if (!identifierNamePattern.test(thenName)) {
|
|
131
|
+
return semanticError(`Invalid await then binding: ${thenName}.`, attributeSpan(node.attrs.find((attr) => attr.name === "then")));
|
|
132
|
+
}
|
|
133
|
+
const reorder = attrString(node, "reorder");
|
|
134
|
+
if (reorder && reorder !== "preserve" && reorder !== "resolve") {
|
|
135
|
+
return semanticError(`<await> reorder must be "preserve" or "resolve".`, attributeSpan(node.attrs.find((attr) => attr.name === "reorder")));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const attr of node.attrs) {
|
|
139
|
+
if (attr.name.startsWith("hydrate:") && !isKnownHydrationAttribute(attr.name)) {
|
|
140
|
+
return semanticError(`Unknown hydration attribute: ${attr.name}.`, attributeSpan(attr));
|
|
141
|
+
}
|
|
142
|
+
if (attr.name.startsWith("bind:")) {
|
|
143
|
+
const expression = readExpressionAttribute(attr.value);
|
|
144
|
+
if (!expression || !isAssignableExpression(expression)) {
|
|
145
|
+
return semanticError(`${attr.name} requires an assignable expression.`, attributeSpan(attr));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return ok(undefined);
|
|
150
|
+
};
|
|
151
|
+
const validateTree = (node, hydrateIds) => {
|
|
152
|
+
const expressionResult = validateTextExpressions(node);
|
|
153
|
+
if (!expressionResult.ok) {
|
|
154
|
+
return expressionResult;
|
|
155
|
+
}
|
|
156
|
+
if (node.type === "text") {
|
|
157
|
+
return ok(undefined);
|
|
158
|
+
}
|
|
159
|
+
const specialResult = validateSpecialNode(node);
|
|
160
|
+
if (!specialResult.ok) {
|
|
161
|
+
return specialResult;
|
|
162
|
+
}
|
|
163
|
+
const hydrateBoundary = hydrationBoundaryFor(node, []);
|
|
164
|
+
if (hydrateBoundary && hydrateBoundary.idKind !== "static") {
|
|
165
|
+
if (hydrateIds.has(hydrateBoundary.id)) {
|
|
166
|
+
return semanticError(`Duplicate hydrate boundary id expression: ${hydrateBoundary.id}.`, openingTagSpan(node));
|
|
167
|
+
}
|
|
168
|
+
hydrateIds.add(hydrateBoundary.id);
|
|
169
|
+
}
|
|
170
|
+
for (const child of node.children) {
|
|
171
|
+
const result = validateTree(child, hydrateIds);
|
|
172
|
+
if (!result.ok) {
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return ok(undefined);
|
|
177
|
+
};
|
|
178
|
+
const collectDirectives = (node, path, directives) => {
|
|
179
|
+
if (node.type === "text") {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (node.tagName === "store") {
|
|
183
|
+
directives.push({ kind: "store", path: [...path], stores: storeDefinitionsFor(node) });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const isComponent = node.tagName === "component";
|
|
187
|
+
if (isComponent) {
|
|
188
|
+
directives.push({
|
|
189
|
+
kind: "component",
|
|
190
|
+
path: [...path],
|
|
191
|
+
name: componentName(node) ?? "Anonymous",
|
|
192
|
+
props: componentProps(node),
|
|
193
|
+
stores: componentStores(node),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const hydrateBoundary = hydrationBoundaryFor(node, path);
|
|
197
|
+
if (hydrateBoundary) {
|
|
198
|
+
directives.push({ kind: "hydrate", ...hydrateBoundary });
|
|
199
|
+
}
|
|
200
|
+
if (node.tagName === "if") {
|
|
201
|
+
directives.push({ kind: "if", path: [...path], test: attrExpression(node, "test") ?? "false" });
|
|
202
|
+
}
|
|
203
|
+
if (node.tagName === "for") {
|
|
204
|
+
const key = attrExpression(node, "key") ?? "item";
|
|
205
|
+
directives.push({
|
|
206
|
+
kind: "for",
|
|
207
|
+
path: [...path],
|
|
208
|
+
each: attrExpression(node, "each") ?? "[]",
|
|
209
|
+
key,
|
|
210
|
+
itemName: itemNameFromKey(key),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (node.tagName === "await") {
|
|
214
|
+
const fallback = attrString(node, "fallback");
|
|
215
|
+
const errorText = attrString(node, "error");
|
|
216
|
+
const reorder = attrString(node, "reorder");
|
|
217
|
+
directives.push({
|
|
218
|
+
kind: "await",
|
|
219
|
+
path: [...path],
|
|
220
|
+
value: attrExpression(node, "value") ?? "undefined",
|
|
221
|
+
thenName: attrString(node, "then") ?? "value",
|
|
222
|
+
...(fallback ? { fallback } : {}),
|
|
223
|
+
...(errorText ? { error: errorText } : {}),
|
|
224
|
+
...(reorder === "preserve" || reorder === "resolve" ? { reorder } : {}),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
for (const attr of node.attrs) {
|
|
228
|
+
if (!attr.name.startsWith("on:")) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const handler = readExpressionAttribute(attr.value);
|
|
232
|
+
if (handler) {
|
|
233
|
+
directives.push({ kind: "event", path: [...path], eventName: attr.name.slice(3), handler });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const children = isComponent ? renderableChildren(node) : node.children;
|
|
237
|
+
children.forEach((child, index) => {
|
|
238
|
+
const childPath = isComponent && children.length === 1 ? path : [...path, index];
|
|
239
|
+
collectDirectives(child, childPath, directives);
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
export const createTemplateIr = (root) => {
|
|
243
|
+
const validationResult = validateTree(root, new Set());
|
|
244
|
+
if (!validationResult.ok) {
|
|
245
|
+
return err(validationResult.error);
|
|
246
|
+
}
|
|
247
|
+
const directives = [];
|
|
248
|
+
if (root.tagName === "component" || root.tagName === "for" || root.tagName === "if" || root.tagName === "await") {
|
|
249
|
+
collectDirectives(root, [], directives);
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
root.children.forEach((child, index) => collectDirectives(child, [index], directives));
|
|
253
|
+
}
|
|
254
|
+
return ok({ kind: "template", root, directives });
|
|
255
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { err, ok } from "../result.js";
|
|
2
|
+
import { voidElementNames } from "./utils.js";
|
|
3
|
+
const isWhitespace = (char) => char === " " || char === "\n" || char === "\t" || char === "\r";
|
|
4
|
+
const parserError = (parser, message) => err({ message, offset: parser.offset });
|
|
5
|
+
const peek = (parser) => parser.source[parser.offset];
|
|
6
|
+
const startsWith = (parser, value) => parser.source.startsWith(value, parser.offset);
|
|
7
|
+
const consumeWhitespace = (parser) => {
|
|
8
|
+
while (isWhitespace(peek(parser))) {
|
|
9
|
+
parser.offset++;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const readWhile = (parser, predicate) => {
|
|
13
|
+
const start = parser.offset;
|
|
14
|
+
while (parser.offset < parser.source.length) {
|
|
15
|
+
const char = parser.source[parser.offset];
|
|
16
|
+
if (!predicate(char)) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
parser.offset++;
|
|
20
|
+
}
|
|
21
|
+
return parser.source.slice(start, parser.offset);
|
|
22
|
+
};
|
|
23
|
+
const readName = (parser) => {
|
|
24
|
+
const name = readWhile(parser, (char) => /[A-Za-z0-9:_$.-]/.test(char));
|
|
25
|
+
if (!name) {
|
|
26
|
+
return parserError(parser, "Expected a name.");
|
|
27
|
+
}
|
|
28
|
+
return ok(name);
|
|
29
|
+
};
|
|
30
|
+
const readQuotedValue = (parser) => {
|
|
31
|
+
const quote = peek(parser);
|
|
32
|
+
if (quote !== `"` && quote !== `'`) {
|
|
33
|
+
return parserError(parser, "Expected a quoted attribute value.");
|
|
34
|
+
}
|
|
35
|
+
parser.offset++;
|
|
36
|
+
const start = parser.offset;
|
|
37
|
+
while (parser.offset < parser.source.length && peek(parser) !== quote) {
|
|
38
|
+
parser.offset++;
|
|
39
|
+
}
|
|
40
|
+
if (peek(parser) !== quote) {
|
|
41
|
+
return parserError(parser, "Unclosed attribute value.");
|
|
42
|
+
}
|
|
43
|
+
const value = parser.source.slice(start, parser.offset);
|
|
44
|
+
parser.offset++;
|
|
45
|
+
return ok(value);
|
|
46
|
+
};
|
|
47
|
+
const readBracedValue = (parser) => {
|
|
48
|
+
let depth = 0;
|
|
49
|
+
let quote;
|
|
50
|
+
const start = parser.offset;
|
|
51
|
+
while (parser.offset < parser.source.length) {
|
|
52
|
+
const char = parser.source[parser.offset];
|
|
53
|
+
const previous = parser.source[parser.offset - 1];
|
|
54
|
+
if (quote) {
|
|
55
|
+
if (char === quote && previous !== "\\") {
|
|
56
|
+
quote = undefined;
|
|
57
|
+
}
|
|
58
|
+
parser.offset++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (char === `"` || char === `'`) {
|
|
62
|
+
quote = char;
|
|
63
|
+
parser.offset++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (char === "{") {
|
|
67
|
+
depth++;
|
|
68
|
+
}
|
|
69
|
+
else if (char === "}") {
|
|
70
|
+
depth--;
|
|
71
|
+
parser.offset++;
|
|
72
|
+
if (depth === 0) {
|
|
73
|
+
return ok(parser.source.slice(start, parser.offset));
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
parser.offset++;
|
|
78
|
+
}
|
|
79
|
+
return parserError(parser, "Unclosed braced attribute value.");
|
|
80
|
+
};
|
|
81
|
+
const readAttributeValue = (parser) => {
|
|
82
|
+
if (peek(parser) === `"` || peek(parser) === `'`) {
|
|
83
|
+
return readQuotedValue(parser);
|
|
84
|
+
}
|
|
85
|
+
if (peek(parser) === "{") {
|
|
86
|
+
return readBracedValue(parser);
|
|
87
|
+
}
|
|
88
|
+
return ok(readWhile(parser, (char) => !isWhitespace(char) && char !== ">" && char !== "/"));
|
|
89
|
+
};
|
|
90
|
+
const parseAttributes = (parser) => {
|
|
91
|
+
const attrs = [];
|
|
92
|
+
while (parser.offset < parser.source.length) {
|
|
93
|
+
consumeWhitespace(parser);
|
|
94
|
+
const char = peek(parser);
|
|
95
|
+
if (char === ">" || startsWith(parser, "/>")) {
|
|
96
|
+
return ok(attrs);
|
|
97
|
+
}
|
|
98
|
+
const start = parser.offset;
|
|
99
|
+
const nameStart = parser.offset;
|
|
100
|
+
const nameResult = readName(parser);
|
|
101
|
+
if (!nameResult.ok) {
|
|
102
|
+
return err(nameResult.error);
|
|
103
|
+
}
|
|
104
|
+
const nameEnd = parser.offset;
|
|
105
|
+
consumeWhitespace(parser);
|
|
106
|
+
if (peek(parser) !== "=") {
|
|
107
|
+
attrs.push({ name: nameResult.value, value: true, start, end: nameEnd, nameStart, nameEnd });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
parser.offset++;
|
|
111
|
+
consumeWhitespace(parser);
|
|
112
|
+
const valueStart = parser.offset;
|
|
113
|
+
const valueResult = readAttributeValue(parser);
|
|
114
|
+
if (!valueResult.ok) {
|
|
115
|
+
return err(valueResult.error);
|
|
116
|
+
}
|
|
117
|
+
const valueEnd = parser.offset;
|
|
118
|
+
attrs.push({
|
|
119
|
+
name: nameResult.value,
|
|
120
|
+
value: valueResult.value,
|
|
121
|
+
start,
|
|
122
|
+
end: valueEnd,
|
|
123
|
+
nameStart,
|
|
124
|
+
nameEnd,
|
|
125
|
+
valueStart,
|
|
126
|
+
valueEnd,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return parserError(parser, "Unclosed attribute list.");
|
|
130
|
+
};
|
|
131
|
+
const parseText = (parser) => {
|
|
132
|
+
const start = parser.offset;
|
|
133
|
+
while (parser.offset < parser.source.length && peek(parser) !== "<") {
|
|
134
|
+
parser.offset++;
|
|
135
|
+
}
|
|
136
|
+
return { type: "text", value: parser.source.slice(start, parser.offset), start, end: parser.offset };
|
|
137
|
+
};
|
|
138
|
+
const consumeComment = (parser) => {
|
|
139
|
+
if (!startsWith(parser, "<!--")) {
|
|
140
|
+
return parserError(parser, "Expected an HTML comment.");
|
|
141
|
+
}
|
|
142
|
+
const end = parser.source.indexOf("-->", parser.offset + 4);
|
|
143
|
+
if (end === -1) {
|
|
144
|
+
return parserError(parser, "Unclosed HTML comment.");
|
|
145
|
+
}
|
|
146
|
+
parser.offset = end + 3;
|
|
147
|
+
return ok(undefined);
|
|
148
|
+
};
|
|
149
|
+
const consumeClosingTag = (parser, tagName) => {
|
|
150
|
+
if (!startsWith(parser, `</${tagName}`)) {
|
|
151
|
+
return parserError(parser, `Missing closing tag for <${tagName}>.`);
|
|
152
|
+
}
|
|
153
|
+
parser.offset += tagName.length + 2;
|
|
154
|
+
consumeWhitespace(parser);
|
|
155
|
+
if (peek(parser) !== ">") {
|
|
156
|
+
return parserError(parser, "Expected end of closing tag.");
|
|
157
|
+
}
|
|
158
|
+
parser.offset++;
|
|
159
|
+
return ok(undefined);
|
|
160
|
+
};
|
|
161
|
+
const parseElement = (parser) => {
|
|
162
|
+
const start = parser.offset;
|
|
163
|
+
if (peek(parser) !== "<") {
|
|
164
|
+
return parserError(parser, "Expected an opening tag.");
|
|
165
|
+
}
|
|
166
|
+
if (startsWith(parser, "<!--")) {
|
|
167
|
+
return parserError(parser, "Unexpected HTML comment.");
|
|
168
|
+
}
|
|
169
|
+
parser.offset++;
|
|
170
|
+
if (peek(parser) === "/") {
|
|
171
|
+
return parserError(parser, "Unexpected closing tag.");
|
|
172
|
+
}
|
|
173
|
+
const tagNameResult = readName(parser);
|
|
174
|
+
if (!tagNameResult.ok) {
|
|
175
|
+
return err(tagNameResult.error);
|
|
176
|
+
}
|
|
177
|
+
const attrsResult = parseAttributes(parser);
|
|
178
|
+
if (!attrsResult.ok) {
|
|
179
|
+
return err(attrsResult.error);
|
|
180
|
+
}
|
|
181
|
+
if (startsWith(parser, "/>")) {
|
|
182
|
+
parser.offset += 2;
|
|
183
|
+
return ok({ type: "element", start, end: parser.offset, openEnd: parser.offset, tagName: tagNameResult.value, attrs: attrsResult.value, children: [] });
|
|
184
|
+
}
|
|
185
|
+
if (peek(parser) !== ">") {
|
|
186
|
+
return parserError(parser, "Expected end of opening tag.");
|
|
187
|
+
}
|
|
188
|
+
parser.offset++;
|
|
189
|
+
const openEnd = parser.offset;
|
|
190
|
+
if (voidElementNames.has(tagNameResult.value)) {
|
|
191
|
+
if (startsWith(parser, `</${tagNameResult.value}`)) {
|
|
192
|
+
const closing = consumeClosingTag(parser, tagNameResult.value);
|
|
193
|
+
if (!closing.ok) {
|
|
194
|
+
return err(closing.error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return ok({ type: "element", start, end: parser.offset, openEnd, tagName: tagNameResult.value, attrs: attrsResult.value, children: [] });
|
|
198
|
+
}
|
|
199
|
+
const children = [];
|
|
200
|
+
while (parser.offset < parser.source.length && !startsWith(parser, `</${tagNameResult.value}`)) {
|
|
201
|
+
if (startsWith(parser, "<!--")) {
|
|
202
|
+
const comment = consumeComment(parser);
|
|
203
|
+
if (!comment.ok) {
|
|
204
|
+
return err(comment.error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (peek(parser) === "<") {
|
|
208
|
+
const childResult = parseElement(parser);
|
|
209
|
+
if (!childResult.ok) {
|
|
210
|
+
return err(childResult.error);
|
|
211
|
+
}
|
|
212
|
+
children.push(childResult.value);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
children.push(parseText(parser));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const closing = consumeClosingTag(parser, tagNameResult.value);
|
|
219
|
+
if (!closing.ok) {
|
|
220
|
+
return err(closing.error);
|
|
221
|
+
}
|
|
222
|
+
return ok({ type: "element", start, end: parser.offset, openEnd, tagName: tagNameResult.value, attrs: attrsResult.value, children });
|
|
223
|
+
};
|
|
224
|
+
export const parseTemplate = (source) => {
|
|
225
|
+
const parser = { source, offset: 0 };
|
|
226
|
+
consumeWhitespace(parser);
|
|
227
|
+
const rootResult = parseElement(parser);
|
|
228
|
+
if (!rootResult.ok) {
|
|
229
|
+
return err(rootResult.error);
|
|
230
|
+
}
|
|
231
|
+
consumeWhitespace(parser);
|
|
232
|
+
if (parser.offset !== source.length) {
|
|
233
|
+
return err({ message: "Only one root element is currently supported.", offset: parser.offset });
|
|
234
|
+
}
|
|
235
|
+
return rootResult;
|
|
236
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Result } from "../result.js";
|
|
2
|
+
import type { CompiledTemplate, CompilerError, CompileTemplateOptions } from "./types.js";
|
|
3
|
+
export declare const sfcDefaultScopeName = "__tachyonSfcDefaultScope";
|
|
4
|
+
export declare const sfcNamedScopeName = "__tachyonSfcScope";
|
|
5
|
+
export declare const sfcSetupScopeName = "__tachyonSfcSetupScope";
|
|
6
|
+
export type TachyonSfcScript = {
|
|
7
|
+
attrs: string;
|
|
8
|
+
content: string;
|
|
9
|
+
offset: number;
|
|
10
|
+
};
|
|
11
|
+
export type TachyonSfcDescriptor = {
|
|
12
|
+
template: string;
|
|
13
|
+
script?: TachyonSfcScript;
|
|
14
|
+
mapTemplateOffset: (offset: number) => number;
|
|
15
|
+
};
|
|
16
|
+
export type CompiledTachyonSfc = {
|
|
17
|
+
descriptor: TachyonSfcDescriptor;
|
|
18
|
+
scriptOnly: boolean;
|
|
19
|
+
template: CompiledTemplate;
|
|
20
|
+
};
|
|
21
|
+
export type TransformedSfcScript = {
|
|
22
|
+
code: string;
|
|
23
|
+
defaultScopeName?: string;
|
|
24
|
+
setupBindings: string[];
|
|
25
|
+
};
|
|
26
|
+
export declare const sfcScriptLanguage: (script: TachyonSfcScript | undefined) => "js" | "ts";
|
|
27
|
+
export declare const isSfcSetupScript: (script: TachyonSfcScript | undefined) => boolean;
|
|
28
|
+
export declare const extractStaticSfcScope: (source: string) => Result<Record<string, unknown> | undefined, string>;
|
|
29
|
+
export declare const parseTachyonSfc: (source: string) => Result<TachyonSfcDescriptor, CompilerError>;
|
|
30
|
+
export declare const compileTachyonSfc: (source: string, options?: CompileTemplateOptions) => Result<CompiledTachyonSfc, CompilerError>;
|
|
31
|
+
export declare const generateScriptOnlyModule: (target: "client" | "server" | "stream") => string;
|
|
32
|
+
export declare const transformSfcScript: (script: TachyonSfcScript | undefined) => Result<TransformedSfcScript, CompilerError>;
|
|
33
|
+
export declare const generateSfcScriptDeclarations: (script: TachyonSfcScript | undefined) => Result<string, string>;
|