@kuratchi/js 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +78 -0
- package/dist/compiler/index.d.ts +34 -0
- package/dist/compiler/index.js +2200 -0
- package/dist/compiler/parser.d.ts +40 -0
- package/dist/compiler/parser.js +534 -0
- package/dist/compiler/template.d.ts +30 -0
- package/dist/compiler/template.js +625 -0
- package/dist/create.d.ts +7 -0
- package/dist/create.js +876 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +15 -0
- package/dist/runtime/app.d.ts +12 -0
- package/dist/runtime/app.js +118 -0
- package/dist/runtime/config.d.ts +5 -0
- package/dist/runtime/config.js +6 -0
- package/dist/runtime/containers.d.ts +61 -0
- package/dist/runtime/containers.js +127 -0
- package/dist/runtime/context.d.ts +54 -0
- package/dist/runtime/context.js +134 -0
- package/dist/runtime/do.d.ts +81 -0
- package/dist/runtime/do.js +123 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/router.d.ts +29 -0
- package/dist/runtime/router.js +73 -0
- package/dist/runtime/types.d.ts +207 -0
- package/dist/runtime/types.js +4 -0
- package/package.json +50 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML file parser.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the top-level <script> block (before the HTML document).
|
|
5
|
+
* Everything else is the template — full HTML with native JS flow control.
|
|
6
|
+
* <style> inside the HTML is NOT extracted; it's part of the template.
|
|
7
|
+
*/
|
|
8
|
+
export interface ParsedFile {
|
|
9
|
+
/** Script content (the server-side code, minus imports) */
|
|
10
|
+
script: string | null;
|
|
11
|
+
/** Template — the full HTML document with inline JS flow control */
|
|
12
|
+
template: string;
|
|
13
|
+
/** All imports from the script block */
|
|
14
|
+
serverImports: string[];
|
|
15
|
+
/** Whether the script has server-side code (beyond imports) */
|
|
16
|
+
hasLoad: boolean;
|
|
17
|
+
/** Action functions referenced via action={fn} in the template */
|
|
18
|
+
actionFunctions: string[];
|
|
19
|
+
/** Top-level variable names declared in the script (const/let/var) */
|
|
20
|
+
dataVars: string[];
|
|
21
|
+
/** Component imports: import Name from '$lib/file.html' → { Name: 'file' } */
|
|
22
|
+
componentImports: Record<string, string>;
|
|
23
|
+
/** Poll functions referenced via data-poll={fn(args)} in the template */
|
|
24
|
+
pollFunctions: string[];
|
|
25
|
+
/** Query blocks referenced via data-get={fn(args)} data-as="name" */
|
|
26
|
+
dataGetQueries: Array<{
|
|
27
|
+
fnName: string;
|
|
28
|
+
argsExpr: string;
|
|
29
|
+
asName: string;
|
|
30
|
+
key?: string;
|
|
31
|
+
rpcId?: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parse a .html route file.
|
|
36
|
+
*
|
|
37
|
+
* The <script> block at the top is extracted for the compiler.
|
|
38
|
+
* Everything else (the HTML document) becomes the template.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseFile(source: string): ParsedFile;
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML file parser.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the top-level <script> block (before the HTML document).
|
|
5
|
+
* Everything else is the template — full HTML with native JS flow control.
|
|
6
|
+
* <style> inside the HTML is NOT extracted; it's part of the template.
|
|
7
|
+
*/
|
|
8
|
+
function splitTopLevel(input, delimiter) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
let start = 0;
|
|
11
|
+
let depthParen = 0;
|
|
12
|
+
let depthBracket = 0;
|
|
13
|
+
let depthBrace = 0;
|
|
14
|
+
let quote = null;
|
|
15
|
+
let escaped = false;
|
|
16
|
+
for (let i = 0; i < input.length; i++) {
|
|
17
|
+
const ch = input[i];
|
|
18
|
+
if (quote) {
|
|
19
|
+
if (escaped) {
|
|
20
|
+
escaped = false;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (ch === '\\') {
|
|
24
|
+
escaped = true;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (ch === quote)
|
|
28
|
+
quote = null;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
32
|
+
quote = ch;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (ch === '(')
|
|
36
|
+
depthParen++;
|
|
37
|
+
else if (ch === ')')
|
|
38
|
+
depthParen = Math.max(0, depthParen - 1);
|
|
39
|
+
else if (ch === '[')
|
|
40
|
+
depthBracket++;
|
|
41
|
+
else if (ch === ']')
|
|
42
|
+
depthBracket = Math.max(0, depthBracket - 1);
|
|
43
|
+
else if (ch === '{')
|
|
44
|
+
depthBrace++;
|
|
45
|
+
else if (ch === '}')
|
|
46
|
+
depthBrace = Math.max(0, depthBrace - 1);
|
|
47
|
+
else if (ch === delimiter && depthParen === 0 && depthBracket === 0 && depthBrace === 0) {
|
|
48
|
+
parts.push(input.slice(start, i));
|
|
49
|
+
start = i + 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
parts.push(input.slice(start));
|
|
53
|
+
return parts;
|
|
54
|
+
}
|
|
55
|
+
function findTopLevelChar(input, target) {
|
|
56
|
+
let depthParen = 0;
|
|
57
|
+
let depthBracket = 0;
|
|
58
|
+
let depthBrace = 0;
|
|
59
|
+
let quote = null;
|
|
60
|
+
let escaped = false;
|
|
61
|
+
for (let i = 0; i < input.length; i++) {
|
|
62
|
+
const ch = input[i];
|
|
63
|
+
if (quote) {
|
|
64
|
+
if (escaped) {
|
|
65
|
+
escaped = false;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (ch === '\\') {
|
|
69
|
+
escaped = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (ch === quote)
|
|
73
|
+
quote = null;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
77
|
+
quote = ch;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (ch === '(')
|
|
81
|
+
depthParen++;
|
|
82
|
+
else if (ch === ')')
|
|
83
|
+
depthParen = Math.max(0, depthParen - 1);
|
|
84
|
+
else if (ch === '[')
|
|
85
|
+
depthBracket++;
|
|
86
|
+
else if (ch === ']')
|
|
87
|
+
depthBracket = Math.max(0, depthBracket - 1);
|
|
88
|
+
else if (ch === '{')
|
|
89
|
+
depthBrace++;
|
|
90
|
+
else if (ch === '}')
|
|
91
|
+
depthBrace = Math.max(0, depthBrace - 1);
|
|
92
|
+
else if (ch === target && depthParen === 0 && depthBracket === 0 && depthBrace === 0) {
|
|
93
|
+
return i;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return -1;
|
|
97
|
+
}
|
|
98
|
+
function pushIdentifier(name, out) {
|
|
99
|
+
if (!name)
|
|
100
|
+
return;
|
|
101
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name))
|
|
102
|
+
return;
|
|
103
|
+
if (!out.includes(name))
|
|
104
|
+
out.push(name);
|
|
105
|
+
}
|
|
106
|
+
function collectPatternNames(pattern, out) {
|
|
107
|
+
const p = pattern.trim();
|
|
108
|
+
if (!p)
|
|
109
|
+
return;
|
|
110
|
+
if (p.startsWith('{') && p.endsWith('}')) {
|
|
111
|
+
const body = p.slice(1, -1);
|
|
112
|
+
for (const partRaw of splitTopLevel(body, ',')) {
|
|
113
|
+
let part = partRaw.trim();
|
|
114
|
+
if (!part)
|
|
115
|
+
continue;
|
|
116
|
+
if (part.startsWith('...'))
|
|
117
|
+
part = part.slice(3).trim();
|
|
118
|
+
const eqIdx = part.indexOf('=');
|
|
119
|
+
if (eqIdx !== -1)
|
|
120
|
+
part = part.slice(0, eqIdx).trim();
|
|
121
|
+
const colonIdx = part.indexOf(':');
|
|
122
|
+
if (colonIdx !== -1) {
|
|
123
|
+
const rhs = part.slice(colonIdx + 1).trim();
|
|
124
|
+
collectPatternNames(rhs, out);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
pushIdentifier(part, out);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (p.startsWith('[') && p.endsWith(']')) {
|
|
133
|
+
const body = p.slice(1, -1);
|
|
134
|
+
for (const partRaw of splitTopLevel(body, ',')) {
|
|
135
|
+
let part = partRaw.trim();
|
|
136
|
+
if (!part)
|
|
137
|
+
continue;
|
|
138
|
+
if (part.startsWith('...'))
|
|
139
|
+
part = part.slice(3).trim();
|
|
140
|
+
const eqIdx = part.indexOf('=');
|
|
141
|
+
if (eqIdx !== -1)
|
|
142
|
+
part = part.slice(0, eqIdx).trim();
|
|
143
|
+
collectPatternNames(part, out);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const eqIdx = p.indexOf('=');
|
|
148
|
+
const ident = (eqIdx === -1 ? p : p.slice(0, eqIdx)).trim();
|
|
149
|
+
pushIdentifier(ident, out);
|
|
150
|
+
}
|
|
151
|
+
function isBoundaryChar(ch) {
|
|
152
|
+
if (!ch)
|
|
153
|
+
return true;
|
|
154
|
+
return !/[A-Za-z0-9_$]/.test(ch);
|
|
155
|
+
}
|
|
156
|
+
function extractTopLevelDataVars(scriptBody) {
|
|
157
|
+
const vars = [];
|
|
158
|
+
let i = 0;
|
|
159
|
+
let depthParen = 0;
|
|
160
|
+
let depthBracket = 0;
|
|
161
|
+
let depthBrace = 0;
|
|
162
|
+
let quote = null;
|
|
163
|
+
let escaped = false;
|
|
164
|
+
let inLineComment = false;
|
|
165
|
+
let inBlockComment = false;
|
|
166
|
+
while (i < scriptBody.length) {
|
|
167
|
+
const ch = scriptBody[i];
|
|
168
|
+
const next = scriptBody[i + 1];
|
|
169
|
+
if (inLineComment) {
|
|
170
|
+
if (ch === '\n')
|
|
171
|
+
inLineComment = false;
|
|
172
|
+
i++;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (inBlockComment) {
|
|
176
|
+
if (ch === '*' && next === '/') {
|
|
177
|
+
inBlockComment = false;
|
|
178
|
+
i += 2;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (quote) {
|
|
185
|
+
if (escaped) {
|
|
186
|
+
escaped = false;
|
|
187
|
+
i++;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (ch === '\\') {
|
|
191
|
+
escaped = true;
|
|
192
|
+
i++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (ch === quote)
|
|
196
|
+
quote = null;
|
|
197
|
+
i++;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (ch === '/' && next === '/') {
|
|
201
|
+
inLineComment = true;
|
|
202
|
+
i += 2;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (ch === '/' && next === '*') {
|
|
206
|
+
inBlockComment = true;
|
|
207
|
+
i += 2;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
211
|
+
quote = ch;
|
|
212
|
+
i++;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (ch === '(')
|
|
216
|
+
depthParen++;
|
|
217
|
+
else if (ch === ')')
|
|
218
|
+
depthParen = Math.max(0, depthParen - 1);
|
|
219
|
+
else if (ch === '[')
|
|
220
|
+
depthBracket++;
|
|
221
|
+
else if (ch === ']')
|
|
222
|
+
depthBracket = Math.max(0, depthBracket - 1);
|
|
223
|
+
else if (ch === '{')
|
|
224
|
+
depthBrace++;
|
|
225
|
+
else if (ch === '}')
|
|
226
|
+
depthBrace = Math.max(0, depthBrace - 1);
|
|
227
|
+
const atTopLevel = depthParen === 0 && depthBracket === 0 && depthBrace === 0;
|
|
228
|
+
if (!atTopLevel) {
|
|
229
|
+
i++;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const remaining = scriptBody.slice(i);
|
|
233
|
+
const match = /^(const|let|var)\b/.exec(remaining);
|
|
234
|
+
if (!match) {
|
|
235
|
+
i++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const keyword = match[1];
|
|
239
|
+
const before = scriptBody[i - 1];
|
|
240
|
+
const after = scriptBody[i + keyword.length];
|
|
241
|
+
if (!isBoundaryChar(before) || !isBoundaryChar(after)) {
|
|
242
|
+
i++;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
i += keyword.length;
|
|
246
|
+
let declStart = i;
|
|
247
|
+
let localParen = 0;
|
|
248
|
+
let localBracket = 0;
|
|
249
|
+
let localBrace = 0;
|
|
250
|
+
let localQuote = null;
|
|
251
|
+
let localEscaped = false;
|
|
252
|
+
while (i < scriptBody.length) {
|
|
253
|
+
const c = scriptBody[i];
|
|
254
|
+
if (localQuote) {
|
|
255
|
+
if (localEscaped) {
|
|
256
|
+
localEscaped = false;
|
|
257
|
+
i++;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (c === '\\') {
|
|
261
|
+
localEscaped = true;
|
|
262
|
+
i++;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (c === localQuote)
|
|
266
|
+
localQuote = null;
|
|
267
|
+
i++;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
271
|
+
localQuote = c;
|
|
272
|
+
i++;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (c === '(')
|
|
276
|
+
localParen++;
|
|
277
|
+
else if (c === ')')
|
|
278
|
+
localParen = Math.max(0, localParen - 1);
|
|
279
|
+
else if (c === '[')
|
|
280
|
+
localBracket++;
|
|
281
|
+
else if (c === ']')
|
|
282
|
+
localBracket = Math.max(0, localBracket - 1);
|
|
283
|
+
else if (c === '{')
|
|
284
|
+
localBrace++;
|
|
285
|
+
else if (c === '}')
|
|
286
|
+
localBrace = Math.max(0, localBrace - 1);
|
|
287
|
+
if (c === ';' && localParen === 0 && localBracket === 0 && localBrace === 0) {
|
|
288
|
+
const decl = scriptBody.slice(declStart, i).trim();
|
|
289
|
+
for (const item of splitTopLevel(decl, ',')) {
|
|
290
|
+
const trimmed = item.trim();
|
|
291
|
+
if (!trimmed)
|
|
292
|
+
continue;
|
|
293
|
+
const eqIdx = findTopLevelChar(trimmed, '=');
|
|
294
|
+
const pattern = eqIdx === -1 ? trimmed : trimmed.slice(0, eqIdx).trim();
|
|
295
|
+
collectPatternNames(pattern, vars);
|
|
296
|
+
}
|
|
297
|
+
i++;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
i++;
|
|
301
|
+
}
|
|
302
|
+
// Semicolon-less declaration at EOF
|
|
303
|
+
if (i >= scriptBody.length) {
|
|
304
|
+
const decl = scriptBody.slice(declStart).trim();
|
|
305
|
+
for (const item of splitTopLevel(decl, ',')) {
|
|
306
|
+
const trimmed = item.trim();
|
|
307
|
+
if (!trimmed)
|
|
308
|
+
continue;
|
|
309
|
+
const eqIdx = findTopLevelChar(trimmed, '=');
|
|
310
|
+
const pattern = eqIdx === -1 ? trimmed : trimmed.slice(0, eqIdx).trim();
|
|
311
|
+
collectPatternNames(pattern, vars);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return vars;
|
|
316
|
+
}
|
|
317
|
+
function extractTopLevelFunctionNames(scriptBody) {
|
|
318
|
+
const names = [];
|
|
319
|
+
let i = 0;
|
|
320
|
+
let depthParen = 0;
|
|
321
|
+
let depthBracket = 0;
|
|
322
|
+
let depthBrace = 0;
|
|
323
|
+
let quote = null;
|
|
324
|
+
let escaped = false;
|
|
325
|
+
let inLineComment = false;
|
|
326
|
+
let inBlockComment = false;
|
|
327
|
+
while (i < scriptBody.length) {
|
|
328
|
+
const ch = scriptBody[i];
|
|
329
|
+
const next = scriptBody[i + 1];
|
|
330
|
+
if (inLineComment) {
|
|
331
|
+
if (ch === '\n')
|
|
332
|
+
inLineComment = false;
|
|
333
|
+
i++;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (inBlockComment) {
|
|
337
|
+
if (ch === '*' && next === '/') {
|
|
338
|
+
inBlockComment = false;
|
|
339
|
+
i += 2;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
i++;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (quote) {
|
|
346
|
+
if (escaped) {
|
|
347
|
+
escaped = false;
|
|
348
|
+
i++;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (ch === '\\') {
|
|
352
|
+
escaped = true;
|
|
353
|
+
i++;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (ch === quote)
|
|
357
|
+
quote = null;
|
|
358
|
+
i++;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (ch === '/' && next === '/') {
|
|
362
|
+
inLineComment = true;
|
|
363
|
+
i += 2;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (ch === '/' && next === '*') {
|
|
367
|
+
inBlockComment = true;
|
|
368
|
+
i += 2;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
372
|
+
quote = ch;
|
|
373
|
+
i++;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (ch === '(')
|
|
377
|
+
depthParen++;
|
|
378
|
+
else if (ch === ')')
|
|
379
|
+
depthParen = Math.max(0, depthParen - 1);
|
|
380
|
+
else if (ch === '[')
|
|
381
|
+
depthBracket++;
|
|
382
|
+
else if (ch === ']')
|
|
383
|
+
depthBracket = Math.max(0, depthBracket - 1);
|
|
384
|
+
else if (ch === '{')
|
|
385
|
+
depthBrace++;
|
|
386
|
+
else if (ch === '}')
|
|
387
|
+
depthBrace = Math.max(0, depthBrace - 1);
|
|
388
|
+
if (depthParen === 0 && depthBracket === 0 && depthBrace === 0) {
|
|
389
|
+
const rest = scriptBody.slice(i);
|
|
390
|
+
const fnMatch = /^(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/.exec(rest);
|
|
391
|
+
if (fnMatch) {
|
|
392
|
+
pushIdentifier(fnMatch[1], names);
|
|
393
|
+
i += fnMatch[0].length;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
i++;
|
|
398
|
+
}
|
|
399
|
+
return names;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Parse a .html route file.
|
|
403
|
+
*
|
|
404
|
+
* The <script> block at the top is extracted for the compiler.
|
|
405
|
+
* Everything else (the HTML document) becomes the template.
|
|
406
|
+
*/
|
|
407
|
+
export function parseFile(source) {
|
|
408
|
+
let script = null;
|
|
409
|
+
let template = source;
|
|
410
|
+
// Extract the first <script>...</script> block (the server-side framework script)
|
|
411
|
+
// Only match if it appears before any HTML document content
|
|
412
|
+
const scriptMatch = template.match(/^(\s*)<script(\s[^>]*)?\s*>([\s\S]*?)<\/script>/);
|
|
413
|
+
if (scriptMatch) {
|
|
414
|
+
script = scriptMatch[3].trim();
|
|
415
|
+
template = template.slice(scriptMatch[0].length).trim();
|
|
416
|
+
}
|
|
417
|
+
// Extract all imports from script
|
|
418
|
+
const serverImports = [];
|
|
419
|
+
const componentImports = {};
|
|
420
|
+
if (script) {
|
|
421
|
+
// Support both single-line and multiline static imports.
|
|
422
|
+
const importRegex = /^\s*import[\s\S]*?from\s+['"][^'"]+['"]\s*;?/gm;
|
|
423
|
+
let m;
|
|
424
|
+
while ((m = importRegex.exec(script)) !== null) {
|
|
425
|
+
const line = m[0].trim();
|
|
426
|
+
// Check for component imports: import Name from '$lib/file.html' or '@kuratchi/ui/file.html'
|
|
427
|
+
const libMatch = line.match(/import\s+([A-Za-z_$][\w$]*)\s+from\s+['"]\$lib\/([^'"]+\.html)['"]/s);
|
|
428
|
+
const pkgMatch = !libMatch ? line.match(/import\s+([A-Za-z_$][\w$]*)\s+from\s+['"](@[^/'"]+\/[^/'"]+)\/([^'"]+\.html)['"]/s) : null;
|
|
429
|
+
if (libMatch) {
|
|
430
|
+
const componentName = libMatch[1]; // e.g. "StatCard"
|
|
431
|
+
const fileName = libMatch[2].replace('.html', ''); // e.g. "stat-card"
|
|
432
|
+
componentImports[componentName] = fileName;
|
|
433
|
+
}
|
|
434
|
+
else if (pkgMatch) {
|
|
435
|
+
const componentName = pkgMatch[1]; // e.g. "Badge"
|
|
436
|
+
const pkg = pkgMatch[2]; // e.g. "@kuratchi/ui"
|
|
437
|
+
const fileName = pkgMatch[3].replace('.html', ''); // e.g. "badge"
|
|
438
|
+
componentImports[componentName] = `${pkg}:${fileName}`; // e.g. "@kuratchi/ui:badge"
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
serverImports.push(line);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// Extract top-level variable declarations from script body (after removing imports)
|
|
446
|
+
const dataVars = [];
|
|
447
|
+
let scriptBody = '';
|
|
448
|
+
if (script) {
|
|
449
|
+
// Remove import lines to get the script body
|
|
450
|
+
scriptBody = script.replace(/^\s*import[\s\S]*?from\s+['"][^'"]+['"]\s*;?/gm, '').trim();
|
|
451
|
+
const topLevelVars = extractTopLevelDataVars(scriptBody);
|
|
452
|
+
for (const v of topLevelVars)
|
|
453
|
+
dataVars.push(v);
|
|
454
|
+
const topLevelFns = extractTopLevelFunctionNames(scriptBody);
|
|
455
|
+
for (const fn of topLevelFns) {
|
|
456
|
+
if (!dataVars.includes(fn))
|
|
457
|
+
dataVars.push(fn);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
const hasLoad = scriptBody.length > 0;
|
|
461
|
+
// Strip HTML comments from the template before scanning for action references.
|
|
462
|
+
// This prevents commented-out code (<!-- ... -->) from being parsed as live
|
|
463
|
+
// action expressions, which would cause false "Invalid action expression" errors.
|
|
464
|
+
const templateWithoutComments = template.replace(/<!--[\s\S]*?-->/g, '');
|
|
465
|
+
// Extract action functions referenced in template: action={fnName}
|
|
466
|
+
const actionFunctions = [];
|
|
467
|
+
const actionRegex = /action=\{(\w+)\}/g;
|
|
468
|
+
let am;
|
|
469
|
+
while ((am = actionRegex.exec(templateWithoutComments)) !== null) {
|
|
470
|
+
if (!actionFunctions.includes(am[1])) {
|
|
471
|
+
actionFunctions.push(am[1]);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// Also collect onX={fnName(...)} candidates (e.g. onclick, onClick, onChange)
|
|
475
|
+
// — the compiler will filter these against actual imports to determine server actions.
|
|
476
|
+
const eventActionRegex = /on[A-Za-z]+\s*=\{(\w+)\s*\(/g;
|
|
477
|
+
let em;
|
|
478
|
+
while ((em = eventActionRegex.exec(templateWithoutComments)) !== null) {
|
|
479
|
+
if (!actionFunctions.includes(em[1])) {
|
|
480
|
+
actionFunctions.push(em[1]);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Collect method-style action attributes: data-post/data-put/data-patch/data-delete
|
|
484
|
+
const methodActionRegex = /data-(?:post|put|patch|delete)\s*=\{(\w+)\s*\(/g;
|
|
485
|
+
let mm;
|
|
486
|
+
while ((mm = methodActionRegex.exec(templateWithoutComments)) !== null) {
|
|
487
|
+
if (!actionFunctions.includes(mm[1])) {
|
|
488
|
+
actionFunctions.push(mm[1]);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Extract poll functions referenced in template: data-poll={fnName(args)}
|
|
492
|
+
const pollFunctions = [];
|
|
493
|
+
const pollRegex = /data-poll=\{(\w+)\s*\(/g;
|
|
494
|
+
let pm;
|
|
495
|
+
while ((pm = pollRegex.exec(templateWithoutComments)) !== null) {
|
|
496
|
+
if (!pollFunctions.includes(pm[1])) {
|
|
497
|
+
pollFunctions.push(pm[1]);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Extract query blocks: tags that include both data-get={fn(args)} and data-as=...
|
|
501
|
+
const dataGetQueries = [];
|
|
502
|
+
const tagRegex = /<[^>]+>/g;
|
|
503
|
+
let tm;
|
|
504
|
+
while ((tm = tagRegex.exec(templateWithoutComments)) !== null) {
|
|
505
|
+
const tag = tm[0];
|
|
506
|
+
const getMatch = tag.match(/\bdata-get=\{([\s\S]*?)\}/);
|
|
507
|
+
if (!getMatch)
|
|
508
|
+
continue;
|
|
509
|
+
const call = getMatch[1].trim();
|
|
510
|
+
const callMatch = call.match(/^([A-Za-z_$][\w$]*)\(([\s\S]*)\)$/);
|
|
511
|
+
if (!callMatch)
|
|
512
|
+
continue;
|
|
513
|
+
const fnName = callMatch[1];
|
|
514
|
+
const argsExpr = (callMatch[2] || '').trim();
|
|
515
|
+
const asMatch = tag.match(/\bdata-as="([A-Za-z_$][\w$]*)"/) ||
|
|
516
|
+
tag.match(/\bdata-as='([A-Za-z_$][\w$]*)'/) ||
|
|
517
|
+
tag.match(/\bdata-as=\{([A-Za-z_$][\w$]*)\}/);
|
|
518
|
+
if (!asMatch)
|
|
519
|
+
continue;
|
|
520
|
+
const asName = asMatch[1];
|
|
521
|
+
const keyMatch = tag.match(/\bdata-key="([^"]+)"/) ||
|
|
522
|
+
tag.match(/\bdata-key='([^']+)'/) ||
|
|
523
|
+
tag.match(/\bdata-key=\{([^}]+)\}/);
|
|
524
|
+
const key = keyMatch?.[1]?.trim() || asName;
|
|
525
|
+
if (!pollFunctions.includes(fnName))
|
|
526
|
+
pollFunctions.push(fnName);
|
|
527
|
+
if (!dataVars.includes(asName))
|
|
528
|
+
dataVars.push(asName);
|
|
529
|
+
const exists = dataGetQueries.some((q) => q.asName === asName);
|
|
530
|
+
if (!exists)
|
|
531
|
+
dataGetQueries.push({ fnName, argsExpr, asName, key });
|
|
532
|
+
}
|
|
533
|
+
return { script, template, serverImports, hasLoad, actionFunctions, dataVars, componentImports, pollFunctions, dataGetQueries };
|
|
534
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template compiler — native JS flow control in HTML.
|
|
3
|
+
*
|
|
4
|
+
* Syntax:
|
|
5
|
+
* {expression} → escaped output
|
|
6
|
+
* {=html expression} → raw HTML output (unescaped)
|
|
7
|
+
* for (const x of arr) { → JS for loop (inline in HTML)
|
|
8
|
+
* <li>{x.name}</li>
|
|
9
|
+
* }
|
|
10
|
+
* if (condition) { → JS if block
|
|
11
|
+
* <p>yes</p>
|
|
12
|
+
* } else {
|
|
13
|
+
* <p>no</p>
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* The compiler scans line-by-line:
|
|
17
|
+
* - Lines that are pure JS control flow (for/if/else/}) → emitted as JS
|
|
18
|
+
* - Everything else → emitted as HTML string with {expr} interpolation
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Compile a template string into a JS render function body.
|
|
22
|
+
*
|
|
23
|
+
* The generated code expects `data` in scope (destructured load return)
|
|
24
|
+
* and an `__esc` helper for HTML-escaping.
|
|
25
|
+
*/
|
|
26
|
+
export declare function compileTemplate(template: string, componentNames?: Map<string, string>, actionNames?: Set<string>, rpcNameMap?: Map<string, string>): string;
|
|
27
|
+
/**
|
|
28
|
+
* Generate the full render function source code.
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateRenderFunction(template: string): string;
|