@barefootjs/hono 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.
Files changed (81) hide show
  1. package/dist/adapter/hono-adapter.d.ts +141 -0
  2. package/dist/adapter/hono-adapter.d.ts.map +1 -0
  3. package/dist/adapter/index.d.ts +6 -0
  4. package/dist/adapter/index.d.ts.map +1 -0
  5. package/dist/adapter/index.js +632 -0
  6. package/dist/app.d.ts +131 -0
  7. package/dist/app.d.ts.map +1 -0
  8. package/dist/app.js +139 -0
  9. package/dist/async.d.ts +15 -0
  10. package/dist/async.d.ts.map +1 -0
  11. package/dist/async.js +12 -0
  12. package/dist/build.d.ts +65 -0
  13. package/dist/build.d.ts.map +1 -0
  14. package/dist/build.js +785 -0
  15. package/dist/client-shim.d.ts +59 -0
  16. package/dist/client-shim.d.ts.map +1 -0
  17. package/dist/client-shim.js +90 -0
  18. package/dist/dev-worker.d.ts +25 -0
  19. package/dist/dev-worker.d.ts.map +1 -0
  20. package/dist/dev-worker.js +65 -0
  21. package/dist/dev.d.ts +36 -0
  22. package/dist/dev.d.ts.map +1 -0
  23. package/dist/dev.js +418 -0
  24. package/dist/dialog-context.d.ts +13 -0
  25. package/dist/dialog-context.d.ts.map +1 -0
  26. package/dist/dialog-context.js +10 -0
  27. package/dist/index.d.ts +13 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +632 -0
  30. package/dist/jsx/jsx-dev-runtime/index.d.ts +9 -0
  31. package/dist/jsx/jsx-dev-runtime/index.d.ts.map +1 -0
  32. package/dist/jsx/jsx-dev-runtime/index.js +6 -0
  33. package/dist/jsx/jsx-runtime/index.d.ts +32 -0
  34. package/dist/jsx/jsx-runtime/index.d.ts.map +1 -0
  35. package/dist/jsx/jsx-runtime/index.js +10 -0
  36. package/dist/portal-ssr.d.ts +22 -0
  37. package/dist/portal-ssr.d.ts.map +1 -0
  38. package/dist/portal-ssr.js +73 -0
  39. package/dist/portals.d.ts +26 -0
  40. package/dist/portals.d.ts.map +1 -0
  41. package/dist/portals.js +41 -0
  42. package/dist/preload.d.ts +56 -0
  43. package/dist/preload.d.ts.map +1 -0
  44. package/dist/preload.js +51 -0
  45. package/dist/scripts.d.ts +80 -0
  46. package/dist/scripts.d.ts.map +1 -0
  47. package/dist/scripts.js +198 -0
  48. package/dist/test-render.d.ts +28 -0
  49. package/dist/test-render.d.ts.map +1 -0
  50. package/dist/utils.d.ts +16 -0
  51. package/dist/utils.d.ts.map +1 -0
  52. package/dist/utils.js +16 -0
  53. package/package.json +116 -0
  54. package/src/__tests__/async.test.tsx +106 -0
  55. package/src/__tests__/bfscripts-entry-roots.test.tsx +135 -0
  56. package/src/__tests__/build.test.ts +299 -0
  57. package/src/__tests__/dev.test.tsx +123 -0
  58. package/src/__tests__/hydration-props-type.test.ts +141 -0
  59. package/src/__tests__/manifest-scripts.test.ts +87 -0
  60. package/src/__tests__/scaffold.test.ts +209 -0
  61. package/src/__tests__/ssr-context-bridge.test.ts +110 -0
  62. package/src/__tests__/string-literal-css-var-prop.test.ts +84 -0
  63. package/src/__tests__/stub-deps-scripts.test.ts +183 -0
  64. package/src/adapter/hono-adapter.ts +1114 -0
  65. package/src/adapter/index.ts +6 -0
  66. package/src/app.ts +220 -0
  67. package/src/async.tsx +55 -0
  68. package/src/build.ts +230 -0
  69. package/src/client-shim.ts +164 -0
  70. package/src/dev-worker.ts +93 -0
  71. package/src/dev.tsx +146 -0
  72. package/src/dialog-context.tsx +44 -0
  73. package/src/index.ts +26 -0
  74. package/src/jsx/jsx-dev-runtime/index.ts +9 -0
  75. package/src/jsx/jsx-runtime/index.ts +40 -0
  76. package/src/portal-ssr.tsx +92 -0
  77. package/src/portals.tsx +98 -0
  78. package/src/preload.tsx +166 -0
  79. package/src/scripts.tsx +220 -0
  80. package/src/test-render.ts +143 -0
  81. package/src/utils.ts +26 -0
