@barefootjs/hono 0.5.0 → 0.5.2

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.
@@ -78,7 +78,7 @@ export declare class HonoAdapter extends JsxAdapter implements IRNodeEmitter<Hon
78
78
  emitElement(node: IRElement, ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
79
79
  emitText(node: IRText): string;
80
80
  emitExpression(node: IRExpression): string;
81
- emitConditional(node: IRConditional, _ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
81
+ emitConditional(node: IRConditional, ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
82
82
  emitLoop(node: IRLoop, _ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
83
83
  emitComponent(node: IRComponent, ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
84
84
  emitFragment(node: IRFragment, _ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string;
@@ -91,7 +91,14 @@ export declare class HonoAdapter extends JsxAdapter implements IRNodeEmitter<Hon
91
91
  }): string;
92
92
  private renderText;
93
93
  renderExpression(expr: IRExpression): string;
94
- renderConditional(cond: IRConditional): string;
94
+ renderConditional(cond: IRConditional, ctx?: HonoRenderCtx): string;
95
+ /**
96
+ * Like the base `renderNodeRaw`, but threads a render ctx through to
97
+ * `renderNode` so a conditional branch can mark its element as a loop item
98
+ * root (#1665). The `null` / `undefined` expression branch carries no
99
+ * element, so it short-circuits exactly as the base helper does.
100
+ */
101
+ private renderNodeRawCtx;
95
102
  private wrapWithCondMarker;
96
103
  renderLoop(loop: IRLoop): string;
97
104
  private renderChildrenInLoop;
@@ -1 +1 @@
1
- {"version":3,"file":"hono-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/hono-adapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,MAAM,EAIX,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAElB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,EAEf,UAAU,EAMX,MAAM,iBAAiB,CAAA;AAExB;;;;;GAKG;AACH,KAAK,aAAa,GAAG;IACnB,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAGD,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IAEvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAuBD,qBAAa,WAAY,SAAQ,UAAW,YAAW,aAAa,CAAC,aAAa,CAAC;IACjF,IAAI,SAAS;IACb,SAAS,SAAS;IAClB,gBAAgB,SAAiC;IAGjD,kBAAkB,EAAG,WAAW,CAAS;IAmBzC,mBAAmB,QAAO,OAAO,CAAQ;IAEzC,SAAS,CAAC,SAAS,EAAE,gBAAgB,CAA0B;IAE/D,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,sBAAsB,CAAiB;IAC/C,OAAO,CAAC,wBAAwB,CAAiB;IACjD;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB,CAAC,CAAgC;IAC9D,uFAAuF;IACvF,OAAO,CAAC,YAAY,CAAmD;IAEvE,YAAY,OAAO,GAAE,kBAAuB,EAQ3C;IAED,QAAQ,CAAC,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,aAAa,CAyCzE;IAED,OAAO,CAAC,kCAAkC;IAkB1C,OAAO,CAAC,eAAe;IAyDvB,aAAa,CAAC,EAAE,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA+EpE;IAED,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,iBAAiB;IAmNzB;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,aAAa,GAAG,MAAM,CAEpD;IAMD,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,CAAC,aAAa,CAAC,GAAG,MAAM,CAEzF;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7B;IAED,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAEzC;IAED,eAAe,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,CAAC,aAAa,CAAC,GAAG,MAAM,CAElG;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,CAAC,aAAa,CAAC,GAAG,MAAM,CAEpF;IAED,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,CAAC,aAAa,CAAC,GAAG,MAAM,CAE7F;IAED,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,CAAC,aAAa,CAAC,GAAG,MAAM,CAE5F;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9B;IAED,eAAe,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,CAAC,aAAa,CAAC,GAAG,MAAM,CAKnG;IAED,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,CAAC,aAAa,CAAC,GAAG,MAAM,CAwB5F;IAED,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,CAAC,aAAa,CAAC,GAAG,MAAM,CAEtF;IAED,aAAa,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,CAwC5E;IAED,OAAO,CAAC,UAAU;IAIlB,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAgB3C;IAED,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CA0B7C;IAED,OAAO,CAAC,kBAAkB;IA2B1B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiF/B;IAED,OAAO,CAAC,oBAAoB;IAI5B;;;OAGG;IACH,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE;QAAE,uBAAuB,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,CA4C5F;IAED,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAIjC;IAED,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE;QAAE,uBAAuB,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,CAiDxI;IAED,OAAO,CAAC,cAAc;IAQtB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAY5B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAmBlC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAepC;IAED,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,oBAAoB;IAyB5B,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,0BAA0B;CAwBnC;AAGD,eAAO,MAAM,WAAW,aAAoB,CAAA"}
1
+ {"version":3,"file":"hono-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/hono-adapter.ts"],"names":[],"mappings":"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"}
@@ -260,9 +260,13 @@ export default ${this.componentName}` : "";
260
260
  }
261
261
  fullPropsDestructure = `{ ${parts.join(", ")} }`;
262
262
  }
263
+ const hasRequiredProps = ir.metadata.propsParams.some((p) => !p.optional && p.defaultValue === undefined && !p.isRest);
264
+ const wantsNoArgDefault = propsObjectName ? !propsTypeName : !hasRequiredProps;
265
+ const propsTypeExpr = typeAnnotation.replace(/^:\s*/, "");
266
+ const noArgDefault = wantsNoArgDefault ? ` = {} as ${propsTypeExpr}` : "";
263
267
  const lines = [];
264
268
  const exportPrefix = ir.metadata.isExported === false ? "" : "export ";
265
- lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}) {`);
269
+ lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`);
266
270
  if (propsExtraction) {
267
271
  lines.push(propsExtraction);
268
272
  }
@@ -313,8 +317,8 @@ export default ${this.componentName}` : "";
313
317
  emitExpression(node) {
314
318
  return this.renderExpression(node);
315
319
  }
316
- emitConditional(node, _ctx, _emit) {
317
- return this.renderConditional(node);
320
+ emitConditional(node, ctx, _emit) {
321
+ return this.renderConditional(node, ctx);
318
322
  }
319
323
  emitLoop(node, _ctx, _emit) {
320
324
  return this.renderLoop(node);
@@ -401,12 +405,13 @@ export default ${this.componentName}` : "";
401
405
  }
402
406
  return `{${expr.expr}}`;
403
407
  }
404
- renderConditional(cond) {
408
+ renderConditional(cond, ctx) {
405
409
  if (cond.clientOnly && cond.slotId) {
406
410
  return `{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}`;
407
411
  }
408
- const whenTrue = this.renderNodeRaw(cond.whenTrue);
409
- let whenFalse = this.renderNodeRaw(cond.whenFalse);
412
+ const branchCtx = ctx?.isLoopItemRoot ? { isLoopItemRoot: true } : undefined;
413
+ const whenTrue = this.renderNodeRawCtx(cond.whenTrue, branchCtx);
414
+ let whenFalse = this.renderNodeRawCtx(cond.whenFalse, branchCtx);
410
415
  if (!whenFalse || whenFalse === "" || whenFalse === "null") {
411
416
  whenFalse = "null";
412
417
  }
@@ -417,6 +422,14 @@ export default ${this.componentName}` : "";
417
422
  }
418
423
  return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`;
419
424
  }
425
+ renderNodeRawCtx(node, ctx) {
426
+ if (node.type === "expression") {
427
+ if (node.expr === "null" || node.expr === "undefined")
428
+ return "null";
429
+ return node.expr;
430
+ }
431
+ return this.renderNode(node, ctx);
432
+ }
420
433
  wrapWithCondMarker(node, content, condId) {
421
434
  if (node.type === "component") {
422
435
  return `<>{bfComment("cond-start:${condId}")}${content}{bfComment("cond-end:${condId}")}</>`;
@@ -447,6 +460,8 @@ export default ${this.componentName}` : "";
447
460
  let safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
448
461
  if (loop.bodyIsMultiRoot) {
449
462
  safeChildren = `<>{bfComment('bf-loop-i')}${children}</>`;
463
+ } else if (loop.bodyIsItemConditional && loop.key) {
464
+ safeChildren = `<>{bfComment('loop-i:' + String(${loop.key}))}${children}</>`;
450
465
  }
451
466
  let chainedArray = applyHonoLoopChain(loop);
452
467
  const iterMethod = loop.method ?? "map";
package/dist/build.js CHANGED
@@ -260,9 +260,13 @@ export default ${this.componentName}` : "";
260
260
  }
261
261
  fullPropsDestructure = `{ ${parts.join(", ")} }`;
262
262
  }
263
+ const hasRequiredProps = ir.metadata.propsParams.some((p) => !p.optional && p.defaultValue === undefined && !p.isRest);
264
+ const wantsNoArgDefault = propsObjectName ? !propsTypeName : !hasRequiredProps;
265
+ const propsTypeExpr = typeAnnotation.replace(/^:\s*/, "");
266
+ const noArgDefault = wantsNoArgDefault ? ` = {} as ${propsTypeExpr}` : "";
263
267
  const lines = [];
264
268
  const exportPrefix = ir.metadata.isExported === false ? "" : "export ";
265
- lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}) {`);
269
+ lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`);
266
270
  if (propsExtraction) {
267
271
  lines.push(propsExtraction);
268
272
  }
@@ -313,8 +317,8 @@ export default ${this.componentName}` : "";
313
317
  emitExpression(node) {
314
318
  return this.renderExpression(node);
315
319
  }
316
- emitConditional(node, _ctx, _emit) {
317
- return this.renderConditional(node);
320
+ emitConditional(node, ctx, _emit) {
321
+ return this.renderConditional(node, ctx);
318
322
  }
319
323
  emitLoop(node, _ctx, _emit) {
320
324
  return this.renderLoop(node);
@@ -401,12 +405,13 @@ export default ${this.componentName}` : "";
401
405
  }
402
406
  return `{${expr.expr}}`;
403
407
  }
404
- renderConditional(cond) {
408
+ renderConditional(cond, ctx) {
405
409
  if (cond.clientOnly && cond.slotId) {
406
410
  return `{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}`;
407
411
  }
408
- const whenTrue = this.renderNodeRaw(cond.whenTrue);
409
- let whenFalse = this.renderNodeRaw(cond.whenFalse);
412
+ const branchCtx = ctx?.isLoopItemRoot ? { isLoopItemRoot: true } : undefined;
413
+ const whenTrue = this.renderNodeRawCtx(cond.whenTrue, branchCtx);
414
+ let whenFalse = this.renderNodeRawCtx(cond.whenFalse, branchCtx);
410
415
  if (!whenFalse || whenFalse === "" || whenFalse === "null") {
411
416
  whenFalse = "null";
412
417
  }
@@ -417,6 +422,14 @@ export default ${this.componentName}` : "";
417
422
  }
418
423
  return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`;
419
424
  }
425
+ renderNodeRawCtx(node, ctx) {
426
+ if (node.type === "expression") {
427
+ if (node.expr === "null" || node.expr === "undefined")
428
+ return "null";
429
+ return node.expr;
430
+ }
431
+ return this.renderNode(node, ctx);
432
+ }
420
433
  wrapWithCondMarker(node, content, condId) {
421
434
  if (node.type === "component") {
422
435
  return `<>{bfComment("cond-start:${condId}")}${content}{bfComment("cond-end:${condId}")}</>`;
@@ -447,6 +460,8 @@ export default ${this.componentName}` : "";
447
460
  let safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
448
461
  if (loop.bodyIsMultiRoot) {
449
462
  safeChildren = `<>{bfComment('bf-loop-i')}${children}</>`;
463
+ } else if (loop.bodyIsItemConditional && loop.key) {
464
+ safeChildren = `<>{bfComment('loop-i:' + String(${loop.key}))}${children}</>`;
450
465
  }
451
466
  let chainedArray = applyHonoLoopChain(loop);
452
467
  const iterMethod = loop.method ?? "map";
package/dist/index.js CHANGED
@@ -260,9 +260,13 @@ export default ${this.componentName}` : "";
260
260
  }
261
261
  fullPropsDestructure = `{ ${parts.join(", ")} }`;
262
262
  }
263
+ const hasRequiredProps = ir.metadata.propsParams.some((p) => !p.optional && p.defaultValue === undefined && !p.isRest);
264
+ const wantsNoArgDefault = propsObjectName ? !propsTypeName : !hasRequiredProps;
265
+ const propsTypeExpr = typeAnnotation.replace(/^:\s*/, "");
266
+ const noArgDefault = wantsNoArgDefault ? ` = {} as ${propsTypeExpr}` : "";
263
267
  const lines = [];
264
268
  const exportPrefix = ir.metadata.isExported === false ? "" : "export ";
265
- lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}) {`);
269
+ lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`);
266
270
  if (propsExtraction) {
267
271
  lines.push(propsExtraction);
268
272
  }
@@ -313,8 +317,8 @@ export default ${this.componentName}` : "";
313
317
  emitExpression(node) {
314
318
  return this.renderExpression(node);
315
319
  }
316
- emitConditional(node, _ctx, _emit) {
317
- return this.renderConditional(node);
320
+ emitConditional(node, ctx, _emit) {
321
+ return this.renderConditional(node, ctx);
318
322
  }
319
323
  emitLoop(node, _ctx, _emit) {
320
324
  return this.renderLoop(node);
@@ -401,12 +405,13 @@ export default ${this.componentName}` : "";
401
405
  }
402
406
  return `{${expr.expr}}`;
403
407
  }
404
- renderConditional(cond) {
408
+ renderConditional(cond, ctx) {
405
409
  if (cond.clientOnly && cond.slotId) {
406
410
  return `{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}`;
407
411
  }
408
- const whenTrue = this.renderNodeRaw(cond.whenTrue);
409
- let whenFalse = this.renderNodeRaw(cond.whenFalse);
412
+ const branchCtx = ctx?.isLoopItemRoot ? { isLoopItemRoot: true } : undefined;
413
+ const whenTrue = this.renderNodeRawCtx(cond.whenTrue, branchCtx);
414
+ let whenFalse = this.renderNodeRawCtx(cond.whenFalse, branchCtx);
410
415
  if (!whenFalse || whenFalse === "" || whenFalse === "null") {
411
416
  whenFalse = "null";
412
417
  }
@@ -417,6 +422,14 @@ export default ${this.componentName}` : "";
417
422
  }
418
423
  return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`;
419
424
  }
425
+ renderNodeRawCtx(node, ctx) {
426
+ if (node.type === "expression") {
427
+ if (node.expr === "null" || node.expr === "undefined")
428
+ return "null";
429
+ return node.expr;
430
+ }
431
+ return this.renderNode(node, ctx);
432
+ }
420
433
  wrapWithCondMarker(node, content, condId) {
421
434
  if (node.type === "component") {
422
435
  return `<>{bfComment("cond-start:${condId}")}${content}{bfComment("cond-end:${condId}")}</>`;
@@ -447,6 +460,8 @@ export default ${this.componentName}` : "";
447
460
  let safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
448
461
  if (loop.bodyIsMultiRoot) {
449
462
  safeChildren = `<>{bfComment('bf-loop-i')}${children}</>`;
463
+ } else if (loop.bodyIsItemConditional && loop.key) {
464
+ safeChildren = `<>{bfComment('loop-i:' + String(${loop.key}))}${children}</>`;
450
465
  }
451
466
  let chainedArray = applyHonoLoopChain(loop);
452
467
  const iterMethod = loop.method ?? "map";
@@ -14,6 +14,16 @@ export interface RenderOptions {
14
14
  props?: Record<string, unknown>;
15
15
  /** Additional component files (filename → source) */
16
16
  components?: Record<string, string>;
17
+ /**
18
+ * Pre-compiled child component modules (import specifier → absolute
19
+ * module path) — #1467 Phase 2a. When the parent imports one of these
20
+ * specifiers, the import is *re-anchored* to the given module path
21
+ * (kept as a real ESM import) instead of having the child inlined via
22
+ * `components`. The module is a committed, export-intact marked
23
+ * template, so SSR loads it through the module system — no export
24
+ * stripping. Takes precedence over `components` for the same key.
25
+ */
26
+ componentModules?: Record<string, string>;
17
27
  /**
18
28
  * Explicit component to render when the source declares multiple
19
29
  * exports. When omitted, the first function-valued export in
@@ -1 +1 @@
1
- {"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAQtD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,eAAe,CAAA;IACxB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA0GjF"}
1
+ {"version":3,"file":"test-render.d.ts","sourceRoot":"","sources":["../src/test-render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAStD,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,8BAA8B;IAC9B,OAAO,EAAE,eAAe,CAAA;IACxB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AA2BD,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA0KjF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/hono",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Hono integration for BarefootJS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -510,11 +510,28 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
510
510
  fullPropsDestructure = `{ ${parts.join(', ')} }`
511
511
  }
512
512
 
513
+ // Default the props param to `{}` when the component has no required
514
+ // props, so a bare no-arg call (`Foo()`) doesn't crash on destructuring
515
+ // `undefined`. This makes a JSX-returning arrow hoisted from an
516
+ // object-literal value (e.g. `THEME_LOGOS[id]()`) renderable at SSR
517
+ // (#1663). `hasRequiredProps` ignores props that carry a destructuring
518
+ // default, but the declared props type may still mark that field
519
+ // required — so a bare `= {}` would fail `tsc`. Assert the default to the
520
+ // param's own annotated type (`{} as T`); the destructuring defaults
521
+ // supply the values at runtime. The SolidJS-style (`propsObjectName`)
522
+ // branch opts in whenever the annotation is satisfiable by `{} as T`.
523
+ const hasRequiredProps = ir.metadata.propsParams.some(
524
+ (p: ParamInfo) => !p.optional && p.defaultValue === undefined && !p.isRest,
525
+ )
526
+ const wantsNoArgDefault = propsObjectName ? !propsTypeName : !hasRequiredProps
527
+ const propsTypeExpr = typeAnnotation.replace(/^:\s*/, '')
528
+ const noArgDefault = wantsNoArgDefault ? ` = {} as ${propsTypeExpr}` : ''
529
+
513
530
  const lines: string[] = []
514
531
  // Module-export keyword belongs to the adapter: it knows the target language
515
532
  // and whether the source declared the component as exported.
516
533
  const exportPrefix = ir.metadata.isExported === false ? '' : 'export '
517
- lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}) {`)
534
+ lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`)
518
535
 
519
536
  // Add props extraction for SolidJS-style pattern
520
537
  if (propsExtraction) {
@@ -600,8 +617,8 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
600
617
  return this.renderExpression(node)
601
618
  }
602
619
 
603
- emitConditional(node: IRConditional, _ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string {
604
- return this.renderConditional(node)
620
+ emitConditional(node: IRConditional, ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string {
621
+ return this.renderConditional(node, ctx)
605
622
  }
606
623
 
607
624
  emitLoop(node: IRLoop, _ctx: HonoRenderCtx, _emit: EmitIRNode<HonoRenderCtx>): string {
@@ -721,14 +738,21 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
721
738
  return `{${expr.expr}}`
722
739
  }
723
740
 
724
- renderConditional(cond: IRConditional): string {
741
+ renderConditional(cond: IRConditional, ctx?: HonoRenderCtx): string {
725
742
  // Handle @client directive - render comment markers for client-side evaluation
726
743
  if (cond.clientOnly && cond.slotId) {
727
744
  return `{bfComment("cond-start:${cond.slotId}")}{bfComment("cond-end:${cond.slotId}")}`
728
745
  }
729
746
 
730
- const whenTrue = this.renderNodeRaw(cond.whenTrue)
731
- let whenFalse = this.renderNodeRaw(cond.whenFalse)
747
+ // A conditional that is itself a loop item root (#1665 whole-item
748
+ // conditional: `arr.map(t => cond && <li/>)`) makes its branch element the
749
+ // loop item's root, so the `data-key` that reconciliation/hydration expect
750
+ // belongs on that element — exactly like a non-conditional loop root. Pass
751
+ // the flag through so `renderElement` emits `data-key`, matching the Go /
752
+ // CSR adapters' generic `key`→`data-key` rewrite.
753
+ const branchCtx: HonoRenderCtx | undefined = ctx?.isLoopItemRoot ? { isLoopItemRoot: true } : undefined
754
+ const whenTrue = this.renderNodeRawCtx(cond.whenTrue, branchCtx)
755
+ let whenFalse = this.renderNodeRawCtx(cond.whenFalse, branchCtx)
732
756
 
733
757
  // Handle empty/null whenFalse
734
758
  if (!whenFalse || whenFalse === '' || whenFalse === 'null') {
@@ -749,6 +773,20 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
749
773
  return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`
750
774
  }
751
775
 
776
+ /**
777
+ * Like the base `renderNodeRaw`, but threads a render ctx through to
778
+ * `renderNode` so a conditional branch can mark its element as a loop item
779
+ * root (#1665). The `null` / `undefined` expression branch carries no
780
+ * element, so it short-circuits exactly as the base helper does.
781
+ */
782
+ private renderNodeRawCtx(node: IRNode, ctx?: HonoRenderCtx): string {
783
+ if (node.type === 'expression') {
784
+ if (node.expr === 'null' || node.expr === 'undefined') return 'null'
785
+ return node.expr
786
+ }
787
+ return this.renderNode(node, ctx)
788
+ }
789
+
752
790
  private wrapWithCondMarker(node: IRNode, content: string, condId: string): string {
753
791
  // Components don't reliably forward bf-c to their root element.
754
792
  // Use comment markers so insert() can find them via TreeWalker.
@@ -816,6 +854,14 @@ export class HonoAdapter extends JsxAdapter implements IRNodeEmitter<HonoRenderC
816
854
  // literal here to match the adapter's existing convention of
817
855
  // emitting comment-marker strings directly.
818
856
  safeChildren = `<>{bfComment('bf-loop-i')}${children}</>`
857
+ } else if (loop.bodyIsItemConditional && loop.key) {
858
+ // Whole-item conditional (#1665): a per-item `<!--bf-loop-i:KEY-->`
859
+ // anchor that is ALWAYS present (even when the item's conditional
860
+ // renders nothing), carrying the key so the client's
861
+ // `mapArrayAnchored` can hydrate every SSR-rendered item by its anchor.
862
+ // `bfComment(k)` emits `<!--bf-${k}-->`, so the `loop-i:` argument
863
+ // yields `<!--bf-loop-i:KEY-->`.
864
+ safeChildren = `<>{bfComment('loop-i:' + String(${loop.key}))}${children}</>`
819
865
  }
820
866
  // Apply chained `.sort()` / `.filter()` extracted to
821
867
  // `loop.sortComparator` / `loop.filterPredicate` (#1448 Tier B).
@@ -8,6 +8,7 @@
8
8
  import { compileJSX } from '@barefootjs/jsx'
9
9
  import type { TemplateAdapter } from '@barefootjs/jsx'
10
10
  import { Hono } from 'hono'
11
+ import { readFileSync } from 'node:fs'
11
12
  import { mkdir, rm } from 'node:fs/promises'
12
13
  import { resolve } from 'node:path'
13
14
 
@@ -23,6 +24,16 @@ export interface RenderOptions {
23
24
  props?: Record<string, unknown>
24
25
  /** Additional component files (filename → source) */
25
26
  components?: Record<string, string>
27
+ /**
28
+ * Pre-compiled child component modules (import specifier → absolute
29
+ * module path) — #1467 Phase 2a. When the parent imports one of these
30
+ * specifiers, the import is *re-anchored* to the given module path
31
+ * (kept as a real ESM import) instead of having the child inlined via
32
+ * `components`. The module is a committed, export-intact marked
33
+ * template, so SSR loads it through the module system — no export
34
+ * stripping. Takes precedence over `components` for the same key.
35
+ */
36
+ componentModules?: Record<string, string>
26
37
  /**
27
38
  * Explicit component to render when the source declares multiple
28
39
  * exports. When omitted, the first function-valued export in
@@ -34,14 +45,46 @@ export interface RenderOptions {
34
45
  componentName?: string
35
46
  }
36
47
 
48
+ /**
49
+ * Drop module-level exports from a compiled marked template so it can be
50
+ * inlined as plain declarations alongside other components. Specifier
51
+ * blocks (`export { … }`, `export type { … }`, with or without a
52
+ * trailing `from '…'` re-export source) are removed whole; declaration
53
+ * forms (`export function/const/let/type/interface`, `export default`)
54
+ * keep their body with only the leading keyword stripped.
55
+ *
56
+ * The set of forms is bounded by `generateModuleExports` in
57
+ * @barefootjs/jsx — see the caller for the enumeration. This stays a
58
+ * line-oriented text pass (rather than a real parse) because the input
59
+ * is compiler-generated with a stable, single-line-per-export shape.
60
+ */
61
+ function stripModuleExports(code: string): string {
62
+ return code
63
+ // `export [type] { … } [from '…']` specifier / re-export blocks.
64
+ .replace(
65
+ /^[ \t]*export\s+(?:type\s+)?\{[^}]*\}(?:[ \t]*from[ \t]*['"][^'"]*['"])?[ \t]*;?[ \t]*$/gm,
66
+ '',
67
+ )
68
+ // Leading keyword on declaration forms (`export function`,
69
+ // `export const X = …`, `export default …`, etc.).
70
+ .replace(/\bexport\s+(default\s+)?/g, '')
71
+ }
72
+
37
73
  export async function renderHonoComponent(options: RenderOptions): Promise<string> {
38
- const { source, adapter, props, components, componentName: requestedName } = options
74
+ const { source, adapter, props, components, componentModules, componentName: requestedName } = options
39
75
 
40
- // Compile child components first
76
+ // Child imports re-anchored to a pre-compiled module (#1467 Phase 2a):
77
+ // import specifier → absolute path. These are NOT inlined; the parent's
78
+ // matching import is rewritten to the path and loaded as a real module.
79
+ const moduleMap = new Map<string, string>(Object.entries(componentModules ?? {}))
80
+
81
+ // Compile child components first (inline path). Keys also present in
82
+ // `moduleMap` are skipped here — they load as real modules instead.
41
83
  const childCodes: string[] = []
42
84
  const componentKeys = new Set<string>()
43
85
  if (components) {
44
86
  for (const [filename, childSource] of Object.entries(components)) {
87
+ if (moduleMap.has(filename)) continue
45
88
  componentKeys.add(filename)
46
89
  const childResult = compileJSX(childSource, filename, { adapter })
47
90
  const childErrors = childResult.errors.filter(e => e.severity === 'error')
@@ -50,8 +93,23 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
50
93
  }
51
94
  const childTemplate = childResult.files.find(f => f.type === 'markedTemplate')
52
95
  if (!childTemplate) throw new Error(`No marked template for ${filename}`)
53
- // Strip export keywords so only the parent component is exported
54
- const localCode = childTemplate.content.replace(/\bexport\s+(default\s+)?/g, '')
96
+ // Strip exports so only the parent component is exported, inlining
97
+ // the child as plain top-level declarations. The marked template's
98
+ // export forms are fixed by `generateModuleExports` (+ the
99
+ // component's own `export function`) in @barefootjs/jsx, each on
100
+ // its own line:
101
+ //
102
+ // export const/let X = … export function / async function …
103
+ // export type X = … export interface X { … }
104
+ // export { A, B } [from '…'] export type { A } [from '…']
105
+ //
106
+ // The `export { … }` / `export type { … }` *specifier* blocks
107
+ // (with or without a trailing `from '…'`) must be dropped whole —
108
+ // their bindings are already declared inline, and naively removing
109
+ // just the `export ` keyword leaves a bare `{ A }` / `type { A }`
110
+ // (the latter a syntax error). Declaration forms keep their body;
111
+ // only the leading `export `/`export default ` is removed.
112
+ const localCode = stripModuleExports(childTemplate.content)
55
113
  childCodes.push(localCode)
56
114
  }
57
115
  }
@@ -67,22 +125,56 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
67
125
  const templateFile = result.files.find(f => f.type === 'markedTemplate')
68
126
  if (!templateFile) throw new Error('No marked template in compile output')
69
127
 
128
+ // Pre-compiled child modules are committed under the adapter-tests
129
+ // fixtures tree, where `hono/jsx` is NOT resolvable (hono lives in
130
+ // this package's node_modules — the very reason render temp files go
131
+ // here). Copy each committed module verbatim into the render temp dir
132
+ // and re-anchor the parent import there. The committed file stays the
133
+ // reviewable source of truth; this is a byte copy, not export surgery.
134
+ const childModuleWrites: Array<{ path: string; content: string }> = []
135
+ const moduleTempPaths = new Map<string, string>()
136
+ for (const [key, modPath] of moduleMap) {
137
+ const safe = key.replace(/[^a-zA-Z0-9]+/g, '_')
138
+ const tempPath = resolve(
139
+ RENDER_TEMP_DIR,
140
+ `child-${safe}-${Date.now()}-${Math.random().toString(36).slice(2)}.tsx`,
141
+ )
142
+ moduleTempPaths.set(key, tempPath)
143
+ childModuleWrites.push({ path: tempPath, content: readFileSync(modPath, 'utf8') })
144
+ }
145
+
70
146
  let parentCode = templateFile.content
71
- // Strip import lines that reference component files
72
- if (componentKeys.size > 0) {
147
+ // Resolve each child import: re-anchor to a pre-compiled module's temp
148
+ // copy (`moduleTempPaths`), strip it (inlined via `components`), or
149
+ // leave it. Both maps key on the import specifier; match the parent's
150
+ // import path with or without a `.tsx` extension (`./badge` ↔
151
+ // `./badge.tsx`).
152
+ //
153
+ // Assumes one import statement per line — the marked-template adapter
154
+ // emits single-line imports (`import { Slot } from '../slot'`), so the
155
+ // per-line scan is sufficient. A multi-line import would not match
156
+ // here; the unrewritten `../slot` then fails loudly at module
157
+ // resolution rather than rendering wrong output.
158
+ if (componentKeys.size > 0 || moduleTempPaths.size > 0) {
159
+ const matchKey = (importPath: string, keys: Iterable<string>): string | undefined => {
160
+ for (const key of keys) {
161
+ const keyWithoutExt = key.replace(/\.tsx?$/, '')
162
+ if (importPath === keyWithoutExt || importPath === key) return key
163
+ }
164
+ return undefined
165
+ }
73
166
  parentCode = parentCode
74
167
  .split('\n')
75
- .filter(line => {
76
- const importMatch = line.match(/^\s*import\s+.*from\s+['"](.+?)['"]/)
77
- if (!importMatch) return true
78
- const importPath = importMatch[1]
79
- // Match against component keys: './badge' matches './badge.tsx'
80
- for (const key of componentKeys) {
81
- const keyWithoutExt = key.replace(/\.tsx?$/, '')
82
- if (importPath === keyWithoutExt || importPath === key) return false
83
- }
84
- return true
168
+ .map(line => {
169
+ const importMatch = line.match(/^(\s*import\s+.*from\s+['"])(.+?)(['"].*)$/)
170
+ if (!importMatch) return line
171
+ const [, prefix, importPath, suffix] = importMatch
172
+ const moduleKey = matchKey(importPath, moduleTempPaths.keys())
173
+ if (moduleKey) return `${prefix}${moduleTempPaths.get(moduleKey)}${suffix}`
174
+ if (matchKey(importPath, componentKeys)) return null
175
+ return line
85
176
  })
177
+ .filter((line): line is string => line !== null)
86
178
  .join('\n')
87
179
  }
88
180
 
@@ -95,6 +187,11 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
95
187
  const code = codeParts.join('\n')
96
188
 
97
189
  await mkdir(RENDER_TEMP_DIR, { recursive: true })
190
+ // Materialise the verbatim child-module copies next to the parent so
191
+ // their `hono/jsx` pragma resolves.
192
+ for (const { path, content } of childModuleWrites) {
193
+ await Bun.write(path, content)
194
+ }
98
195
  // Unique filename per render to avoid Bun's process-level module cache
99
196
  // (bun#12371: re-importing the same path returns stale module)
100
197
  const tempFile = resolve(
@@ -139,5 +236,8 @@ export async function renderHonoComponent(options: RenderOptions): Promise<strin
139
236
  return await res.text()
140
237
  } finally {
141
238
  await rm(tempFile, { force: true }).catch(() => {})
239
+ for (const { path } of childModuleWrites) {
240
+ await rm(path, { force: true }).catch(() => {})
241
+ }
142
242
  }
143
243
  }