@kuratchi/js 0.0.15 → 0.0.17
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 +160 -1
- package/dist/cli.js +78 -45
- package/dist/compiler/api-route-pipeline.d.ts +8 -0
- package/dist/compiler/api-route-pipeline.js +23 -0
- package/dist/compiler/asset-pipeline.d.ts +7 -0
- package/dist/compiler/asset-pipeline.js +33 -0
- package/dist/compiler/client-module-pipeline.d.ts +25 -0
- package/dist/compiler/client-module-pipeline.js +257 -0
- package/dist/compiler/compiler-shared.d.ts +73 -0
- package/dist/compiler/compiler-shared.js +4 -0
- package/dist/compiler/component-pipeline.d.ts +15 -0
- package/dist/compiler/component-pipeline.js +158 -0
- package/dist/compiler/config-reading.d.ts +12 -0
- package/dist/compiler/config-reading.js +380 -0
- package/dist/compiler/convention-discovery.d.ts +9 -0
- package/dist/compiler/convention-discovery.js +83 -0
- package/dist/compiler/durable-object-pipeline.d.ts +9 -0
- package/dist/compiler/durable-object-pipeline.js +255 -0
- package/dist/compiler/error-page-pipeline.d.ts +1 -0
- package/dist/compiler/error-page-pipeline.js +16 -0
- package/dist/compiler/import-linking.d.ts +36 -0
- package/dist/compiler/import-linking.js +140 -0
- package/dist/compiler/index.d.ts +7 -7
- package/dist/compiler/index.js +181 -3321
- package/dist/compiler/layout-pipeline.d.ts +31 -0
- package/dist/compiler/layout-pipeline.js +155 -0
- package/dist/compiler/page-route-pipeline.d.ts +16 -0
- package/dist/compiler/page-route-pipeline.js +62 -0
- package/dist/compiler/parser.d.ts +4 -0
- package/dist/compiler/parser.js +436 -55
- package/dist/compiler/root-layout-pipeline.d.ts +10 -0
- package/dist/compiler/root-layout-pipeline.js +532 -0
- package/dist/compiler/route-discovery.d.ts +7 -0
- package/dist/compiler/route-discovery.js +87 -0
- package/dist/compiler/route-pipeline.d.ts +57 -0
- package/dist/compiler/route-pipeline.js +291 -0
- package/dist/compiler/route-state-pipeline.d.ts +26 -0
- package/dist/compiler/route-state-pipeline.js +139 -0
- package/dist/compiler/routes-module-feature-blocks.d.ts +2 -0
- package/dist/compiler/routes-module-feature-blocks.js +330 -0
- package/dist/compiler/routes-module-pipeline.d.ts +2 -0
- package/dist/compiler/routes-module-pipeline.js +6 -0
- package/dist/compiler/routes-module-runtime-shell.d.ts +2 -0
- package/dist/compiler/routes-module-runtime-shell.js +91 -0
- package/dist/compiler/routes-module-types.d.ts +45 -0
- package/dist/compiler/routes-module-types.js +1 -0
- package/dist/compiler/script-transform.d.ts +16 -0
- package/dist/compiler/script-transform.js +218 -0
- package/dist/compiler/server-module-pipeline.d.ts +13 -0
- package/dist/compiler/server-module-pipeline.js +124 -0
- package/dist/compiler/template.d.ts +13 -1
- package/dist/compiler/template.js +337 -71
- package/dist/compiler/worker-output-pipeline.d.ts +13 -0
- package/dist/compiler/worker-output-pipeline.js +37 -0
- package/dist/compiler/wrangler-sync.d.ts +14 -0
- package/dist/compiler/wrangler-sync.js +185 -0
- package/dist/runtime/app.js +15 -3
- package/dist/runtime/context.d.ts +4 -0
- package/dist/runtime/context.js +40 -2
- package/dist/runtime/do.js +21 -6
- package/dist/runtime/generated-worker.d.ts +55 -0
- package/dist/runtime/generated-worker.js +543 -0
- package/dist/runtime/index.d.ts +4 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/router.d.ts +6 -1
- package/dist/runtime/router.js +125 -31
- package/dist/runtime/security.d.ts +101 -0
- package/dist/runtime/security.js +298 -0
- package/dist/runtime/types.d.ts +29 -2
- package/package.json +5 -1
|
@@ -37,14 +37,249 @@ const JS_CONTROL_PATTERNS = [
|
|
|
37
37
|
function isJsControlLine(line) {
|
|
38
38
|
return JS_CONTROL_PATTERNS.some(p => p.test(line));
|
|
39
39
|
}
|
|
40
|
+
const FRAGMENT_OPEN_MARKER = '<!--__KURATCHI_FRAGMENT_OPEN:';
|
|
41
|
+
const FRAGMENT_CLOSE_MARKER = '<!--__KURATCHI_FRAGMENT_CLOSE-->';
|
|
42
|
+
export function splitTemplateRenderSections(template) {
|
|
43
|
+
const bodyLines = [];
|
|
44
|
+
const headLines = [];
|
|
45
|
+
const lines = template.split('\n');
|
|
46
|
+
let inHead = false;
|
|
47
|
+
let inStyle = false;
|
|
48
|
+
let inScript = false;
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
const trimmed = line.trim();
|
|
51
|
+
const opensStyle = /<style[\s>]/i.test(trimmed);
|
|
52
|
+
const closesStyle = /<\/style>/i.test(trimmed);
|
|
53
|
+
const opensScript = /<script[\s>]/i.test(trimmed);
|
|
54
|
+
const closesScript = /<\/script>/i.test(trimmed);
|
|
55
|
+
const inRawBlock = inStyle || inScript || opensStyle || opensScript;
|
|
56
|
+
if (inRawBlock) {
|
|
57
|
+
let remaining = line;
|
|
58
|
+
let bodyLine = '';
|
|
59
|
+
let headLine = '';
|
|
60
|
+
while (remaining.length > 0) {
|
|
61
|
+
if (!inHead) {
|
|
62
|
+
const openMatch = remaining.match(/<head(?:\s[^>]*)?>/i);
|
|
63
|
+
if (!openMatch || openMatch.index === undefined) {
|
|
64
|
+
bodyLine += remaining;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
bodyLine += remaining.slice(0, openMatch.index);
|
|
68
|
+
remaining = remaining.slice(openMatch.index + openMatch[0].length);
|
|
69
|
+
const closeMatch = remaining.match(/<\/head>/i);
|
|
70
|
+
if (!closeMatch || closeMatch.index === undefined) {
|
|
71
|
+
headLine += remaining;
|
|
72
|
+
remaining = '';
|
|
73
|
+
inHead = true;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
headLine += remaining.slice(0, closeMatch.index);
|
|
77
|
+
remaining = remaining.slice(closeMatch.index + closeMatch[0].length);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const closeMatch = remaining.match(/<\/head>/i);
|
|
81
|
+
if (!closeMatch || closeMatch.index === undefined) {
|
|
82
|
+
headLine += remaining;
|
|
83
|
+
remaining = '';
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
headLine += remaining.slice(0, closeMatch.index);
|
|
87
|
+
remaining = remaining.slice(closeMatch.index + closeMatch[0].length);
|
|
88
|
+
inHead = false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
bodyLines.push(bodyLine);
|
|
92
|
+
headLines.push(headLine);
|
|
93
|
+
if (opensStyle && !closesStyle)
|
|
94
|
+
inStyle = true;
|
|
95
|
+
if (closesStyle)
|
|
96
|
+
inStyle = false;
|
|
97
|
+
if (opensScript && !closesScript)
|
|
98
|
+
inScript = true;
|
|
99
|
+
if (closesScript)
|
|
100
|
+
inScript = false;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (isJsControlLine(trimmed) && !/<\/?head(?:\s|>)/i.test(line)) {
|
|
104
|
+
bodyLines.push(line);
|
|
105
|
+
headLines.push(line);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
let remaining = line;
|
|
109
|
+
let bodyLine = '';
|
|
110
|
+
let headLine = '';
|
|
111
|
+
while (remaining.length > 0) {
|
|
112
|
+
if (!inHead) {
|
|
113
|
+
const openMatch = remaining.match(/<head(?:\s[^>]*)?>/i);
|
|
114
|
+
if (!openMatch || openMatch.index === undefined) {
|
|
115
|
+
bodyLine += remaining;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
bodyLine += remaining.slice(0, openMatch.index);
|
|
119
|
+
remaining = remaining.slice(openMatch.index + openMatch[0].length);
|
|
120
|
+
const closeMatch = remaining.match(/<\/head>/i);
|
|
121
|
+
if (!closeMatch || closeMatch.index === undefined) {
|
|
122
|
+
headLine += remaining;
|
|
123
|
+
remaining = '';
|
|
124
|
+
inHead = true;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
headLine += remaining.slice(0, closeMatch.index);
|
|
128
|
+
remaining = remaining.slice(closeMatch.index + closeMatch[0].length);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const closeMatch = remaining.match(/<\/head>/i);
|
|
132
|
+
if (!closeMatch || closeMatch.index === undefined) {
|
|
133
|
+
headLine += remaining;
|
|
134
|
+
remaining = '';
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
headLine += remaining.slice(0, closeMatch.index);
|
|
138
|
+
remaining = remaining.slice(closeMatch.index + closeMatch[0].length);
|
|
139
|
+
inHead = false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
bodyLines.push(bodyLine);
|
|
143
|
+
headLines.push(headLine);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
bodyTemplate: bodyLines.join('\n'),
|
|
147
|
+
headTemplate: headLines.join('\n'),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function buildAppendStatement(expression, emitCall) {
|
|
151
|
+
// When emitCall is provided, use it (e.g., __emit(expr))
|
|
152
|
+
// Otherwise, use array push for O(n) performance
|
|
153
|
+
return emitCall ? `${emitCall}(${expression});` : `__parts.push(${expression});`;
|
|
154
|
+
}
|
|
155
|
+
function extractPollFragmentExpr(tagText, rpcNameMap) {
|
|
156
|
+
const pollMatch = tagText.match(/\bdata-poll=\{([\s\S]*?)\}/);
|
|
157
|
+
if (!pollMatch)
|
|
158
|
+
return null;
|
|
159
|
+
const inner = pollMatch[1].trim();
|
|
160
|
+
const callMatch = inner.match(/^([A-Za-z_$][\w$]*)\(([\s\S]*)\)$/);
|
|
161
|
+
if (!callMatch)
|
|
162
|
+
return null;
|
|
163
|
+
const fnName = callMatch[1];
|
|
164
|
+
const rpcName = rpcNameMap?.get(fnName) || fnName;
|
|
165
|
+
const argsExpr = (callMatch[2] || '').trim();
|
|
166
|
+
if (!argsExpr)
|
|
167
|
+
return JSON.stringify(`__poll_${rpcName}`);
|
|
168
|
+
return `'__poll_' + String(${argsExpr}).replace(/[^a-zA-Z0-9]/g, '_')`;
|
|
169
|
+
}
|
|
170
|
+
function findTagEnd(template, start) {
|
|
171
|
+
let quote = null;
|
|
172
|
+
let escaped = false;
|
|
173
|
+
let braceDepth = 0;
|
|
174
|
+
for (let i = start; i < template.length; i++) {
|
|
175
|
+
const ch = template[i];
|
|
176
|
+
if (quote) {
|
|
177
|
+
if (escaped) {
|
|
178
|
+
escaped = false;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (ch === '\\') {
|
|
182
|
+
escaped = true;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (ch === quote)
|
|
186
|
+
quote = null;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
190
|
+
quote = ch;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (ch === '{') {
|
|
194
|
+
braceDepth++;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (ch === '}') {
|
|
198
|
+
braceDepth = Math.max(0, braceDepth - 1);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (ch === '>' && braceDepth === 0) {
|
|
202
|
+
return i;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return -1;
|
|
206
|
+
}
|
|
207
|
+
function instrumentPollFragments(template, rpcNameMap) {
|
|
208
|
+
const out = [];
|
|
209
|
+
const stack = [];
|
|
210
|
+
let cursor = 0;
|
|
211
|
+
while (cursor < template.length) {
|
|
212
|
+
const lt = template.indexOf('<', cursor);
|
|
213
|
+
if (lt === -1) {
|
|
214
|
+
out.push(template.slice(cursor));
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
out.push(template.slice(cursor, lt));
|
|
218
|
+
if (template.startsWith('<!--', lt)) {
|
|
219
|
+
const commentEnd = template.indexOf('-->', lt + 4);
|
|
220
|
+
if (commentEnd === -1) {
|
|
221
|
+
out.push(template.slice(lt));
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
out.push(template.slice(lt, commentEnd + 3));
|
|
225
|
+
cursor = commentEnd + 3;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const tagEnd = findTagEnd(template, lt + 1);
|
|
229
|
+
if (tagEnd === -1) {
|
|
230
|
+
out.push(template.slice(lt));
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
const tagText = template.slice(lt, tagEnd + 1);
|
|
234
|
+
const closingMatch = tagText.match(/^<\s*\/\s*([A-Za-z][\w:-]*)\s*>$/);
|
|
235
|
+
if (closingMatch) {
|
|
236
|
+
const closingTag = closingMatch[1].toLowerCase();
|
|
237
|
+
const last = stack[stack.length - 1];
|
|
238
|
+
if (last && last.tagName === closingTag) {
|
|
239
|
+
if (last.fragmentExpr)
|
|
240
|
+
out.push(FRAGMENT_CLOSE_MARKER);
|
|
241
|
+
stack.pop();
|
|
242
|
+
}
|
|
243
|
+
out.push(tagText);
|
|
244
|
+
cursor = tagEnd + 1;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const openMatch = tagText.match(/^<\s*([A-Za-z][\w:-]*)\b/);
|
|
248
|
+
out.push(tagText);
|
|
249
|
+
if (!openMatch) {
|
|
250
|
+
cursor = tagEnd + 1;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const tagName = openMatch[1].toLowerCase();
|
|
254
|
+
const isVoidLike = /\/\s*>$/.test(tagText) || /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i.test(tagName);
|
|
255
|
+
const fragmentExpr = extractPollFragmentExpr(tagText, rpcNameMap);
|
|
256
|
+
if (fragmentExpr) {
|
|
257
|
+
out.push(`${FRAGMENT_OPEN_MARKER}${encodeURIComponent(fragmentExpr)}-->`);
|
|
258
|
+
if (isVoidLike) {
|
|
259
|
+
out.push(FRAGMENT_CLOSE_MARKER);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (!isVoidLike) {
|
|
263
|
+
stack.push({ tagName, fragmentExpr });
|
|
264
|
+
}
|
|
265
|
+
cursor = tagEnd + 1;
|
|
266
|
+
}
|
|
267
|
+
return out.join('');
|
|
268
|
+
}
|
|
40
269
|
/**
|
|
41
270
|
* Compile a template string into a JS render function body.
|
|
42
271
|
*
|
|
43
272
|
* The generated code expects `data` in scope (destructured load return)
|
|
44
273
|
* and an `__esc` helper for HTML-escaping.
|
|
45
274
|
*/
|
|
46
|
-
export function compileTemplate(template, componentNames, actionNames, rpcNameMap) {
|
|
47
|
-
const
|
|
275
|
+
export function compileTemplate(template, componentNames, actionNames, rpcNameMap, options = {}) {
|
|
276
|
+
const emitCall = options.emitCall;
|
|
277
|
+
if (options.enableFragmentManifest) {
|
|
278
|
+
template = instrumentPollFragments(template, rpcNameMap);
|
|
279
|
+
}
|
|
280
|
+
// Use array accumulation for O(n) performance instead of O(n²) string concatenation
|
|
281
|
+
const useArrayAccum = !emitCall;
|
|
282
|
+
const out = useArrayAccum ? ['const __parts = [];'] : ['let __html = "";'];
|
|
48
283
|
const lines = template.split('\n');
|
|
49
284
|
let inStyle = false;
|
|
50
285
|
let inScript = false;
|
|
@@ -58,7 +293,7 @@ export function compileTemplate(template, componentNames, actionNames, rpcNameMa
|
|
|
58
293
|
if (trimmed.match(/<style[\s>]/i))
|
|
59
294
|
inStyle = true;
|
|
60
295
|
if (inStyle) {
|
|
61
|
-
out.push(
|
|
296
|
+
out.push(buildAppendStatement(`\`${escapeLiteral(line)}\\n\``, emitCall));
|
|
62
297
|
if (trimmed.match(/<\/style>/i))
|
|
63
298
|
inStyle = false;
|
|
64
299
|
continue;
|
|
@@ -70,7 +305,7 @@ export function compileTemplate(template, componentNames, actionNames, rpcNameMa
|
|
|
70
305
|
if (trimmed.match(/<\/script>/i)) {
|
|
71
306
|
const transformed = transformClientScriptBlock(scriptBuffer.join('\n'));
|
|
72
307
|
for (const scriptLine of transformed.split('\n')) {
|
|
73
|
-
out.push(
|
|
308
|
+
out.push(buildAppendStatement(`\`${escapeLiteral(scriptLine)}\\n\``, emitCall));
|
|
74
309
|
}
|
|
75
310
|
scriptBuffer = [];
|
|
76
311
|
inScript = false;
|
|
@@ -82,7 +317,7 @@ export function compileTemplate(template, componentNames, actionNames, rpcNameMa
|
|
|
82
317
|
if (trimmed.match(/<\/script>/i)) {
|
|
83
318
|
const transformed = transformClientScriptBlock(scriptBuffer.join('\n'));
|
|
84
319
|
for (const scriptLine of transformed.split('\n')) {
|
|
85
|
-
out.push(
|
|
320
|
+
out.push(buildAppendStatement(`\`${escapeLiteral(scriptLine)}\\n\``, emitCall));
|
|
86
321
|
}
|
|
87
322
|
scriptBuffer = [];
|
|
88
323
|
inScript = false;
|
|
@@ -92,7 +327,7 @@ export function compileTemplate(template, componentNames, actionNames, rpcNameMa
|
|
|
92
327
|
const startedInsideQuotedAttr = !!htmlAttrQuote;
|
|
93
328
|
const nextHtmlState = advanceHtmlTagState(line, inHtmlTag, htmlAttrQuote);
|
|
94
329
|
if (startedInsideQuotedAttr) {
|
|
95
|
-
out.push(
|
|
330
|
+
out.push(buildAppendStatement(`\`${escapeLiteral(line)}\n\``, emitCall));
|
|
96
331
|
inHtmlTag = nextHtmlState.inTag;
|
|
97
332
|
htmlAttrQuote = nextHtmlState.quote;
|
|
98
333
|
continue;
|
|
@@ -101,13 +336,13 @@ export function compileTemplate(template, componentNames, actionNames, rpcNameMa
|
|
|
101
336
|
htmlAttrQuote = nextHtmlState.quote;
|
|
102
337
|
// Skip empty lines
|
|
103
338
|
if (!trimmed) {
|
|
104
|
-
out.push(
|
|
339
|
+
out.push(buildAppendStatement(`"\\n"`, emitCall));
|
|
105
340
|
continue;
|
|
106
341
|
}
|
|
107
342
|
// One-line inline if/else with branch content:
|
|
108
343
|
// if (cond) { text/html } else { text/html }
|
|
109
344
|
// Compile branch content as template output instead of raw JS.
|
|
110
|
-
const inlineIfElse = tryCompileInlineIfElseLine(trimmed, actionNames, rpcNameMap);
|
|
345
|
+
const inlineIfElse = tryCompileInlineIfElseLine(trimmed, actionNames, rpcNameMap, options);
|
|
111
346
|
if (inlineIfElse) {
|
|
112
347
|
out.push(...inlineIfElse);
|
|
113
348
|
continue;
|
|
@@ -137,7 +372,7 @@ export function compileTemplate(template, componentNames, actionNames, rpcNameMa
|
|
|
137
372
|
}
|
|
138
373
|
}
|
|
139
374
|
// Self-closing: <Card attr="x" />
|
|
140
|
-
const componentLine = tryCompileComponentTag(joinedTrimmed, componentNames, actionNames, rpcNameMap);
|
|
375
|
+
const componentLine = tryCompileComponentTag(joinedTrimmed, componentNames, actionNames, rpcNameMap, options);
|
|
141
376
|
if (componentLine) {
|
|
142
377
|
i += joinedExtra;
|
|
143
378
|
out.push(componentLine);
|
|
@@ -167,10 +402,12 @@ export function compileTemplate(template, componentNames, actionNames, rpcNameMa
|
|
|
167
402
|
}
|
|
168
403
|
// Compile children into a sub-render block
|
|
169
404
|
const childTemplate = childLines.join('\n');
|
|
170
|
-
const childBody = compileTemplate(childTemplate, componentNames, actionNames, rpcNameMap
|
|
405
|
+
const childBody = compileTemplate(childTemplate, componentNames, actionNames, rpcNameMap, {
|
|
406
|
+
clientRouteRegistry: options.clientRouteRegistry,
|
|
407
|
+
});
|
|
171
408
|
// Wrap in an IIFE that returns the children HTML
|
|
172
409
|
const childrenExpr = `(function() { ${childBody}; return __html; })()`;
|
|
173
|
-
out.push(
|
|
410
|
+
out.push(buildAppendStatement(`${openResult.funcName}({ ${openResult.propsStr}${openResult.propsStr ? ', ' : ''}children: ${childrenExpr} }, __esc)`, emitCall));
|
|
174
411
|
continue;
|
|
175
412
|
}
|
|
176
413
|
}
|
|
@@ -187,7 +424,11 @@ export function compileTemplate(template, componentNames, actionNames, rpcNameMa
|
|
|
187
424
|
}
|
|
188
425
|
i += extraLines;
|
|
189
426
|
}
|
|
190
|
-
out.push(
|
|
427
|
+
out.push(...compileHtmlLineStatements(htmlLine, actionNames, rpcNameMap, options));
|
|
428
|
+
}
|
|
429
|
+
// For non-emit mode, add final join to produce __html
|
|
430
|
+
if (!emitCall) {
|
|
431
|
+
out.push('let __html = __parts.join(\'\');');
|
|
191
432
|
}
|
|
192
433
|
return out.join('\n');
|
|
193
434
|
}
|
|
@@ -262,9 +503,9 @@ function transformClientScriptBlock(block) {
|
|
|
262
503
|
const openTag = match[1];
|
|
263
504
|
const body = match[2];
|
|
264
505
|
const closeTag = match[3];
|
|
506
|
+
// TypeScript is preserved — wrangler's esbuild handles transpilation
|
|
265
507
|
if (!/\$\s*:/.test(body)) {
|
|
266
|
-
|
|
267
|
-
return `${openTag}${transpiled}${closeTag}`;
|
|
508
|
+
return `${openTag}${body}${closeTag}`;
|
|
268
509
|
}
|
|
269
510
|
const out = [];
|
|
270
511
|
const lines = body.split('\n');
|
|
@@ -360,8 +601,8 @@ function transformClientScriptBlock(block) {
|
|
|
360
601
|
break;
|
|
361
602
|
}
|
|
362
603
|
out.splice(insertAt, 0, 'const __k$ = window.__kuratchiReactive;');
|
|
363
|
-
|
|
364
|
-
return `${openTag}${
|
|
604
|
+
// TypeScript is preserved — wrangler's esbuild handles transpilation
|
|
605
|
+
return `${openTag}${out.join('\n')}${closeTag}`;
|
|
365
606
|
}
|
|
366
607
|
function braceDelta(line) {
|
|
367
608
|
let delta = 0;
|
|
@@ -441,14 +682,13 @@ function findMatching(src, openPos, openChar, closeChar) {
|
|
|
441
682
|
}
|
|
442
683
|
return -1;
|
|
443
684
|
}
|
|
444
|
-
function compileInlineBranchContent(content, actionNames, rpcNameMap) {
|
|
685
|
+
function compileInlineBranchContent(content, actionNames, rpcNameMap, options = {}) {
|
|
445
686
|
const c = content.trim();
|
|
446
687
|
if (!c)
|
|
447
688
|
return [];
|
|
448
|
-
|
|
449
|
-
return [compiled.replace(/\\n`;/, '`;')];
|
|
689
|
+
return compileHtmlLineStatements(c, actionNames, rpcNameMap, { ...options, appendNewline: false });
|
|
450
690
|
}
|
|
451
|
-
function tryCompileInlineIfElseLine(line, actionNames, rpcNameMap) {
|
|
691
|
+
function tryCompileInlineIfElseLine(line, actionNames, rpcNameMap, options = {}) {
|
|
452
692
|
if (!line.startsWith('if'))
|
|
453
693
|
return null;
|
|
454
694
|
const ifMatch = line.match(/^if\s*\(/);
|
|
@@ -489,9 +729,9 @@ function tryCompileInlineIfElseLine(line, actionNames, rpcNameMap) {
|
|
|
489
729
|
const elseContent = line.slice(elseOpen + 1, elseClose);
|
|
490
730
|
const out = [];
|
|
491
731
|
out.push(`if (${condition}) {`);
|
|
492
|
-
out.push(...compileInlineBranchContent(thenContent, actionNames, rpcNameMap));
|
|
732
|
+
out.push(...compileInlineBranchContent(thenContent, actionNames, rpcNameMap, options));
|
|
493
733
|
out.push(`} else {`);
|
|
494
|
-
out.push(...compileInlineBranchContent(elseContent, actionNames, rpcNameMap));
|
|
734
|
+
out.push(...compileInlineBranchContent(elseContent, actionNames, rpcNameMap, options));
|
|
495
735
|
out.push(`}`);
|
|
496
736
|
return out;
|
|
497
737
|
}
|
|
@@ -575,7 +815,7 @@ function parseComponentAttrs(attrsStr, actionNames) {
|
|
|
575
815
|
* Matches: <StatCard attr="literal" attr={expr} />
|
|
576
816
|
* Generates: __html += __c_stat_card({ attr: "literal", attr: (expr) }, __esc);
|
|
577
817
|
*/
|
|
578
|
-
function tryCompileComponentTag(line, componentNames, actionNames, rpcNameMap) {
|
|
818
|
+
function tryCompileComponentTag(line, componentNames, actionNames, rpcNameMap, options = {}) {
|
|
579
819
|
// Match self-closing tags: <TagName ...attrs... />
|
|
580
820
|
const selfCloseMatch = line.match(/^<([A-Z]\w*)\s*(.*?)\s*\/>\s*$/);
|
|
581
821
|
// Match open+close on same line with no content: <TagName ...attrs...></TagName>
|
|
@@ -590,7 +830,7 @@ function tryCompileComponentTag(line, componentNames, actionNames, rpcNameMap) {
|
|
|
590
830
|
return null;
|
|
591
831
|
const funcName = componentFuncName(fileName);
|
|
592
832
|
const propsStr = parseComponentAttrs(match[2].trim(), actionNames);
|
|
593
|
-
return
|
|
833
|
+
return buildAppendStatement(`${funcName}({ ${propsStr} }, __esc)`, options.emitCall);
|
|
594
834
|
}
|
|
595
835
|
if (inlinePairMatch) {
|
|
596
836
|
const tagName = inlinePairMatch[1];
|
|
@@ -601,9 +841,11 @@ function tryCompileComponentTag(line, componentNames, actionNames, rpcNameMap) {
|
|
|
601
841
|
const propsStr = parseComponentAttrs(inlinePairMatch[2].trim(), actionNames);
|
|
602
842
|
const innerContent = inlinePairMatch[3];
|
|
603
843
|
// Compile the inline content as a mini-template to handle {expr} interpolation
|
|
604
|
-
const childBody = compileTemplate(innerContent, componentNames, actionNames, rpcNameMap
|
|
844
|
+
const childBody = compileTemplate(innerContent, componentNames, actionNames, rpcNameMap, {
|
|
845
|
+
clientRouteRegistry: options.clientRouteRegistry,
|
|
846
|
+
});
|
|
605
847
|
const childrenExpr = `(function() { ${childBody}; return __html; })()`;
|
|
606
|
-
return
|
|
848
|
+
return buildAppendStatement(`${funcName}({ ${propsStr}${propsStr ? ', ' : ''}children: ${childrenExpr} }, __esc)`, options.emitCall);
|
|
607
849
|
}
|
|
608
850
|
return null;
|
|
609
851
|
}
|
|
@@ -642,17 +884,47 @@ function expandShorthands(line) {
|
|
|
642
884
|
});
|
|
643
885
|
return line;
|
|
644
886
|
}
|
|
887
|
+
function compileHtmlLineStatements(line, actionNames, rpcNameMap, options = {}) {
|
|
888
|
+
const markerRegex = /<!--__KURATCHI_FRAGMENT_(OPEN:([\s\S]*?)|CLOSE)-->/g;
|
|
889
|
+
const statements = [];
|
|
890
|
+
let cursor = 0;
|
|
891
|
+
let sawLiteral = false;
|
|
892
|
+
let match;
|
|
893
|
+
while ((match = markerRegex.exec(line)) !== null) {
|
|
894
|
+
const literal = line.slice(cursor, match.index);
|
|
895
|
+
if (literal) {
|
|
896
|
+
statements.push(compileHtmlSegment(literal, actionNames, rpcNameMap, { ...options, appendNewline: false }));
|
|
897
|
+
sawLiteral = true;
|
|
898
|
+
}
|
|
899
|
+
if (match[1].startsWith('OPEN:')) {
|
|
900
|
+
const encodedExpr = match[2] || '';
|
|
901
|
+
statements.push(`__pushFragment(${decodeURIComponent(encodedExpr)});`);
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
statements.push(`__popFragment();`);
|
|
905
|
+
}
|
|
906
|
+
cursor = match.index + match[0].length;
|
|
907
|
+
}
|
|
908
|
+
const tail = line.slice(cursor);
|
|
909
|
+
if (tail || sawLiteral || options.appendNewline !== false) {
|
|
910
|
+
const appendNewline = options.appendNewline !== false;
|
|
911
|
+
if (tail || appendNewline) {
|
|
912
|
+
statements.push(compileHtmlSegment(tail, actionNames, rpcNameMap, { ...options, appendNewline }));
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return statements.filter(Boolean);
|
|
916
|
+
}
|
|
645
917
|
/**
|
|
646
|
-
* Compile a single HTML
|
|
918
|
+
* Compile a single HTML segment, replacing {expr} with escaped output,
|
|
647
919
|
* {@html expr} with sanitized HTML, and {@raw expr} with raw output.
|
|
648
920
|
* Handles attribute values like value={x}.
|
|
649
921
|
*/
|
|
650
|
-
function
|
|
922
|
+
function compileHtmlSegment(line, actionNames, rpcNameMap, options = {}) {
|
|
651
923
|
// Expand shorthand syntax before main compilation
|
|
652
924
|
line = expandShorthands(line);
|
|
653
925
|
let result = '';
|
|
654
926
|
let pos = 0;
|
|
655
|
-
let
|
|
927
|
+
let pendingActionHiddenInput = null;
|
|
656
928
|
while (pos < line.length) {
|
|
657
929
|
const braceIdx = findNextTemplateBrace(line, pos);
|
|
658
930
|
if (braceIdx === -1) {
|
|
@@ -671,19 +943,16 @@ function compileHtmlLine(line, actionNames, rpcNameMap) {
|
|
|
671
943
|
if (inner.startsWith('@html ')) {
|
|
672
944
|
const expr = inner.slice(6).trim();
|
|
673
945
|
result += `\${__sanitizeHtml(${expr})}`;
|
|
674
|
-
hasExpr = true;
|
|
675
946
|
}
|
|
676
947
|
else if (inner.startsWith('@raw ')) {
|
|
677
948
|
// Unsafe raw HTML: {@raw expr}
|
|
678
949
|
const expr = inner.slice(5).trim();
|
|
679
950
|
result += `\${__rawHtml(${expr})}`;
|
|
680
|
-
hasExpr = true;
|
|
681
951
|
}
|
|
682
952
|
else if (inner.startsWith('=html ')) {
|
|
683
953
|
// Legacy alias for raw HTML: {=html expr}
|
|
684
954
|
const expr = inner.slice(6).trim();
|
|
685
955
|
result += `\${__rawHtml(${expr})}`;
|
|
686
|
-
hasExpr = true;
|
|
687
956
|
}
|
|
688
957
|
else {
|
|
689
958
|
// Check if this is an attribute value: attr={expr}
|
|
@@ -697,7 +966,6 @@ function compileHtmlLine(line, actionNames, rpcNameMap) {
|
|
|
697
966
|
if (attrName === 'data-dialog-data') {
|
|
698
967
|
// data-dialog-data={expr} → data-dialog-data="JSON.stringify(expr)" (HTML-escaped)
|
|
699
968
|
result += `"\${__esc(JSON.stringify(${inner}))}"`;
|
|
700
|
-
hasExpr = true;
|
|
701
969
|
pos = closeIdx + 1;
|
|
702
970
|
continue;
|
|
703
971
|
}
|
|
@@ -719,7 +987,6 @@ function compileHtmlLine(line, actionNames, rpcNameMap) {
|
|
|
719
987
|
else {
|
|
720
988
|
result += ` data-refresh="\${__esc(${inner})}"`;
|
|
721
989
|
}
|
|
722
|
-
hasExpr = true;
|
|
723
990
|
pos = closeIdx + 1;
|
|
724
991
|
continue;
|
|
725
992
|
}
|
|
@@ -737,7 +1004,6 @@ function compileHtmlLine(line, actionNames, rpcNameMap) {
|
|
|
737
1004
|
else {
|
|
738
1005
|
result += ` ${attrName}="${escapeLiteral(inner)}"`;
|
|
739
1006
|
}
|
|
740
|
-
hasExpr = true;
|
|
741
1007
|
pos = closeIdx + 1;
|
|
742
1008
|
continue;
|
|
743
1009
|
}
|
|
@@ -759,12 +1025,11 @@ function compileHtmlLine(line, actionNames, rpcNameMap) {
|
|
|
759
1025
|
// Non-call expression mode (e.g., data-get={someUrl})
|
|
760
1026
|
result += ` ${attrName}="\${__esc(${inner})}"`;
|
|
761
1027
|
}
|
|
762
|
-
hasExpr = true;
|
|
763
1028
|
pos = closeIdx + 1;
|
|
764
1029
|
continue;
|
|
765
1030
|
}
|
|
766
1031
|
else if (attrName === 'data-poll') {
|
|
767
|
-
// data-poll={fn(args)} → data-poll="fnName" data-poll-args="[serialized]" data-poll-id="
|
|
1032
|
+
// data-poll={fn(args)} → data-poll="fnName" data-poll-args="[serialized]" data-poll-id="signed-id"
|
|
768
1033
|
const pollCallMatch = inner.match(/^(\w+)\((.*)\)$/);
|
|
769
1034
|
if (pollCallMatch) {
|
|
770
1035
|
const fnName = pollCallMatch[1];
|
|
@@ -772,19 +1037,18 @@ function compileHtmlLine(line, actionNames, rpcNameMap) {
|
|
|
772
1037
|
const argsExpr = pollCallMatch[2].trim();
|
|
773
1038
|
// Remove the trailing "data-poll=" we already appended
|
|
774
1039
|
result = result.replace(/\s*data-poll=$/, '');
|
|
775
|
-
// Emit data-poll, data-poll-args, and
|
|
1040
|
+
// Emit data-poll, data-poll-args, and signed data-poll-id (signed at runtime for security)
|
|
776
1041
|
result += ` data-poll="${rpcName}"`;
|
|
777
1042
|
if (argsExpr) {
|
|
778
1043
|
result += ` data-poll-args="\${__esc(JSON.stringify([${argsExpr}]))}"`;
|
|
779
|
-
//
|
|
780
|
-
result += ` data-poll-id="\${
|
|
1044
|
+
// Sign the fragment ID at runtime with __signFragment(fragmentId, routePath)
|
|
1045
|
+
result += ` data-poll-id="\${__signFragment('__poll_' + String(${argsExpr}).replace(/[^a-zA-Z0-9]/g, '_'))}"`;
|
|
781
1046
|
}
|
|
782
1047
|
else {
|
|
783
|
-
// No args -
|
|
784
|
-
result += ` data-poll-id="__poll_${rpcName}"`;
|
|
1048
|
+
// No args - sign with function name as base ID
|
|
1049
|
+
result += ` data-poll-id="\${__signFragment('__poll_${rpcName}')}"`;
|
|
785
1050
|
}
|
|
786
1051
|
}
|
|
787
|
-
hasExpr = true;
|
|
788
1052
|
pos = closeIdx + 1;
|
|
789
1053
|
continue;
|
|
790
1054
|
}
|
|
@@ -798,25 +1062,17 @@ function compileHtmlLine(line, actionNames, rpcNameMap) {
|
|
|
798
1062
|
if (!isServerAction) {
|
|
799
1063
|
throw new Error(`Invalid action expression: "${inner}". Use action={myActionFn} for server actions.`);
|
|
800
1064
|
}
|
|
801
|
-
// Remove trailing `action=` from output and inject _action hidden field.
|
|
1065
|
+
// Remove trailing `action=` from output and inject _action hidden field + CSRF token.
|
|
802
1066
|
result = result.replace(/\s*action=$/, '');
|
|
1067
|
+
const actionValue = actionNames === undefined
|
|
1068
|
+
? `\${__esc(${inner})}`
|
|
1069
|
+
: inner;
|
|
1070
|
+
// Inject both _action and _csrf hidden fields for server action forms
|
|
1071
|
+
pendingActionHiddenInput = `\\n<input type="hidden" name="_action" value="${actionValue}">\\n<input type="hidden" name="_csrf" value="\${__getCsrfToken()}">`;
|
|
803
1072
|
pos = closeIdx + 1;
|
|
804
|
-
const tagEnd = line.indexOf('>', pos);
|
|
805
|
-
if (tagEnd !== -1) {
|
|
806
|
-
result += escapeLiteral(line.slice(pos, tagEnd + 1));
|
|
807
|
-
// In a route context, inner is the literal action function name (a string key).
|
|
808
|
-
// In a component context (actionNames === undefined), inner is a prop name whose
|
|
809
|
-
// value at runtime is the action name string — emit it as a dynamic expression.
|
|
810
|
-
const actionValue = actionNames === undefined
|
|
811
|
-
? `\${__esc(${inner})}`
|
|
812
|
-
: inner;
|
|
813
|
-
result += `\\n<input type="hidden" name="_action" value="${actionValue}">`;
|
|
814
|
-
pos = tagEnd + 1;
|
|
815
|
-
}
|
|
816
|
-
hasExpr = true;
|
|
817
1073
|
continue;
|
|
818
1074
|
}
|
|
819
|
-
else if (/^on[A-Za-z]
|
|
1075
|
+
else if (/^on[A-Za-z]+$/i.test(attrName)) {
|
|
820
1076
|
// onClick/onChange/...={expr} — check if it's a server action or plain client JS
|
|
821
1077
|
const eventName = attrName.slice(2).toLowerCase();
|
|
822
1078
|
const actionCallMatch = inner.match(/^(\w+)\((.*)\)$/);
|
|
@@ -831,12 +1087,25 @@ function compileHtmlLine(line, actionNames, rpcNameMap) {
|
|
|
831
1087
|
result += ` data-action="${fnName}" data-args="\${__esc(JSON.stringify([${argsExpr}]))}" data-action-event="${eventName}"`;
|
|
832
1088
|
}
|
|
833
1089
|
else {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1090
|
+
const clientRegistration = options.clientRouteRegistry?.registerEventHandler(eventName, inner) ?? null;
|
|
1091
|
+
if (clientRegistration) {
|
|
1092
|
+
result = result.replace(new RegExp(`\\s*${attrName}=$`), '');
|
|
1093
|
+
result += ` data-client-route="${clientRegistration.routeId}" data-client-handler="${clientRegistration.handlerId}" data-client-event="${eventName}"`;
|
|
1094
|
+
if (clientRegistration.argsExpr) {
|
|
1095
|
+
result += ` data-client-args="\${__esc(JSON.stringify([${clientRegistration.argsExpr}]))}"`;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
else if (options.clientRouteRegistry?.hasBindingReference(inner)) {
|
|
1099
|
+
throw new Error(`Unsupported client handler expression: "${inner}". ` +
|
|
1100
|
+
`Top-level $client/$shared event handlers must be a direct function reference or call, like onClick={openDialog()} or onClick={helpers.openDialog()}.`);
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
// Plain client-side event handler: onClick={myFn()}
|
|
1104
|
+
// Emit as native inline handler (lowercased event attribute).
|
|
1105
|
+
result = result.replace(new RegExp(`\\s*${attrName}=$`), ` on${eventName}=`);
|
|
1106
|
+
result += `"${escapeLiteral(inner)}"`;
|
|
1107
|
+
}
|
|
838
1108
|
}
|
|
839
|
-
hasExpr = true;
|
|
840
1109
|
pos = closeIdx + 1;
|
|
841
1110
|
continue;
|
|
842
1111
|
}
|
|
@@ -852,17 +1121,14 @@ function compileHtmlLine(line, actionNames, rpcNameMap) {
|
|
|
852
1121
|
else {
|
|
853
1122
|
result += `\${__esc(${inner})}`;
|
|
854
1123
|
}
|
|
855
|
-
hasExpr = true;
|
|
856
1124
|
}
|
|
857
1125
|
pos = closeIdx + 1;
|
|
858
1126
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
}
|
|
863
|
-
else {
|
|
864
|
-
return `__html += \`${result}\\n\`;`;
|
|
1127
|
+
if (pendingActionHiddenInput && result.includes('>')) {
|
|
1128
|
+
const gtIndex = result.indexOf('>');
|
|
1129
|
+
result = result.slice(0, gtIndex + 1) + pendingActionHiddenInput + result.slice(gtIndex + 1);
|
|
865
1130
|
}
|
|
1131
|
+
return buildAppendStatement(`\`${result}${options.appendNewline === false ? '' : '\\n'}\``, options.emitCall);
|
|
866
1132
|
}
|
|
867
1133
|
/** Escape characters that would break a JS template literal. */
|
|
868
1134
|
function escapeLiteral(text) {
|
|
@@ -942,4 +1208,4 @@ export function generateRenderFunction(template) {
|
|
|
942
1208
|
${body}
|
|
943
1209
|
}`;
|
|
944
1210
|
}
|
|
945
|
-
|
|
1211
|
+
// TypeScript transpilation removed — wrangler's esbuild handles it
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface WorkerClassExportEntry {
|
|
2
|
+
className: string;
|
|
3
|
+
exportKind: 'named' | 'default';
|
|
4
|
+
file: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveRuntimeImportPath(projectDir: string): string | null;
|
|
7
|
+
export declare function toWorkerImportPath(projectDir: string, outDir: string, filePath: string): string;
|
|
8
|
+
export declare function buildWorkerEntrypointSource(opts: {
|
|
9
|
+
projectDir: string;
|
|
10
|
+
outDir: string;
|
|
11
|
+
doClassNames: string[];
|
|
12
|
+
workerClassEntries: WorkerClassExportEntry[];
|
|
13
|
+
}): string;
|