package/dist/build.js ADDED
@@ -0,0 +1,785 @@
1
+ // src/adapter/hono-adapter.ts
2
+ import {
3
+ JsxAdapter,
4
+ isBooleanAttr,
5
+ rewriteImportsForTemplate,
6
+ emitIRNode,
7
+ emitAttrValue,
8
+ buildLoopChainExpr
9
+ } from "@barefootjs/jsx";
10
+ import { BF_SCOPE, BF_HOST, BF_AT, BF_ROOT, BF_PROPS } from "@barefootjs/shared";
11
+ function applyHonoLoopChain(loop) {
12
+ return buildLoopChainExpr({
13
+ base: loop.array,
14
+ sortComparator: loop.sortComparator,
15
+ filterPredicate: loop.filterPredicate,
16
+ chainOrder: loop.chainOrder
17
+ });
18
+ }
19
+
20
+ class HonoAdapter extends JsxAdapter {
21
+ name = "hono";
22
+ extension = ".tsx";
23
+ clientShimSource = "@barefootjs/hono/client-shim";
24
+ acceptsTemplateCall = () => true;
25
+ jsxConfig = { preserveTypes: true };
26
+ options;
27
+ isClientComponent = false;
28
+ hasClientInteractivity = false;
29
+ currentComponentHasProps = false;
30
+ rewriteRelativeImport;
31
+ loopKeyStack = [];
32
+ constructor(options = {}) {
33
+ super();
34
+ this.options = {
35
+ clientJsBasePath: options.clientJsBasePath ?? "/static/components/",
36
+ barefootJsPath: options.barefootJsPath ?? "/static/components/barefoot.js",
37
+ clientJsFilename: options.clientJsFilename
38
+ };
39
+ if (options.name)
40
+ this.name = options.name;
41
+ }
42
+ generate(ir, options) {
43
+ this.componentName = ir.metadata.componentName;
44
+ this.isClientComponent = ir.metadata.isClientComponent;
45
+ this.rewriteRelativeImport = options?.rewriteRelativeImport;
46
+ const component = this.generateComponent(ir);
47
+ const types = this.generateTypes(ir, component);
48
+ const componentCode = [types, component].filter(Boolean).join(`
49
+ `);
50
+ const imports = this.generateImports(ir, componentCode);
51
+ const moduleConstants = this.generateModuleLevelContextBindings(ir);
52
+ const defaultExport = ir.metadata.hasDefaultExport ? `
53
+ export default ${this.componentName}` : "";
54
+ const sections = {
55
+ imports,
56
+ types: types || "",
57
+ component,
58
+ defaultExport,
59
+ moduleConstants
60
+ };
61
+ const template = [imports, moduleConstants, types, component].filter(Boolean).join(`
62
+
63
+ `) + defaultExport;
64
+ const result = {
65
+ template,
66
+ sections,
67
+ types: types || undefined,
68
+ extension: this.extension
69
+ };
70
+ this.rewriteRelativeImport = undefined;
71
+ return result;
72
+ }
73
+ generateModuleLevelContextBindings(ir) {
74
+ const lines = [];
75
+ for (const c of ir.metadata.localConstants) {
76
+ if (!c.isModule)
77
+ continue;
78
+ if (c.isExported)
79
+ continue;
80
+ if (c.systemConstructKind !== "createContext")
81
+ continue;
82
+ if (!c.value)
83
+ continue;
84
+ const keyword = c.declarationKind ?? "const";
85
+ const value = this.jsxConfig.preserveTypes ? c.typedValue ?? c.value : c.value;
86
+ lines.push(`${keyword} ${c.name} = ${value}`);
87
+ }
88
+ return lines.join(`
89
+ `);
90
+ }
91
+ generateImports(ir, componentCode) {
92
+ const lines = [];
93
+ const utilImports = [];
94
+ for (const util of ["bfComment", "bfText", "bfTextEnd"]) {
95
+ if (new RegExp(`\\b${util}\\b`).test(componentCode)) {
96
+ utilImports.push(util);
97
+ }
98
+ }
99
+ if (utilImports.length > 0) {
100
+ lines.push(`import { ${utilImports.join(", ")} } from '@barefootjs/hono/utils'`);
101
+ }
102
+ if (componentCode.includes("<Suspense")) {
103
+ lines.push(`import { Suspense } from 'hono/jsx/streaming'`);
104
+ }
105
+ const templateImports = rewriteImportsForTemplate(ir.metadata.templateImports, this.clientShimSource, this.rewriteRelativeImport);
106
+ for (const imp of templateImports) {
107
+ if (imp.specifiers.length === 0) {
108
+ if (!imp.isTypeOnly) {
109
+ lines.push(`import '${imp.source}'`);
110
+ }
111
+ continue;
112
+ }
113
+ if (imp.isTypeOnly) {
114
+ lines.push(`import type ${this.formatImportSpecifiers(imp.specifiers)} from '${imp.source}'`);
115
+ } else {
116
+ lines.push(`import ${this.formatImportSpecifiers(imp.specifiers)} from '${imp.source}'`);
117
+ }
118
+ }
119
+ if (/\bprovideContextSSR\(/.test(componentCode)) {
120
+ lines.push(`import { provideContextSSR } from '@barefootjs/hono/client-shim'`);
121
+ }
122
+ return lines.join(`
123
+ `);
124
+ }
125
+ generateTypes(ir, componentBody) {
126
+ const lines = [];
127
+ if (componentBody && ir.metadata.typeDefinitions.length > 0) {
128
+ const propsTypeName2 = this.getPropsTypeName(ir);
129
+ const seedText = [
130
+ componentBody,
131
+ propsTypeName2 && !ir.metadata.propsObjectName ? propsTypeName2 : "",
132
+ ...ir.metadata.namedExports.filter((block) => block.source === null).flatMap((block) => block.specifiers.map((s) => s.name))
133
+ ].filter(Boolean).join(`
134
+ `);
135
+ const included = new Set;
136
+ for (const typeDef of ir.metadata.typeDefinitions) {
137
+ if (new RegExp(`\\b${typeDef.name}\\b`).test(seedText)) {
138
+ included.add(typeDef.name);
139
+ }
140
+ }
141
+ let changed = true;
142
+ while (changed) {
143
+ changed = false;
144
+ for (const typeDef of ir.metadata.typeDefinitions) {
145
+ if (included.has(typeDef.name))
146
+ continue;
147
+ for (const name of included) {
148
+ const includedDef = ir.metadata.typeDefinitions.find((t) => t.name === name);
149
+ if (includedDef && new RegExp(`\\b${typeDef.name}\\b`).test(includedDef.definition)) {
150
+ included.add(typeDef.name);
151
+ changed = true;
152
+ break;
153
+ }
154
+ }
155
+ }
156
+ }
157
+ for (const typeDef of ir.metadata.typeDefinitions) {
158
+ if (included.has(typeDef.name))
159
+ lines.push(typeDef.definition);
160
+ }
161
+ } else {
162
+ for (const typeDef of ir.metadata.typeDefinitions) {
163
+ lines.push(typeDef.definition);
164
+ }
165
+ }
166
+ const propsTypeName = this.getPropsTypeName(ir);
167
+ if (propsTypeName && !ir.metadata.propsObjectName) {
168
+ lines.push("");
169
+ lines.push(`type ${this.componentName}PropsWithHydration = ${propsTypeName} & {`);
170
+ lines.push(" __instanceId?: string");
171
+ lines.push(" __bfScope?: string");
172
+ lines.push(" __bfChild?: boolean");
173
+ lines.push(" __bfParentProps?: string");
174
+ lines.push(" __bfParent?: string");
175
+ lines.push(" __bfMount?: string");
176
+ lines.push(' "data-key"?: string | number');
177
+ lines.push("}");
178
+ }
179
+ return lines.length > 0 ? lines.join(`
180
+ `) : null;
181
+ }
182
+ getPropsTypeName(ir) {
183
+ if (ir.metadata.propsType?.raw) {
184
+ return ir.metadata.propsType.raw;
185
+ }
186
+ return null;
187
+ }
188
+ generateComponent(ir) {
189
+ const name = ir.metadata.componentName;
190
+ const propsTypeName = this.getPropsTypeName(ir);
191
+ const hasReactivePrimitives = ir.metadata.signals.length > 0 || ir.metadata.memos.length > 0 || ir.metadata.effects.length > 0 || ir.metadata.onMounts.length > 0;
192
+ if (hasReactivePrimitives && !ir.metadata.isClientComponent) {
193
+ throw new Error(`Component "${name}" has reactive primitives (signals, memos, effects, or onMounts) ` + `but is not marked as a client component. Add "use client" directive at the top of the file.`);
194
+ }
195
+ const needsClientInit = ir.metadata.clientAnalysis?.needsInit ?? false;
196
+ const hasClientInteractivity = ir.metadata.isClientComponent || needsClientInit;
197
+ this.hasClientInteractivity = hasClientInteractivity;
198
+ const propsObjectName = ir.metadata.propsObjectName;
199
+ let fullPropsDestructure;
200
+ let typeAnnotation;
201
+ let propsExtraction = null;
202
+ const HYDRATION_PROPS_TYPE = '{ __instanceId?: string; __bfScope?: string; __bfChild?: boolean; __bfParentProps?: string; __bfParent?: string; __bfMount?: string; "data-key"?: string | number }';
203
+ if (propsObjectName) {
204
+ fullPropsDestructure = `__allProps`;
205
+ typeAnnotation = propsTypeName ? `: ${propsTypeName} & ${HYDRATION_PROPS_TYPE}` : `: Record<string, unknown> & ${HYDRATION_PROPS_TYPE}`;
206
+ } else {
207
+ fullPropsDestructure = "";
208
+ typeAnnotation = propsTypeName ? `: ${name}PropsWithHydration` : `: ${HYDRATION_PROPS_TYPE}`;
209
+ }
210
+ const clientUsedProps = new Set(ir.metadata.clientAnalysis?.usedProps ?? []);
211
+ const needsInit = ir.metadata.clientAnalysis?.needsInit ?? false;
212
+ const propsToSerialize = ir.metadata.propsParams.filter((p) => {
213
+ return !p.name.startsWith("on") && !p.name.startsWith("__") && clientUsedProps.has(p.name);
214
+ });
215
+ const hasPropsToSerialize = propsToSerialize.length > 0 && hasClientInteractivity && needsInit;
216
+ const isIfStatement = ir.root.type === "if-statement";
217
+ const isRootComponent = ir.root.type === "component";
218
+ this.currentComponentHasProps = hasPropsToSerialize || hasClientInteractivity && isRootComponent;
219
+ let jsxBody = isIfStatement ? "" : this.renderNode(ir.root, {
220
+ isRootOfClientComponent: hasClientInteractivity && isRootComponent
221
+ });
222
+ if (!isIfStatement && hasClientInteractivity && isRootComponent) {
223
+ jsxBody = this.wrapWithScopeComment(jsxBody);
224
+ }
225
+ const ifCode = isIfStatement ? this.renderIfStatement(ir.root, { isRootOfClientComponent: true }) : "";
226
+ const fullBodyText = jsxBody + `
227
+ ` + ifCode;
228
+ const signalInits = this.generateSignalInitializers(ir, fullBodyText);
229
+ const scopeIdLine = hasClientInteractivity ? `__instanceId` : `__bfScope || __instanceId`;
230
+ const bodyRefText = [
231
+ fullBodyText,
232
+ signalInits,
233
+ scopeIdLine,
234
+ hasPropsToSerialize || hasClientInteractivity && isRootComponent ? "__bfParentProps" : ""
235
+ ].join(`
236
+ `);
237
+ const bfScopeAlias = /\b__bfScope\b/.test(bodyRefText) ? "__bfScope" : "__bfScope: _bfScope";
238
+ const bfChildAlias = /\b__bfChild\b/.test(bodyRefText) ? "__bfChild" : "__bfChild: _bfChild";
239
+ const bfParentPropsAlias = /\b__bfParentProps\b/.test(bodyRefText) ? "__bfParentProps" : "__bfParentProps: _bfParentProps";
240
+ const bfParentAlias = /\b__bfParent\b/.test(bodyRefText) ? "__bfParent" : "__bfParent: _bfParent";
241
+ const bfMountAlias = /\b__bfMount\b/.test(bodyRefText) ? "__bfMount" : "__bfMount: _bfMount";
242
+ const dataKeyAlias = /\b__dataKey\b/.test(bodyRefText) ? '"data-key": __dataKey' : '"data-key": _dataKey';
243
+ if (propsObjectName) {
244
+ propsExtraction = ` const { __instanceId, ${bfScopeAlias}, ${bfChildAlias}, ${bfParentPropsAlias}, ${bfParentAlias}, ${bfMountAlias}, ${dataKeyAlias}, ...${propsObjectName} } = __allProps`;
245
+ } else {
246
+ const hydrationProps = `__instanceId, ${bfScopeAlias}, ${bfChildAlias}, ${bfParentPropsAlias}, ${bfParentAlias}, ${bfMountAlias}, ${dataKeyAlias}`;
247
+ const parts = [];
248
+ const propsParams = ir.metadata.propsParams.map((p) => {
249
+ const paramName = p.name === "class" ? "className" : p.name;
250
+ return p.defaultValue ? `${paramName} = ${p.defaultValue}` : paramName;
251
+ }).join(", ");
252
+ if (propsParams) {
253
+ parts.push(propsParams);
254
+ }
255
+ parts.push(hydrationProps);
256
+ const restPropsName = ir.metadata.restPropsName;
257
+ if (restPropsName) {
258
+ parts.push(`...${restPropsName}`);
259
+ }
260
+ fullPropsDestructure = `{ ${parts.join(", ")} }`;
261
+ }
262
+ const lines = [];
263
+ const exportPrefix = ir.metadata.isExported === false ? "" : "export ";
264
+ lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}) {`);
265
+ if (propsExtraction) {
266
+ lines.push(propsExtraction);
267
+ }
268
+ if (hasClientInteractivity) {
269
+ lines.push(` const __scopeId = __instanceId || \`${name}_\${Math.random().toString(36).slice(2, 8)}\``);
270
+ } else {
271
+ lines.push(` const __scopeId = __bfScope || __instanceId || \`${name}_\${Math.random().toString(36).slice(2, 8)}\``);
272
+ }
273
+ if (signalInits) {
274
+ lines.push(signalInits);
275
+ }
276
+ if (hasPropsToSerialize) {
277
+ lines.push("");
278
+ lines.push(` // Serialize props for client hydration`);
279
+ lines.push(` const __hydrateProps: Record<string, unknown> = {}`);
280
+ for (const p of propsToSerialize) {
281
+ const propAccess = propsObjectName ? `${propsObjectName}.${p.name}` : p.name;
282
+ lines.push(` if (typeof ${propAccess} !== 'function' && !(typeof ${propAccess} === 'object' && ${propAccess} !== null && 'isEscaped' in ${propAccess})) __hydrateProps['${p.name}'] = ${propAccess}`);
283
+ }
284
+ lines.push(` const __bfPropsJson = __bfParentProps || (Object.keys(__hydrateProps).length > 0 ? JSON.stringify(__hydrateProps) : undefined)`);
285
+ } else if (hasClientInteractivity && isRootComponent) {
286
+ lines.push("");
287
+ lines.push(` const __bfPropsJson = __bfParentProps`);
288
+ }
289
+ lines.push("");
290
+ if (isIfStatement) {
291
+ lines.push(ifCode);
292
+ lines.push(`}`);
293
+ return lines.join(`
294
+ `);
295
+ }
296
+ lines.push(` return (`);
297
+ lines.push(` ${jsxBody}`);
298
+ lines.push(` )`);
299
+ lines.push(`}`);
300
+ return lines.join(`
301
+ `);
302
+ }
303
+ renderNode(node, ctx) {
304
+ return emitIRNode(node, this, ctx ?? {});
305
+ }
306
+ emitElement(node, ctx, _emit) {
307
+ return this.renderElement(node, ctx);
308
+ }
309
+ emitText(node) {
310
+ return this.renderText(node);
311
+ }
312
+ emitExpression(node) {
313
+ return this.renderExpression(node);
314
+ }
315
+ emitConditional(node, _ctx, _emit) {
316
+ return this.renderConditional(node);
317
+ }
318
+ emitLoop(node, _ctx, _emit) {
319
+ return this.renderLoop(node);
320
+ }
321
+ emitComponent(node, ctx, _emit) {
322
+ return this.renderComponent(node, ctx);
323
+ }
324
+ emitFragment(node, _ctx, _emit) {
325
+ return this.renderFragment(node);
326
+ }
327
+ emitSlot(_node) {
328
+ return "{children}";
329
+ }
330
+ emitIfStatement(_node, _ctx, _emit) {
331
+ return "";
332
+ }
333
+ emitProvider(node, _ctx, _emit) {
334
+ const children = this.renderChildren(node.children);
335
+ const valueExpr = (() => {
336
+ const v = node.valueProp.value;
337
+ switch (v.kind) {
338
+ case "literal":
339
+ return JSON.stringify(v.value);
340
+ case "expression":
341
+ case "spread":
342
+ return v.expr;
343
+ case "template":
344
+ return this.renderTemplateLiteralParts(v.parts);
345
+ case "boolean-attr":
346
+ case "boolean-shorthand":
347
+ return "true";
348
+ case "jsx-children":
349
+ return "undefined";
350
+ }
351
+ })();
352
+ return `<>{provideContextSSR(${node.contextName}, ${valueExpr}, <>${children}</>)}</>`;
353
+ }
354
+ emitAsync(node, _ctx, _emit) {
355
+ return this.renderAsync(node);
356
+ }
357
+ renderElement(element, ctx) {
358
+ const tag = element.tag;
359
+ const attrs = this.renderAttributes(element);
360
+ const children = this.renderChildren(element.children);
361
+ let hydrationAttrs = "";
362
+ if (element.needsScope) {
363
+ hydrationAttrs += ` ${BF_SCOPE}={__scopeId}`;
364
+ hydrationAttrs += ` {...(__bfParent ? { "${BF_HOST}": __bfParent } : {})}`;
365
+ hydrationAttrs += ` {...(__bfMount ? { "${BF_AT}": __bfMount } : {})}`;
366
+ hydrationAttrs += ` {...(!__bfChild ? { "${BF_ROOT}": "" } : {})}`;
367
+ if (this.currentComponentHasProps) {
368
+ hydrationAttrs += ` {...(!__bfChild && __bfPropsJson ? { "${BF_PROPS}": __bfPropsJson } : {})}`;
369
+ }
370
+ hydrationAttrs += ' {...(__dataKey !== undefined ? { "data-key": __dataKey } : {})}';
371
+ }
372
+ if (ctx?.isLoopItemRoot && this.loopKeyStack.length > 0) {
373
+ const loop = this.loopKeyStack[this.loopKeyStack.length - 1];
374
+ if (loop.key) {
375
+ const keyAttrName = this.loopKeyStack.length === 1 ? "data-key" : `data-key-${this.loopKeyStack.length - 1}`;
376
+ hydrationAttrs += ` ${keyAttrName}={String(${loop.key})}`;
377
+ }
378
+ }
379
+ if (element.slotId) {
380
+ hydrationAttrs += ` bf="${element.slotId}"`;
381
+ }
382
+ if (children) {
383
+ return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
384
+ } else {
385
+ return `<${tag}${attrs}${hydrationAttrs} />`;
386
+ }
387
+ }
388
+ renderText(text) {
389
+ return text.value;
390
+ }
391
+ renderExpression(expr) {
392
+ if (expr.expr === "null" || expr.expr === "undefined") {
393
+ return "null";
394
+ }
395
+ if (expr.clientOnly && expr.slotId) {
396
+ return `{bfComment("client:${expr.slotId}")}`;
397
+ }
398
+ if (expr.slotId) {
399
+ return `{bfText("${expr.slotId}")}{${expr.expr}}{bfTextEnd()}`;
400
+ }
401
+ return `{${expr.expr}}`;
402
+ }
403
+ renderConditional(cond) {
404
+ if (cond.clientOnly && cond.slotId) {
405
+ return `{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}`;
406
+ }
407
+ const whenTrue = this.renderNodeRaw(cond.whenTrue);
408
+ let whenFalse = this.renderNodeRaw(cond.whenFalse);
409
+ if (!whenFalse || whenFalse === "" || whenFalse === "null") {
410
+ whenFalse = "null";
411
+ }
412
+ if (cond.slotId) {
413
+ const trueWithMarker = this.wrapWithCondMarker(cond.whenTrue, whenTrue, cond.slotId);
414
+ const falseWithMarker = cond.whenFalse.type === "expression" && cond.whenFalse.expr === "null" ? `<>{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}</>` : this.wrapWithCondMarker(cond.whenFalse, whenFalse, cond.slotId);
415
+ return `{${cond.condition} ? ${trueWithMarker} : ${falseWithMarker}}`;
416
+ }
417
+ return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`;
418
+ }
419
+ wrapWithCondMarker(node, content, condId) {
420
+ if (node.type === "component") {
421
+ return `<>{bfComment("cond-start:${condId}")}${content}{bfComment("cond-end:${condId}")}</>`;
422
+ }
423
+ if (content.startsWith("<") && node.type !== "fragment") {
424
+ const match = content.match(/^<(\w+)/);
425
+ if (match) {
426
+ return content.replace(`<${match[1]}`, `<${match[1]} bf-c="${condId}"`);
427
+ }
428
+ }
429
+ if (node.type === "expression") {
430
+ return `<>{bfComment("cond-start:${condId}")}{${content}}{bfComment("cond-end:${condId}")}</>`;
431
+ }
432
+ return `<>{bfComment("cond-start:${condId}")}${content}{bfComment("cond-end:${condId}")}</>`;
433
+ }
434
+ renderLoop(loop) {
435
+ if (loop.clientOnly) {
436
+ return `{bfComment('loop:${loop.markerId}')}{bfComment('/loop:${loop.markerId}')}`;
437
+ }
438
+ const paramAnnotation = loop.paramType ? `: ${loop.paramType}` : "";
439
+ const indexAnnotation = loop.indexType ? `: ${loop.indexType}` : "";
440
+ const indexParam = loop.index ? `, ${loop.index}${indexAnnotation}` : "";
441
+ this.loopKeyStack.push({ key: loop.key, param: loop.param });
442
+ const children = this.renderChildrenInLoop(loop.children);
443
+ this.loopKeyStack.pop();
444
+ let mapExpr;
445
+ const preamble = loop.typedMapPreamble ?? loop.mapPreamble;
446
+ let safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
447
+ if (loop.bodyIsMultiRoot) {
448
+ safeChildren = `<>{bfComment('bf-loop-i')}${children}</>`;
449
+ }
450
+ const chainedArray = applyHonoLoopChain(loop);
451
+ const iterMethod = loop.method ?? "map";
452
+ if (loop.flatMapCallback) {
453
+ mapExpr = `{${chainedArray}.flatMap(${loop.flatMapCallback.params} => ${loop.flatMapCallback.rawBody})}`;
454
+ } else if (preamble) {
455
+ mapExpr = `{${chainedArray}.${iterMethod}((${loop.param}${paramAnnotation}${indexParam}) => { ${preamble} return ${safeChildren} })}`;
456
+ } else {
457
+ mapExpr = `{${chainedArray}.${iterMethod}((${loop.param}${paramAnnotation}${indexParam}) => ${safeChildren})}`;
458
+ }
459
+ return `{bfComment('loop:${loop.markerId}')}${mapExpr}{bfComment('/loop:${loop.markerId}')}`;
460
+ }
461
+ renderChildrenInLoop(children) {
462
+ return children.map((child) => this.renderNode(child, { isInsideLoop: true, isLoopItemRoot: true })).join("");
463
+ }
464
+ renderIfStatement(ifStmt, ctx) {
465
+ const lines = [];
466
+ for (const v of ifStmt.scopeVariables) {
467
+ const init = this.jsxConfig.preserveTypes && v.typedInitializer || v.initializer;
468
+ lines.push(` const ${v.name} = ${init}`);
469
+ }
470
+ const consequent = this.renderNode(ifStmt.consequent, ctx);
471
+ lines.unshift(` if (${ifStmt.condition}) {`);
472
+ lines.push(` return (`);
473
+ lines.push(` ${consequent}`);
474
+ lines.push(` )`);
475
+ lines.push(` }`);
476
+ if (ifStmt.alternate) {
477
+ if (ifStmt.alternate.type === "if-statement") {
478
+ const elseIfCode = this.renderIfStatement(ifStmt.alternate, ctx);
479
+ lines.push(elseIfCode.replace(/^\s*if/, " else if"));
480
+ } else {
481
+ const alternate = this.renderNode(ifStmt.alternate, ctx);
482
+ lines.push(` return (`);
483
+ lines.push(` ${alternate}`);
484
+ lines.push(` )`);
485
+ }
486
+ } else {
487
+ lines.push(` return null`);
488
+ }
489
+ return lines.join(`
490
+ `);
491
+ }
492
+ renderAsync(node) {
493
+ const fallback = this.renderNode(node.fallback);
494
+ const children = this.renderChildren(node.children);
495
+ return `<Suspense fallback={<>${fallback}</>}>${children}</Suspense>`;
496
+ }
497
+ renderComponent(comp, ctx) {
498
+ const props = this.renderComponentProps(comp);
499
+ const children = this.renderChildren(comp.children);
500
+ let scopeAttr;
501
+ const bfChildAttr = comp.slotId && this.hasClientInteractivity ? " __bfChild={true}" : "";
502
+ const bfMountAttr = comp.slotId ? ` __bfParent={__scopeId} __bfMount={'${comp.slotId}'}` : "";
503
+ if (ctx?.isRootOfClientComponent) {
504
+ const propsPassAttr = this.currentComponentHasProps ? " __bfParentProps={__bfPropsJson}" : "";
505
+ if (comp.slotId) {
506
+ scopeAttr = ` __instanceId={\`\${__scopeId}_${comp.slotId}\`}${propsPassAttr}${bfMountAttr}`;
507
+ } else {
508
+ scopeAttr = ` __instanceId={__scopeId}${propsPassAttr}`;
509
+ }
510
+ scopeAttr += ` ${BF_SCOPE}={__scopeId}`;
511
+ } else if (ctx?.isInsideLoop) {
512
+ if (comp.slotId) {
513
+ scopeAttr = ` __bfScope={\`\${__scopeId}_${comp.slotId}\`}${bfChildAttr}${bfMountAttr}`;
514
+ } else {
515
+ scopeAttr = " __bfScope={__scopeId}";
516
+ }
517
+ } else if (comp.slotId) {
518
+ scopeAttr = ` __instanceId={\`\${__scopeId}_${comp.slotId}\`}${bfChildAttr}${bfMountAttr}`;
519
+ } else {
520
+ scopeAttr = " __instanceId={__scopeId}";
521
+ }
522
+ if (children) {
523
+ return `<${comp.name}${props}${scopeAttr}>${children}</${comp.name}>`;
524
+ } else {
525
+ return `<${comp.name}${props}${scopeAttr} />`;
526
+ }
527
+ }
528
+ renderFragment(fragment) {
529
+ const children = this.renderChildren(fragment.children);
530
+ if (fragment.needsScopeComment) {
531
+ return this.wrapWithScopeComment(children);
532
+ }
533
+ return `<>${children}</>`;
534
+ }
535
+ wrapWithScopeComment(body) {
536
+ const hostExpr = '${__bfParent ? `|h=${__bfParent}|m=${__bfMount}` : ""}';
537
+ const propsExpr = this.currentComponentHasProps ? '${__bfPropsJson ? `|${__bfPropsJson}` : ""}' : "";
538
+ return `<>{bfComment(\`scope:\${__scopeId}${hostExpr}${propsExpr}\`)}${body}</>`;
539
+ }
540
+ elementAttrEmitter = {
541
+ emitLiteral: (value, name) => `${name}="${value.value}"`,
542
+ emitExpression: (value, name) => {
543
+ if (isBooleanAttr(name) || value.presenceOrUndefined) {
544
+ return `${name}={(${value.expr}) || undefined}`;
545
+ }
546
+ return `${name}={${value.expr}}`;
547
+ },
548
+ emitBooleanAttr: (_value, name) => name,
549
+ emitBooleanShorthand: () => "",
550
+ emitTemplate: (value, name) => `${name}={${this.renderTemplateLiteralParts(value.parts)}}`,
551
+ emitSpread: (value) => `{...${value.expr}}`,
552
+ emitJsxChildren: () => ""
553
+ };
554
+ componentPropEmitter = {
555
+ emitLiteral: (value, name) => `${name}="${value.value}"`,
556
+ emitExpression: (value, name) => `${name}={${value.expr}}`,
557
+ emitBooleanAttr: (_value, name) => name,
558
+ emitBooleanShorthand: (_value, name) => name,
559
+ emitTemplate: (value, name) => `${name}={${this.renderTemplateLiteralParts(value.parts)}}`,
560
+ emitSpread: (value) => `{...${value.expr}}`,
561
+ emitJsxChildren: (value, name) => {
562
+ const rendered = value.children.map((c) => this.renderNode(c)).join("");
563
+ return `${name}={<>${rendered}</>}`;
564
+ }
565
+ };
566
+ renderAttributes(element) {
567
+ const parts = [];
568
+ for (const attr of element.attrs) {
569
+ const lowered = emitAttrValue(attr.value, this.elementAttrEmitter, attr.name);
570
+ if (lowered)
571
+ parts.push(lowered);
572
+ }
573
+ for (const event of element.events) {
574
+ const handlerName = event.originalAttr ?? `on${event.name.charAt(0).toUpperCase()}${event.name.slice(1)}`;
575
+ parts.push(`${handlerName}={() => {}}`);
576
+ }
577
+ return parts.length > 0 ? " " + parts.join(" ") : "";
578
+ }
579
+ renderComponentProps(comp) {
580
+ const parts = [];
581
+ let keyValue = null;
582
+ for (const prop of comp.props) {
583
+ if (prop.name === "key") {
584
+ keyValue = this.attrValueToJsExpr(prop.value);
585
+ continue;
586
+ }
587
+ const lowered = emitAttrValue(prop.value, this.componentPropEmitter, prop.name);
588
+ if (lowered)
589
+ parts.push(lowered);
590
+ }
591
+ if (keyValue) {
592
+ parts.push(`data-key={${keyValue}}`);
593
+ }
594
+ return parts.length > 0 ? " " + parts.join(" ") : "";
595
+ }
596
+ attrValueToJsExpr(value) {
597
+ switch (value.kind) {
598
+ case "literal":
599
+ return JSON.stringify(value.value);
600
+ case "expression":
601
+ case "spread":
602
+ return value.expr;
603
+ case "template":
604
+ return this.renderTemplateLiteralParts(value.parts);
605
+ case "boolean-shorthand":
606
+ case "boolean-attr":
607
+ return "true";
608
+ case "jsx-children":
609
+ return "undefined";
610
+ }
611
+ }
612
+ renderTemplateLiteralParts(parts) {
613
+ let output = "`";
614
+ for (const part of parts) {
615
+ if (part.type === "string") {
616
+ output += part.value;
617
+ } else if (part.type === "ternary") {
618
+ output += `\${${part.condition} ? '${part.whenTrue}' : '${part.whenFalse}'}`;
619
+ } else if (part.type === "lookup") {
620
+ const obj = "{" + Object.entries(part.cases).map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`).join(", ") + "}";
621
+ output += `\${(${obj})[${part.key}]}`;
622
+ }
623
+ }
624
+ output += "`";
625
+ return output;
626
+ }
627
+ }
628
+ var honoAdapter = new HonoAdapter;
629
+ // src/build.ts
630
+ function createConfig(options = {}) {
631
+ const useScriptCollection = options.scriptCollection ?? true;
632
+ return {
633
+ adapter: new HonoAdapter(options.adapterOptions),
634
+ paths: options.paths,
635
+ components: options.components,
636
+ outDir: options.outDir,
637
+ minify: options.minify,
638
+ contentHash: options.contentHash,
639
+ externals: options.externals,
640
+ externalsBasePath: options.externalsBasePath,
641
+ bundleEntries: options.bundleEntries,
642
+ localImportPrefixes: options.localImportPrefixes,
643
+ transformMarkedTemplate: useScriptCollection ? (content, componentId, clientJsPath) => addScriptCollection(content, componentId, clientJsPath, options.scriptBasePath) : undefined
644
+ };
645
+ }
646
+ function addScriptCollection(content, componentId, clientJsPath, scriptBasePath = "/static/components/") {
647
+ const basePath = scriptBasePath.endsWith("/") ? scriptBasePath : scriptBasePath + "/";
648
+ const importStatement = `import { useRequestContext } from 'hono/jsx-renderer'
649
+ import { Fragment } from 'hono/jsx'
650
+ `;
651
+ const importMatch = content.match(/^([\s\S]*?)((?:import[^\n]+\n)*)/m);
652
+ if (!importMatch) {
653
+ return content;
654
+ }
655
+ const beforeImports = importMatch[1];
656
+ const existingImports = importMatch[2];
657
+ const restOfFile = content.slice(importMatch[0].length);
658
+ const helperFn = `
659
+ function __bfWrap(jsx: any, scripts: string[]) {
660
+ if (scripts.length === 0) return jsx
661
+ return <Fragment>{jsx}{scripts.map(s => <script type="module" src={s} />)}</Fragment>
662
+ }
663
+ `;
664
+ const scriptCollector = `
665
+ let __bfInlineScripts: string[] = []
666
+ // Script collection for client JS hydration
667
+ try {
668
+ const __c = useRequestContext()
669
+ const __scripts: { src: string }[] = __c.get('bfCollectedScripts') || []
670
+ const __outputScripts: Set<string> = __c.get('bfOutputScripts') || new Set()
671
+ const __bfRendered = __c.get('bfScriptsRendered')
672
+ if (!__outputScripts.has('__barefoot__')) {
673
+ __outputScripts.add('__barefoot__')
674
+ if (__bfRendered) __bfInlineScripts.push('${basePath}barefoot.js')
675
+ else __scripts.push({ src: '${basePath}barefoot.js' })
676
+ }
677
+ if (!__outputScripts.has('${componentId}')) {
678
+ __outputScripts.add('${componentId}')
679
+ if (__bfRendered) __bfInlineScripts.push('${basePath}${clientJsPath}')
680
+ else __scripts.push({ src: '${basePath}${clientJsPath}' })
681
+ }
682
+ __c.set('bfCollectedScripts', __scripts)
683
+ __c.set('bfOutputScripts', __outputScripts)
684
+ } catch {}
685
+ `;
686
+ let modifiedRest = restOfFile;
687
+ const maskedRest = maskComments(restOfFile);
688
+ const exportFuncPattern = /(?:export )?function ([A-Z]\w*)\s*\(/g;
689
+ const insertions = [];
690
+ let efMatch;
691
+ while ((efMatch = exportFuncPattern.exec(maskedRest)) !== null) {
692
+ const openParenPos = efMatch.index + efMatch[0].length - 1;
693
+ let depth = 1;
694
+ let i = openParenPos + 1;
695
+ while (i < restOfFile.length && depth > 0) {
696
+ const ch = restOfFile[i];
697
+ if (ch === "'" || ch === '"' || ch === "`") {
698
+ i++;
699
+ while (i < restOfFile.length) {
700
+ if (restOfFile[i] === "\\") {
701
+ i += 2;
702
+ continue;
703
+ }
704
+ if (restOfFile[i] === ch) {
705
+ i++;
706
+ break;
707
+ }
708
+ i++;
709
+ }
710
+ continue;
711
+ }
712
+ if (ch === "(")
713
+ depth++;
714
+ else if (ch === ")")
715
+ depth--;
716
+ i++;
717
+ }
718
+ while (i < restOfFile.length && restOfFile[i] !== "{")
719
+ i++;
720
+ if (i < restOfFile.length) {
721
+ insertions.push({ index: i + 1, text: scriptCollector });
722
+ }
723
+ }
724
+ for (let ii = insertions.length - 1;ii >= 0; ii--) {
725
+ const ins = insertions[ii];
726
+ modifiedRest = modifiedRest.slice(0, ins.index) + ins.text + modifiedRest.slice(ins.index);
727
+ }
728
+ const returnPattern = /return\s*\(/g;
729
+ const returnMatches = [];
730
+ let m;
731
+ while ((m = returnPattern.exec(modifiedRest)) !== null) {
732
+ returnMatches.push({ index: m.index, length: m[0].length });
733
+ }
734
+ for (let ri = returnMatches.length - 1;ri >= 0; ri--) {
735
+ const rm = returnMatches[ri];
736
+ const afterOpen = rm.index + rm.length;
737
+ let depth = 1;
738
+ let ci = afterOpen;
739
+ while (ci < modifiedRest.length && depth > 0) {
740
+ if (modifiedRest[ci] === "(")
741
+ depth++;
742
+ else if (modifiedRest[ci] === ")")
743
+ depth--;
744
+ ci++;
745
+ }
746
+ modifiedRest = modifiedRest.slice(0, ci) + ", __bfInlineScripts)" + modifiedRest.slice(ci);
747
+ modifiedRest = modifiedRest.slice(0, rm.index) + "return __bfWrap((" + modifiedRest.slice(rm.index + rm.length);
748
+ }
749
+ return beforeImports + existingImports + importStatement + helperFn + modifiedRest;
750
+ }
751
+ function maskComments(s) {
752
+ let out = "";
753
+ let i = 0;
754
+ while (i < s.length) {
755
+ const ch = s[i];
756
+ const next = s[i + 1];
757
+ if (ch === "/" && next === "*") {
758
+ const end = s.indexOf("*/", i + 2);
759
+ const stop = end === -1 ? s.length : end + 2;
760
+ for (let j = i;j < stop; j++)
761
+ out += s[j] === `
762
+ ` ? `
763
+ ` : " ";
764
+ i = stop;
765
+ continue;
766
+ }
767
+ if (ch === "/" && next === "/") {
768
+ const end = s.indexOf(`
769
+ `, i + 2);
770
+ const stop = end === -1 ? s.length : end;
771
+ for (let j = i;j < stop; j++)
772
+ out += " ";
773
+ i = stop;
774
+ continue;
775
+ }
776
+ out += ch;
777
+ i++;
778
+ }
779
+ return out;
780
+ }
781
+ export {
782
+ maskComments,
783
+ createConfig,
784
+ addScriptCollection
785
+ };