@barefootjs/jsx 0.26.4 → 0.28.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 (106) hide show
  1. package/dist/compiler.d.ts.map +1 -1
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +941 -130
  5. package/dist/ir-to-client-js/client-only-elision.d.ts +103 -0
  6. package/dist/ir-to-client-js/client-only-elision.d.ts.map +1 -0
  7. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  8. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +6 -0
  9. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
  10. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts +2 -1
  11. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
  12. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts +7 -0
  13. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts.map +1 -1
  14. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts +107 -0
  15. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -0
  16. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts +9 -1
  17. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts +203 -0
  19. package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts.map +1 -0
  20. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +10 -0
  21. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow/shared.d.ts +1 -1
  23. package/dist/ir-to-client-js/control-flow/stringify/branch-loop.d.ts.map +1 -1
  24. package/dist/ir-to-client-js/control-flow/stringify/claim-plan.d.ts +54 -0
  25. package/dist/ir-to-client-js/control-flow/stringify/claim-plan.d.ts.map +1 -0
  26. package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts.map +1 -1
  27. package/dist/ir-to-client-js/control-flow/stringify/insert.d.ts.map +1 -1
  28. package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts +98 -0
  29. package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -0
  30. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +5 -0
  31. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  32. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +17 -20
  33. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  34. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts +63 -4
  35. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
  36. package/dist/ir-to-client-js/control-flow.d.ts +1 -1
  37. package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
  38. package/dist/ir-to-client-js/element-refs.d.ts +8 -3
  39. package/dist/ir-to-client-js/element-refs.d.ts.map +1 -1
  40. package/dist/ir-to-client-js/emit-reactive.d.ts +8 -1
  41. package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
  42. package/dist/ir-to-client-js/html-template.d.ts +24 -0
  43. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  44. package/dist/ir-to-client-js/imports.d.ts +2 -2
  45. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  46. package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
  47. package/dist/ir-to-client-js/types.d.ts +9 -1
  48. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  49. package/dist/ir-to-client-js/utils.d.ts +1 -1
  50. package/dist/types.d.ts +25 -2
  51. package/dist/types.d.ts.map +1 -1
  52. package/package.json +2 -2
  53. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +542 -689
  54. package/src/__tests__/child-components-in-map.test.ts +4 -4
  55. package/src/__tests__/client-js-generation.test.ts +23 -12
  56. package/src/__tests__/composite-branch-loop.test.ts +14 -5
  57. package/src/__tests__/conditional-mapArray-key.test.ts +7 -1
  58. package/src/__tests__/create-selector.test.ts +21 -7
  59. package/src/__tests__/cross-file-client-signal.test.ts +5 -1
  60. package/src/__tests__/early-return-scope-var-ref.test.ts +10 -11
  61. package/src/__tests__/inline-jsx-callback.test.ts +6 -4
  62. package/src/__tests__/ir-jsx-props.test.ts +6 -5
  63. package/src/__tests__/lazy-row-eligibility.test.ts +692 -0
  64. package/src/__tests__/loop-branch-bare-expression-reactive-text.test.ts +98 -0
  65. package/src/__tests__/loop-fallback-wrap.test.ts +34 -16
  66. package/src/__tests__/loop-hoisted-template.test.ts +10 -6
  67. package/src/__tests__/module-level-signal.audit.test.ts +7 -2
  68. package/src/__tests__/nested-loop-conditional.test.ts +9 -7
  69. package/src/__tests__/nested-loop-reactive-attrs.test.ts +2 -2
  70. package/src/__tests__/preamble-region-patch.test.ts +18 -12
  71. package/src/__tests__/profile-cond-binding-ids.test.ts +3 -2
  72. package/src/__tests__/profile-loop-binding-ids.test.ts +4 -3
  73. package/src/__tests__/profile-nested-binding-ids.test.ts +11 -6
  74. package/src/__tests__/reactive-attrs-in-map.test.ts +6 -4
  75. package/src/__tests__/static-loop-csr-materialize.test.ts +6 -1
  76. package/src/__tests__/template-closure.test.ts +1 -1
  77. package/src/compiler.ts +11 -0
  78. package/src/index.ts +1 -0
  79. package/src/ir-to-client-js/client-only-elision.ts +273 -0
  80. package/src/ir-to-client-js/collect-elements.ts +56 -17
  81. package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +6 -0
  82. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +35 -12
  83. package/src/ir-to-client-js/control-flow/plan/build-insert.ts +9 -2
  84. package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +305 -0
  85. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +44 -12
  86. package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +440 -0
  87. package/src/ir-to-client-js/control-flow/plan/loop.ts +10 -0
  88. package/src/ir-to-client-js/control-flow/shared.ts +1 -1
  89. package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +34 -4
  90. package/src/ir-to-client-js/control-flow/stringify/claim-plan.ts +68 -0
  91. package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +18 -9
  92. package/src/ir-to-client-js/control-flow/stringify/insert.ts +17 -11
  93. package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +478 -0
  94. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +41 -17
  95. package/src/ir-to-client-js/control-flow/stringify/loop.ts +71 -46
  96. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +241 -31
  97. package/src/ir-to-client-js/control-flow.ts +8 -3
  98. package/src/ir-to-client-js/element-refs.ts +9 -11
  99. package/src/ir-to-client-js/emit-reactive.ts +50 -14
  100. package/src/ir-to-client-js/html-template.ts +29 -9
  101. package/src/ir-to-client-js/imports.ts +13 -4
  102. package/src/ir-to-client-js/reactivity.ts +3 -2
  103. package/src/ir-to-client-js/types.ts +10 -2
  104. package/src/ir-to-client-js/utils.ts +1 -1
  105. package/src/jsx-to-ir.ts +26 -8
  106. package/src/types.ts +25 -2
package/dist/index.js CHANGED
@@ -3505,6 +3505,10 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3505
3505
  case "expression": {
3506
3506
  if (node.expr === "null" || node.expr === "undefined")
3507
3507
  return "";
3508
+ if (node.markerless) {
3509
+ const bare = wrapInterpolation(wrapExpr(node.expr));
3510
+ return `\${${bare}}`;
3511
+ }
3508
3512
  const inner = wrapInterpolation(wrapExpr(node.expr));
3509
3513
  const valueExpr = node.joinArrayChild ? `Array.isArray(${inner}) ? ${inner}.join('') : (${inner} ?? '')` : inner;
3510
3514
  if (node.slotId) {
@@ -4380,7 +4384,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4380
4384
  if (node.expr === "null" || node.expr === "undefined")
4381
4385
  return "";
4382
4386
  if (node.clientOnly && node.slotId) {
4383
- return `<!--bf-client:${node.slotId}--><!--/-->`;
4387
+ return `<!--bf:${node.slotId}--><!--/-->`;
4384
4388
  }
4385
4389
  {
4386
4390
  const transformed = transformExpr(node.expr, node.templateExpr);
@@ -11233,7 +11237,8 @@ function transformConditionalBranch(node, ctx) {
11233
11237
  const callsReactive = exprCallsReactiveGetters(node, ctx);
11234
11238
  const hasCalls = exprHasFunctionCalls(node);
11235
11239
  const reactive = isReactiveExpression(exprText, ctx, node) || isReactiveOrigin(branchOrigin);
11236
- const needsSlot = reactive || callsReactive;
11240
+ const refsLoopParam = branchOrigin.freeRefs?.some((r) => r.kind === "render-item") ?? false;
11241
+ const needsSlot = reactive || callsReactive || refsLoopParam;
11237
11242
  const slotId = needsSlot ? generateSlotId(ctx) : null;
11238
11243
  return {
11239
11244
  type: "expression",
@@ -14402,7 +14407,11 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
14402
14407
  },
14403
14408
  expression: ({ node: ex, scope: inCond }) => {
14404
14409
  if (ex.clientOnly && ex.slotId) {
14405
- ctx.clientOnlyElements.push({ slotId: ex.slotId, expression: ex.expr });
14410
+ ctx.clientOnlyElements.push({
14411
+ slotId: ex.slotId,
14412
+ expression: ex.expr,
14413
+ elidedPath: ex.markerless ? ex.elidedPath : undefined
14414
+ });
14406
14415
  return;
14407
14416
  }
14408
14417
  if (!ex.slotId || inCond)
@@ -14824,7 +14833,7 @@ function summarizeLoopChildBranch(node, ctx, siblingOffsets, loopParam, loopPara
14824
14833
  conditionals: collectLoopChildConditionals(node, ctx, siblingOffsets, loopParam, loopParamBindings),
14825
14834
  events: collectConditionalBranchEvents(node),
14826
14835
  reactiveAttrs: collectLoopChildReactiveAttrs(node, ctx, loopParam, loopParamBindings, true),
14827
- reactiveTexts: node.type === "expression" ? [] : collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings, true)
14836
+ reactiveTexts: node.type === "expression" && node.hasFunctionCalls ? [] : collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings, true)
14828
14837
  };
14829
14838
  }
14830
14839
 
@@ -15279,13 +15288,12 @@ var RUNTIME_IMPORT_CANDIDATES = [
15279
15288
  "onMount",
15280
15289
  "hydrate",
15281
15290
  "insert",
15282
- "reconcileElements",
15283
15291
  "getLoopChildren",
15284
15292
  "getLoopNodes",
15285
15293
  "mapArray",
15286
15294
  "mapArrayAnchored",
15295
+ "mapArrayLazy",
15287
15296
  "patchLeaf",
15288
- "patchSlotRange",
15289
15297
  "createDisposableEffect",
15290
15298
  "createComponent",
15291
15299
  "renderChild",
@@ -15293,7 +15301,6 @@ var RUNTIME_IMPORT_CANDIDATES = [
15293
15301
  "registerTemplate",
15294
15302
  "initChild",
15295
15303
  "upsertChild",
15296
- "updateClientMarker",
15297
15304
  "createPortal",
15298
15305
  "provideContext",
15299
15306
  "createContext",
@@ -15305,6 +15312,7 @@ var RUNTIME_IMPORT_CANDIDATES = [
15305
15312
  "styleToCss",
15306
15313
  "escapeAttr",
15307
15314
  "escapeText",
15315
+ "escapeTextOrNode",
15308
15316
  "qsa",
15309
15317
  "qsaItem",
15310
15318
  "qsaChildScope",
@@ -15313,7 +15321,10 @@ var RUNTIME_IMPORT_CANDIDATES = [
15313
15321
  "__slot",
15314
15322
  "__bfSlot",
15315
15323
  "__bfText",
15316
- "tAfter",
15324
+ "claimSlots",
15325
+ "lazySlots",
15326
+ "lazyClaimSlots",
15327
+ "textOrNode",
15317
15328
  "beginTurn",
15318
15329
  "endTurn",
15319
15330
  "date",
@@ -18382,8 +18393,352 @@ function buildKeyedOrIndexLookup(args) {
18382
18393
  };
18383
18394
  }
18384
18395
 
18396
+ // src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts
18397
+ var PURE_SOURCE_GLOBALS = new Set([
18398
+ "Object",
18399
+ "Array",
18400
+ "JSON",
18401
+ "Number",
18402
+ "String",
18403
+ "Boolean"
18404
+ ]);
18405
+ var INERT_BINDING_GLOBALS = new Set([
18406
+ "Object",
18407
+ "Array",
18408
+ "JSON",
18409
+ "Number",
18410
+ "String",
18411
+ "Boolean",
18412
+ "Math",
18413
+ "Date",
18414
+ "Intl",
18415
+ "Symbol",
18416
+ "Map",
18417
+ "Set",
18418
+ "WeakMap",
18419
+ "WeakSet",
18420
+ "Promise",
18421
+ "RegExp",
18422
+ "Error",
18423
+ "BigInt",
18424
+ "console",
18425
+ "undefined",
18426
+ "NaN",
18427
+ "Infinity",
18428
+ "globalThis",
18429
+ "parseInt",
18430
+ "parseFloat",
18431
+ "isNaN",
18432
+ "isFinite",
18433
+ "encodeURIComponent",
18434
+ "decodeURIComponent",
18435
+ "encodeURI",
18436
+ "decodeURI"
18437
+ ]);
18438
+ var UNKNOWN_IDENTIFIERS = "<unknown>";
18439
+ var NO = (reason) => ({ eligible: false, reason });
18440
+ function lazyRowEligibility(args) {
18441
+ const { shape, bindings, arraySourceIdentifiers, scope } = args;
18442
+ if (scope.profile)
18443
+ return NO("profile mode keeps the granular eager emission");
18444
+ if (shape.callSite !== "plain" && shape.callSite !== "branch-plain") {
18445
+ return NO(`call site '${shape.callSite}' is not a plain loop row`);
18446
+ }
18447
+ if (shape.flatMapLeafItem)
18448
+ return NO("flatMap descriptor loop (build-or-patch renderItem)");
18449
+ if (shape.anchored)
18450
+ return NO("anchored whole-item-conditional loop");
18451
+ if (shape.bodyIsMultiRoot)
18452
+ return NO("multi-root (Fragment) row");
18453
+ if (!shape.hasExplicitKey)
18454
+ return NO("index-keyed loop (no explicit key)");
18455
+ if (shape.conditionalCount > 0)
18456
+ return NO("row contains a reactive conditional");
18457
+ if (shape.childRefCount > 0)
18458
+ return NO("row has imperative child refs");
18459
+ if (shape.hasChildComponent)
18460
+ return NO("row body is a child component");
18461
+ if (shape.nestedComponentCount > 0)
18462
+ return NO("row contains nested child components");
18463
+ if (shape.innerLoopCount > 0)
18464
+ return NO("row contains an inner loop");
18465
+ if (shape.hasMapPreamble)
18466
+ return NO("row has a map-callback preamble (may declare row-local reactivity)");
18467
+ if (shape.preambleRegionCount > 0)
18468
+ return NO("row has preamble-patched regions");
18469
+ if (shape.hasParamUnwrap)
18470
+ return NO("destructured loop param without param bindings");
18471
+ for (const b of bindings) {
18472
+ if (b.referencesIndex) {
18473
+ return NO(`binding on slot ${b.slotId} references the loop index parameter`);
18474
+ }
18475
+ if (b.opaqueOuterNames.includes(UNKNOWN_IDENTIFIERS)) {
18476
+ return NO(`binding on slot ${b.slotId} has no analyzable identifier set`);
18477
+ }
18478
+ }
18479
+ if (!arraySourceIdentifiers)
18480
+ return NO("loop source free identifiers unavailable");
18481
+ const sourceGate = checkSourceConsistency(arraySourceIdentifiers, scope);
18482
+ if (sourceGate)
18483
+ return NO(`loop source is not provably hydration-consistent: ${sourceGate}`);
18484
+ return { eligible: true };
18485
+ }
18486
+ function checkSourceConsistency(names, scope) {
18487
+ const seen = new Set;
18488
+ const resolve2 = (name) => {
18489
+ if (seen.has(name))
18490
+ return null;
18491
+ seen.add(name);
18492
+ if (scope.props.has(name))
18493
+ return null;
18494
+ if (PURE_SOURCE_GLOBALS.has(name))
18495
+ return null;
18496
+ const constFree = scope.constants.get(name);
18497
+ if (constFree !== undefined) {
18498
+ if (constFree === null)
18499
+ return `constant '${name}' has no analyzable value`;
18500
+ for (const inner of constFree) {
18501
+ const failure = resolve2(inner);
18502
+ if (failure)
18503
+ return failure;
18504
+ }
18505
+ return null;
18506
+ }
18507
+ const signal = scope.signals.get(name);
18508
+ if (signal !== undefined) {
18509
+ if (signal.initializerFreeIdentifiers === null) {
18510
+ return `signal '${name}' has no structured initializer to prove props/literal derivation`;
18511
+ }
18512
+ for (const inner of signal.initializerFreeIdentifiers) {
18513
+ const failure = resolve2(inner);
18514
+ if (failure)
18515
+ return failure;
18516
+ }
18517
+ return null;
18518
+ }
18519
+ if (scope.memos.has(name))
18520
+ return `memo '${name}' initializer is not analyzed by the v1 gate`;
18521
+ if (scope.inert.has(name))
18522
+ return `'${name}' is an import or local function`;
18523
+ return `'${name}' does not resolve to a prop, literal-derived const, or props/literal-derived signal`;
18524
+ };
18525
+ for (const name of names) {
18526
+ const failure = resolve2(name);
18527
+ if (failure)
18528
+ return failure;
18529
+ }
18530
+ return null;
18531
+ }
18532
+ function classifyLazyBinding(args) {
18533
+ const { kind, slotId, free, rowLocalNames, indexParam, scope } = args;
18534
+ if (free === null) {
18535
+ return {
18536
+ kind,
18537
+ slotId,
18538
+ readsItem: true,
18539
+ readsOuter: true,
18540
+ reactiveOuterNames: [],
18541
+ opaqueOuterNames: [UNKNOWN_IDENTIFIERS],
18542
+ referencesIndex: false
18543
+ };
18544
+ }
18545
+ let readsItem = false;
18546
+ let referencesIndex = false;
18547
+ const reactiveOuterNames = [];
18548
+ const opaqueOuterNames = [];
18549
+ for (const name of free) {
18550
+ if (rowLocalNames.has(name)) {
18551
+ readsItem = true;
18552
+ continue;
18553
+ }
18554
+ if (name === indexParam) {
18555
+ referencesIndex = true;
18556
+ continue;
18557
+ }
18558
+ if (INERT_BINDING_GLOBALS.has(name))
18559
+ continue;
18560
+ if (scope.signals.has(name) || scope.memos.has(name)) {
18561
+ if (!reactiveOuterNames.includes(name))
18562
+ reactiveOuterNames.push(name);
18563
+ continue;
18564
+ }
18565
+ const constFree = scope.constants.get(name);
18566
+ if (constFree !== undefined && constFree !== null && constFree.size === 0)
18567
+ continue;
18568
+ opaqueOuterNames.push(name);
18569
+ }
18570
+ return {
18571
+ kind,
18572
+ slotId,
18573
+ readsItem,
18574
+ readsOuter: reactiveOuterNames.length > 0 || opaqueOuterNames.length > 0,
18575
+ reactiveOuterNames,
18576
+ opaqueOuterNames,
18577
+ referencesIndex
18578
+ };
18579
+ }
18580
+
18581
+ // src/ir-to-client-js/control-flow/plan/build-lazy-row.ts
18582
+ function buildLazyRowPlan(args) {
18583
+ return decideLazyRow(args).plan;
18584
+ }
18585
+ function decideLazyRow(args) {
18586
+ const { loop, scope } = args;
18587
+ if (!scope) {
18588
+ return { plan: null, decision: { eligible: false, reason: "no component scope info supplied" } };
18589
+ }
18590
+ const wrap = (expr) => wrapLoopParamAsAccessor(expr, loop.param, loop.paramBindings);
18591
+ const rowLocalNames = new Set([loop.param]);
18592
+ for (const b of loop.paramBindings ?? [])
18593
+ rowLocalNames.add(b.name);
18594
+ const classified = [];
18595
+ const attrClass = new Map;
18596
+ loop.bindings.reactiveAttrs.forEach((attr, i) => {
18597
+ const c = classifyLazyBinding({
18598
+ kind: "attr",
18599
+ slotId: attr.childSlotId,
18600
+ free: attrFreeIdentifiers2(attr.expression),
18601
+ rowLocalNames,
18602
+ indexParam: args.indexParam,
18603
+ scope
18604
+ });
18605
+ attrClass.set(i, c);
18606
+ classified.push(c);
18607
+ });
18608
+ const textClass = new Map;
18609
+ loop.bindings.reactiveTexts.forEach((text, i) => {
18610
+ const c = classifyLazyBinding({
18611
+ kind: "text",
18612
+ slotId: text.slotId,
18613
+ free: text.freeIdentifiers ?? null,
18614
+ rowLocalNames,
18615
+ indexParam: args.indexParam,
18616
+ scope
18617
+ });
18618
+ textClass.set(i, c);
18619
+ classified.push(c);
18620
+ });
18621
+ const shape = {
18622
+ callSite: args.callSite,
18623
+ flatMapLeafItem: args.flatMapLeafItem,
18624
+ anchored: args.anchored,
18625
+ bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
18626
+ hasExplicitKey: loop.key != null,
18627
+ conditionalCount: loop.bindings.conditionals?.length ?? 0,
18628
+ childRefCount: loop.bindings.refs?.length ?? 0,
18629
+ nestedComponentCount: loop.nestedComponents?.length ?? 0,
18630
+ innerLoopCount: loop.innerLoops?.length ?? 0,
18631
+ hasChildComponent: "childComponent" in loop && loop.childComponent != null,
18632
+ hasMapPreamble: args.mapPreambleWrapped.length > 0,
18633
+ preambleRegionCount: args.preambleRegionCount,
18634
+ hasParamUnwrap: args.paramUnwrap.length > 0
18635
+ };
18636
+ const decision = lazyRowEligibility({
18637
+ shape,
18638
+ bindings: classified,
18639
+ arraySourceIdentifiers: loopSourceIdentifiers(loop, args.arrayExpr),
18640
+ scope
18641
+ });
18642
+ if (!decision.eligible)
18643
+ return { plan: null, decision };
18644
+ const attrSlotIds = [];
18645
+ for (const attr of loop.bindings.reactiveAttrs) {
18646
+ if (!attrSlotIds.includes(attr.childSlotId))
18647
+ attrSlotIds.push(attr.childSlotId);
18648
+ }
18649
+ let ordinal = 0;
18650
+ const attrs = loop.bindings.reactiveAttrs.map((attr, i) => {
18651
+ const c = attrClass.get(i);
18652
+ return {
18653
+ slotId: attr.childSlotId,
18654
+ attrName: attr.attrName,
18655
+ wrappedExpression: wrap(attr.expression),
18656
+ meta: pickAttrMeta(attr),
18657
+ refIndex: attrSlotIds.indexOf(attr.childSlotId),
18658
+ ordinal: ordinal++,
18659
+ readsItem: c.readsItem,
18660
+ readsOuter: c.readsOuter
18661
+ };
18662
+ });
18663
+ const texts = loop.bindings.reactiveTexts.map((text, i) => {
18664
+ const c = textClass.get(i);
18665
+ return {
18666
+ slotId: text.slotId,
18667
+ wrappedExpression: wrap(text.expression),
18668
+ ordinal: ordinal++,
18669
+ readsItem: c.readsItem || !c.readsOuter,
18670
+ readsOuter: c.readsOuter
18671
+ };
18672
+ });
18673
+ const outerPrimeGetters = [];
18674
+ for (const c of classified) {
18675
+ for (const name of c.reactiveOuterNames) {
18676
+ if (!outerPrimeGetters.includes(name))
18677
+ outerPrimeGetters.push(name);
18678
+ }
18679
+ }
18680
+ return {
18681
+ plan: {
18682
+ attrSlotIds,
18683
+ attrs,
18684
+ texts,
18685
+ writerIndex: texts.length > 0 ? attrSlotIds.length : -1,
18686
+ textNeedsRead: texts.some((t) => t.readsOuter),
18687
+ lastCount: ordinal,
18688
+ outerPrimeGetters,
18689
+ hasOuter: attrs.some((a) => a.readsOuter) || texts.some((t) => t.readsOuter)
18690
+ },
18691
+ decision
18692
+ };
18693
+ }
18694
+ function buildLazyRowScopeInfo(ctx) {
18695
+ const signals = new Map;
18696
+ for (const s of ctx.signals) {
18697
+ signals.set(s.getter, {
18698
+ initializerFreeIdentifiers: s.parsed ? freeIdentifiers(s.parsed) : null
18699
+ });
18700
+ }
18701
+ const memos = new Set(ctx.memos.map((m) => m.name));
18702
+ const props = new Set([PROPS_PARAM]);
18703
+ if (ctx.propsObjectName)
18704
+ props.add(ctx.propsObjectName);
18705
+ for (const p of ctx.propsParams)
18706
+ props.add(p.name);
18707
+ const constants = new Map;
18708
+ for (const c of ctx.localConstants) {
18709
+ constants.set(c.name, c.freeIdentifiers ?? null);
18710
+ }
18711
+ const inert = new Set;
18712
+ for (const f of ctx.localFunctions)
18713
+ inert.add(f.name);
18714
+ for (const imp of ctx.imports) {
18715
+ if (imp.isTypeOnly)
18716
+ continue;
18717
+ for (const spec of imp.specifiers)
18718
+ inert.add(spec.alias || spec.name);
18719
+ }
18720
+ return { signals, memos, props, constants, inert, profile: ctx.profile };
18721
+ }
18722
+ function attrFreeIdentifiers2(expression) {
18723
+ if (!expression || expression.trim().length === 0)
18724
+ return new Set;
18725
+ try {
18726
+ return freeIdentifiers(parseExpression(expression));
18727
+ } catch {
18728
+ return null;
18729
+ }
18730
+ }
18731
+ function loopSourceIdentifiers(loop, arrayExpr) {
18732
+ if (!loop.arrayFreeIdentifiers)
18733
+ return null;
18734
+ const names = new Set(loop.arrayFreeIdentifiers);
18735
+ for (const name of extractFreeIdentifiersFromText(arrayExpr))
18736
+ names.add(name);
18737
+ return names;
18738
+ }
18739
+
18385
18740
  // src/ir-to-client-js/control-flow/plan/build-branch-loop.ts
18386
- function buildBranchLoopPlan(loop, profileComponentName) {
18741
+ function buildBranchLoopPlan(loop, profileComponentName, lazyScope) {
18387
18742
  const containerSlotId = loop.containerSlotId;
18388
18743
  const cv = varSlotId(containerSlotId);
18389
18744
  const containerVar = `__loop_${cv}`;
@@ -18399,6 +18754,13 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18399
18754
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(loop.param, loop.paramBindings);
18400
18755
  const hasReactiveEffects = loop.bindings.reactiveAttrs.length > 0 || loop.bindings.reactiveTexts.length > 0 || loop.bindings.conditionals.length > 0;
18401
18756
  const fm = loop.flatMapClient;
18757
+ const arrayExpr = fm ? `(${buildChainedArrayExpr(loop)}).flatMap(${fm.params} => ${fm.body})` : buildChainedArrayExpr(loop);
18758
+ const indexParam = loop.index || "__idx";
18759
+ const mapPreambleWrapped = loop.preamble ? renderPreamble(loop.preamble, {
18760
+ transformJs: (t) => wrapLoopParamAsAccessor(t, loop.param, loop.paramBindings),
18761
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings }], undefined, true)
18762
+ }) : "";
18763
+ const preambleRegions = buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings);
18402
18764
  const plan = {
18403
18765
  kind: "plain",
18404
18766
  rowConstruction: "string-template",
@@ -18406,15 +18768,24 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18406
18768
  containerVar,
18407
18769
  markerId: loop.markerId,
18408
18770
  flatMapLeafItem: fm ? true : undefined,
18409
- arrayExpr: fm ? `(${buildChainedArrayExpr(loop)}).flatMap(${fm.params} => ${fm.body})` : buildChainedArrayExpr(loop),
18771
+ arrayExpr,
18410
18772
  keyFn: fm ? fm.keyed ? "(__bfD, __bfI) => String(__bfD.k ?? __bfI)" : "null" : loopKeyFn(loop),
18411
18773
  paramHead,
18412
18774
  paramUnwrap,
18413
- indexParam: loop.index || "__idx",
18414
- mapPreambleWrapped: loop.preamble ? renderPreamble(loop.preamble, {
18415
- transformJs: (t) => wrapLoopParamAsAccessor(t, loop.param, loop.paramBindings),
18416
- renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings }], undefined, true)
18417
- }) : "",
18775
+ indexParam,
18776
+ mapPreambleWrapped,
18777
+ lazyRow: buildLazyRowPlan({
18778
+ loop,
18779
+ arrayExpr,
18780
+ indexParam,
18781
+ paramUnwrap,
18782
+ mapPreambleWrapped,
18783
+ preambleRegionCount: preambleRegions.length,
18784
+ callSite: "branch-plain",
18785
+ flatMapLeafItem: Boolean(fm),
18786
+ anchored: false,
18787
+ scope: lazyScope
18788
+ }) ?? undefined,
18418
18789
  template: loop.template,
18419
18790
  reactiveEffects: hasReactiveEffects ? buildReactiveEffectsPlan({
18420
18791
  attrs: loop.bindings.reactiveAttrs,
@@ -18426,7 +18797,7 @@ function buildBranchLoopPlan(loop, profileComponentName) {
18426
18797
  }) : null,
18427
18798
  eventDelegation: buildBranchLoopDelegationPlan(loop, cv, profileComponentName),
18428
18799
  childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
18429
- preambleRegions: buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings),
18800
+ preambleRegions,
18430
18801
  bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
18431
18802
  profileLoopId: profileComponentName ? `${profileComponentName}#binding:${containerSlotId}` : undefined
18432
18803
  };
@@ -18477,13 +18848,29 @@ function buildArmBody(branch, options) {
18477
18848
  slotId: t.slotId,
18478
18849
  expression: t.expression
18479
18850
  })),
18480
- loops: branch.loops.map((l) => buildBranchLoopPlan(l, pc)),
18481
- conditionals: branch.conditionals.map((c) => buildInsertPlan(c, { scope: { kind: "branchScope" }, eventNameMode: options.eventNameMode, profileComponentName: pc }))
18851
+ loops: branch.loops.map((l) => buildBranchLoopPlan(l, pc, options.lazyScope)),
18852
+ conditionals: branch.conditionals.map((c) => buildInsertPlan(c, { scope: { kind: "branchScope" }, eventNameMode: options.eventNameMode, profileComponentName: pc, lazyScope: options.lazyScope }))
18482
18853
  };
18483
18854
  }
18484
18855
 
18485
18856
  // src/ir-to-client-js/emit-reactive.ts
18486
18857
  import ts14 from "typescript";
18858
+
18859
+ // src/ir-to-client-js/control-flow/stringify/claim-plan.ts
18860
+ function slotSpecLiteral(slot) {
18861
+ const pathSrc = slot.pathExpr ?? `[${slot.path.join(", ")}]`;
18862
+ const markerlessSrc = slot.markerless ? ", markerless: true" : "";
18863
+ return `{ id: '${slot.id}', kind: '${slot.kind}', path: ${pathSrc}${markerlessSrc} }`;
18864
+ }
18865
+ function claimPlanLiteral(slots) {
18866
+ return `[${slots.map(slotSpecLiteral).join(", ")}]`;
18867
+ }
18868
+ function claimWriterVarName(slots, sanitize) {
18869
+ const first = slots[0]?.id ?? "0";
18870
+ return `__bfw_${sanitize(first)}`;
18871
+ }
18872
+
18873
+ // src/ir-to-client-js/emit-reactive.ts
18487
18874
  function bindingIdArg(ctx, slotId) {
18488
18875
  if (!ctx.profile || !slotId)
18489
18876
  return "";
@@ -18667,17 +19054,18 @@ function emitDynamicTextUpdates(lines, ctx) {
18667
19054
  const conditionalElems = elems.filter((e) => e.insideConditional);
18668
19055
  const normalElems = elems.filter((e) => !e.insideConditional);
18669
19056
  if (normalElems.length > 0 || conditionalElems.length > 0) {
18670
- for (const elem of normalElems) {
18671
- const v = varSlotId(elem.slotId);
18672
- lines.push(` let __anchor_${v} = _${v}`);
18673
- }
18674
19057
  const __textSlot = (normalElems[0] ?? conditionalElems[0])?.slotId;
19058
+ let writer = "";
19059
+ if (normalElems.length > 0) {
19060
+ const slots = normalElems.map((elem) => ({ id: elem.slotId, kind: "markup", path: [] }));
19061
+ writer = claimWriterVarName(slots, varSlotId);
19062
+ lines.push(` const ${writer} = lazySlots(__scope, ${claimPlanLiteral(slots)})`);
19063
+ }
18675
19064
  lines.push(` createEffect(() => {`);
18676
19065
  if (normalElems.length > 0) {
18677
19066
  lines.push(` const __val = ${expr}`);
18678
19067
  for (const elem of normalElems) {
18679
- const v = varSlotId(elem.slotId);
18680
- lines.push(` __anchor_${v} = __bfText(__anchor_${v}, __val)`);
19068
+ lines.push(` ${writer}('${elem.slotId}', escapeTextOrNode(__val))`);
18681
19069
  }
18682
19070
  for (const elem of conditionalElems) {
18683
19071
  const v = varSlotId(elem.slotId);
@@ -18700,10 +19088,13 @@ function emitDynamicTextUpdates(lines, ctx) {
18700
19088
  }
18701
19089
  function emitClientOnlyExpressions(lines, ctx) {
18702
19090
  for (const elem of ctx.clientOnlyElements) {
19091
+ const slots = elem.elidedPath ? [{ id: elem.slotId, kind: "text", path: elem.elidedPath, markerless: true }] : [{ id: elem.slotId, kind: "text", path: [] }];
19092
+ const writer = claimWriterVarName(slots, varSlotId);
18703
19093
  lines.push(` // @client: ${elem.slotId}`);
19094
+ lines.push(` { const ${writer} = lazySlots(__scope, ${claimPlanLiteral(slots)})`);
18704
19095
  lines.push(` createEffect(() => {`);
18705
- lines.push(` updateClientMarker(__scope, '${elem.slotId}', ${elem.expression})`);
18706
- lines.push(` }${bindingIdArg(ctx, elem.slotId)})`);
19096
+ lines.push(` ${writer}('${elem.slotId}', ${elem.expression})`);
19097
+ lines.push(` }${bindingIdArg(ctx, elem.slotId)}) }`);
18707
19098
  lines.push("");
18708
19099
  }
18709
19100
  }
@@ -18835,7 +19226,7 @@ function stringifyBranchEventBindings(lines, plan, indent) {
18835
19226
  }
18836
19227
  function stringifyBranchChildComponentInits(lines, plan, indent) {
18837
19228
  for (const init of plan) {
18838
- lines.push(`${indent}{ let __c = qsa(__branchScope, ${init.selector}); if (!__c) { const __ph = __branchScope.querySelector('[${DATA_BF_PH}="${init.placeholderId}"]'); if (__ph) { __c = createComponent('${nameForRegistryRef(init.name)}', ${init.propsExpr}); __ph.replaceWith(__c) } } if (__c) initChild('${nameForRegistryRef(init.name)}', __c, ${init.propsExpr}) }`);
19229
+ lines.push(`${indent}{ let __c = qsa(__branchScope, ${init.selector}); if (!__c) { const __ph = __branchScope.querySelector('[${DATA_BF_PH}="${init.placeholderId}"]'); if (__ph) { __c = createComponent('${nameForRegistryRef(init.name)}', ${init.propsExpr}, undefined, undefined, __ph) } } if (__c) initChild('${nameForRegistryRef(init.name)}', __c, ${init.propsExpr}) }`);
18839
19230
  }
18840
19231
  }
18841
19232
  function stringifyBranchInnerLoops(lines, plan, indent, pc) {
@@ -18861,13 +19252,18 @@ function stringifyBranchInnerLoops(lines, plan, indent, pc) {
18861
19252
  if (inner.legacyComponents.length > 0 || inner.legacyEvents.length > 0) {
18862
19253
  emitComponentAndEventSetup(lines, `${indent} `, `__bel${uid}`, [...inner.legacyComponents], [...inner.legacyEvents], inner.outerLoopParam, inner.outerLoopParamBindings);
18863
19254
  }
18864
- for (const text of inner.reactiveTexts) {
19255
+ const conditionalTexts = inner.reactiveTexts.filter((t) => t.insideConditional);
19256
+ const plainTexts = inner.reactiveTexts.filter((t) => !t.insideConditional);
19257
+ for (const text of conditionalTexts) {
18865
19258
  const bf = profileBindingId(pc, text.slotId);
18866
- if (text.insideConditional) {
18867
- lines.push(`${indent} createEffect(() => { const [__rt] = $t(__bel${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) }${bf})`);
18868
- } else {
18869
- lines.push(`${indent} { const [__rt] = $t(__bel${uid}, '${text.slotId}')`);
18870
- lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }${bf}) }`);
19259
+ lines.push(`${indent} createEffect(() => { claimSlots(__bel${uid}, [{ id: '${text.slotId}', kind: 'text', path: [] }]).write('${text.slotId}', String(${text.wrappedExpression})) }${bf})`);
19260
+ }
19261
+ if (plainTexts.length > 0) {
19262
+ const slots = plainTexts.map((t) => ({ id: t.slotId, kind: "text", path: [] }));
19263
+ const writer = claimWriterVarName(slots, varSlotId);
19264
+ lines.push(`${indent} const ${writer} = lazySlots(__bel${uid}, ${claimPlanLiteral(slots)})`);
19265
+ for (const text of plainTexts) {
19266
+ lines.push(`${indent} createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${profileBindingId(pc, text.slotId)})`);
18871
19267
  }
18872
19268
  }
18873
19269
  if (inner.nestedConditionals.length > 0) {
@@ -18910,24 +19306,41 @@ function stringifyLoopChildArm(lines, arm, armIndent, pc) {
18910
19306
  stringifyLoopChildConditional(lines, cond, `${armIndent} `, pc);
18911
19307
  lines.push(`${armIndent}}))`);
18912
19308
  }
18913
- for (const text of arm.texts) {
18914
- const varName = `__rt_${varSlotId(text.slotId)}`;
18915
- lines.push(`${armIndent}let ${varName} = $t(__branchScope, '${text.slotId}')[0]`);
18916
- lines.push(`${armIndent}__disposers.push(createDisposableEffect(() => { ${varName} = __bfText(${varName}, ${text.wrappedExpression}) }${profileBindingId(pc, text.slotId)}))`);
19309
+ if (arm.texts.length > 0) {
19310
+ const slots = arm.texts.map((t) => ({ id: t.slotId, kind: "markup", path: [] }));
19311
+ const writer = claimWriterVarName(slots, varSlotId);
19312
+ lines.push(`${armIndent}const ${writer} = lazySlots(__branchScope, ${claimPlanLiteral(slots)})`);
19313
+ for (const text of arm.texts) {
19314
+ lines.push(`${armIndent}__disposers.push(createDisposableEffect(() => { ${writer}('${text.slotId}', escapeTextOrNode(${text.wrappedExpression})) }${profileBindingId(pc, text.slotId)}))`);
19315
+ }
18917
19316
  }
18918
19317
  lines.push(`${armIndent}return () => __disposers.forEach(d => d())`);
18919
19318
  }
18920
19319
 
18921
19320
  // src/ir-to-client-js/control-flow/stringify/reactive-effects.ts
18922
19321
  function stringifyReactiveEffects(lines, plan, opts) {
18923
- const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot, textIndexBySlot } = opts;
19322
+ const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot, textClaimPathExprs, preambleRegions = [], mapPreambleWrapped } = opts;
18924
19323
  const lookup = bodyIsMultiRoot ? "qsaItem" : "qsa";
18925
- const pc = plan.profileComponentName;
19324
+ const pc = plan?.profileComponentName;
18926
19325
  const bindingBfId = (slotId) => profileBindingId(pc, slotId);
18927
- for (const slot of plan.attrSlots) {
19326
+ const attrSlots = plan?.attrSlots ?? [];
19327
+ const outerTexts = plan?.outerTexts ?? [];
19328
+ const conditionals = plan?.conditionals ?? [];
19329
+ if (pc) {
19330
+ emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementIndexBySlot, bindingBfId);
19331
+ emitOuterTexts(lines, indent, elVar, outerTexts, bindingBfId, textClaimPathExprs);
19332
+ emitPreambleRegionsEffect(lines, indent, elVar, preambleRegions, mapPreambleWrapped);
19333
+ } else {
19334
+ emitConsolidatedRowEffect(lines, indent, elVar, lookup, attrSlots, outerTexts, elementIndexBySlot, textClaimPathExprs, preambleRegions, mapPreambleWrapped);
19335
+ }
19336
+ for (const cond of conditionals) {
19337
+ emitOuterConditional(lines, indent, elVar, cond, pc);
19338
+ }
19339
+ }
19340
+ function emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementIndexBySlot, bindingBfId) {
19341
+ for (const slot of attrSlots) {
18928
19342
  const varName = `__ra_${varSlotId(slot.slotId)}`;
18929
- const pIdx = elementIndexBySlot?.get(slot.slotId);
18930
- const lookupExpr = pIdx !== undefined ? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${slot.slotId}"]')` : `${lookup}(${elVar}, '[bf="${slot.slotId}"]')`;
19343
+ const lookupExpr = attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot);
18931
19344
  lines.push(`${indent}{ const ${varName} = ${lookupExpr}`);
18932
19345
  lines.push(`${indent}if (${varName}) {`);
18933
19346
  for (const attr of slot.attrs) {
@@ -18939,21 +19352,83 @@ function stringifyReactiveEffects(lines, plan, opts) {
18939
19352
  }
18940
19353
  lines.push(`${indent}} }`);
18941
19354
  }
18942
- for (const text of plan.outerTexts) {
18943
- emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId), textIndexBySlot?.get(text.slotId));
19355
+ }
19356
+ function emitPreambleRegionsEffect(lines, indent, elVar, preambleRegions, mapPreambleWrapped) {
19357
+ if (preambleRegions.length === 0)
19358
+ return;
19359
+ const slots = preambleRegions.map((r) => ({ id: r.slotId, kind: "markup", path: [] }));
19360
+ const writer = claimWriterVarName(slots, varSlotId);
19361
+ lines.push(`${indent}const ${writer} = lazySlots(${elVar}, ${claimPlanLiteral(slots)})`);
19362
+ lines.push(`${indent}createEffect(() => {`);
19363
+ if (mapPreambleWrapped)
19364
+ lines.push(`${indent} ${mapPreambleWrapped}`);
19365
+ for (const region of preambleRegions) {
19366
+ lines.push(`${indent} ${writer}('${region.slotId}', ${region.valueExpr})`);
18944
19367
  }
18945
- for (const cond of plan.conditionals) {
18946
- emitOuterConditional(lines, indent, elVar, cond, pc);
19368
+ lines.push(`${indent}})`);
19369
+ }
19370
+ function attrLookupExpr(slotId, varName, elVar, lookup, elementIndexBySlot) {
19371
+ const pIdx = elementIndexBySlot?.get(slotId);
19372
+ return pIdx !== undefined ? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${slotId}"]')` : `${lookup}(${elVar}, '[bf="${slotId}"]')`;
19373
+ }
19374
+ function emitConsolidatedRowEffect(lines, indent, elVar, lookup, attrSlots, outerTexts, elementIndexBySlot, textClaimPathExprs, preambleRegions, mapPreambleWrapped) {
19375
+ if (attrSlots.length === 0 && outerTexts.length === 0 && preambleRegions.length === 0)
19376
+ return;
19377
+ for (const slot of attrSlots) {
19378
+ const varName = `__ra_${varSlotId(slot.slotId)}`;
19379
+ lines.push(`${indent}const ${varName} = ${attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot)}`);
19380
+ }
19381
+ const claimSlots = [
19382
+ ...outerTexts.map((t) => ({ id: t.slotId, kind: "text", path: [], pathExpr: textClaimPathExprs?.get(t.slotId) })),
19383
+ ...preambleRegions.map((r) => ({ id: r.slotId, kind: "markup", path: [] }))
19384
+ ];
19385
+ const writer = claimSlots.length > 0 ? claimWriterVarName(claimSlots, varSlotId) : null;
19386
+ if (writer) {
19387
+ lines.push(`${indent}const ${writer} = lazySlots(${elVar}, ${claimPlanLiteral(claimSlots)})`);
19388
+ }
19389
+ if (attrSlots.length === 0 && preambleRegions.length === 0 && outerTexts.length === 1) {
19390
+ const text = outerTexts[0];
19391
+ lines.push(`${indent}createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) })`);
19392
+ return;
19393
+ }
19394
+ lines.push(`${indent}createEffect(() => {`);
19395
+ for (const slot of attrSlots) {
19396
+ const varName = `__ra_${varSlotId(slot.slotId)}`;
19397
+ lines.push(`${indent} if (${varName}) {`);
19398
+ for (const attr of slot.attrs) {
19399
+ lines.push(`${indent} {`);
19400
+ for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta)) {
19401
+ lines.push(`${indent} ${stmt}`);
19402
+ }
19403
+ lines.push(`${indent} }`);
19404
+ }
19405
+ lines.push(`${indent} }`);
19406
+ }
19407
+ if (preambleRegions.length > 0 && mapPreambleWrapped) {
19408
+ lines.push(`${indent} ${mapPreambleWrapped}`);
18947
19409
  }
19410
+ for (const text of outerTexts) {
19411
+ lines.push(`${indent} ${writer}('${text.slotId}', String(${text.wrappedExpression}))`);
19412
+ }
19413
+ for (const region of preambleRegions) {
19414
+ lines.push(`${indent} ${writer}('${region.slotId}', ${region.valueExpr})`);
19415
+ }
19416
+ lines.push(`${indent}})`);
18948
19417
  }
18949
- function emitOuterText(lines, indent, elVar, text, bfId = "", pIdx) {
18950
- const varName = `__rt_${varSlotId(text.slotId)}`;
18951
- if (pIdx !== undefined) {
18952
- lines.push(`${indent}{ const ${varName} = __p ? tAfter(__p[${pIdx}]) : $t(${elVar}, '${text.slotId}')[0]`);
18953
- } else {
18954
- lines.push(`${indent}{ const [${varName}] = $t(${elVar}, '${text.slotId}')`);
19418
+ function emitOuterTexts(lines, indent, elVar, texts, bindingBfId, textClaimPathExprs) {
19419
+ if (texts.length === 0)
19420
+ return;
19421
+ const slots = texts.map((t) => ({
19422
+ id: t.slotId,
19423
+ kind: "text",
19424
+ path: [],
19425
+ pathExpr: textClaimPathExprs?.get(t.slotId)
19426
+ }));
19427
+ const writer = claimWriterVarName(slots, varSlotId);
19428
+ lines.push(`${indent}const ${writer} = lazySlots(${elVar}, ${claimPlanLiteral(slots)})`);
19429
+ for (const text of texts) {
19430
+ lines.push(`${indent}createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${bindingBfId(text.slotId)})`);
18955
19431
  }
18956
- lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${bfId}) }`);
18957
19432
  }
18958
19433
  function emitOuterConditional(lines, indent, elVar, cond, pc) {
18959
19434
  const armIndent = `${indent} `;
@@ -19060,6 +19535,184 @@ function emitNestedInit(lines, indent, parentVar, nc) {
19060
19535
  }
19061
19536
  }
19062
19537
 
19538
+ // src/ir-to-client-js/control-flow/stringify/lazy-row.ts
19539
+ function stringifyLazyRowLoop(lines, o) {
19540
+ const { indent, lazyRow, paramHead } = o;
19541
+ const mid = o.markerId.replace(/[^A-Za-z0-9_$]/g, "_");
19542
+ const tplVar = `__tpl_${mid}`;
19543
+ const claimVar = `__lzc_${mid}`;
19544
+ const hasRefs = lazyRow.attrSlotIds.length > 0 || lazyRow.texts.length > 0;
19545
+ const hasBindings = lazyRow.attrs.length > 0 || lazyRow.texts.length > 0;
19546
+ const rwDoor = lazyRow.textNeedsRead;
19547
+ const paths = o.skeletonPaths;
19548
+ const useHoisted = Boolean(o.skeletonTemplate);
19549
+ if (useHoisted)
19550
+ emitHoistedTemplateDecl(lines, indent, tplVar, o.skeletonTemplate);
19551
+ const textPathVar = useHoisted && paths && lazyRow.texts.length > 0 ? `__lzp_${mid}` : null;
19552
+ if (textPathVar) {
19553
+ const arrays = lazyRow.texts.map((t) => `[${(paths.textMarkerPaths.get(t.slotId) ?? []).join(", ")}]`);
19554
+ lines.push(`${indent}const ${textPathVar} = [${arrays.join(", ")}]`);
19555
+ }
19556
+ let adoptedPlanVar = null;
19557
+ let freshPlanVar = null;
19558
+ if (lazyRow.texts.length > 0) {
19559
+ adoptedPlanVar = `__lzs_${mid}`;
19560
+ const adoptedSlots = lazyRow.texts.map((t) => ({ id: t.slotId, kind: "text", path: [] }));
19561
+ lines.push(`${indent}const ${adoptedPlanVar} = ${claimPlanLiteral(adoptedSlots)}`);
19562
+ if (textPathVar) {
19563
+ freshPlanVar = `__lzsc_${mid}`;
19564
+ const freshSlots = lazyRow.texts.map((t, i) => ({
19565
+ id: t.slotId,
19566
+ kind: "text",
19567
+ path: [],
19568
+ pathExpr: `${textPathVar}[${i}]`
19569
+ }));
19570
+ lines.push(`${indent}const ${freshPlanVar} = ${claimPlanLiteral(freshSlots)}`);
19571
+ } else {
19572
+ freshPlanVar = adoptedPlanVar;
19573
+ }
19574
+ }
19575
+ if (hasRefs) {
19576
+ const parts = refParts(lazyRow, "__el", null, adoptedPlanVar, true);
19577
+ lines.push(`${indent}const ${claimVar} = (__e) => {`);
19578
+ if (parts.some((p) => p.includes("__el")))
19579
+ lines.push(`${indent} const __el = __e.primaryEl`);
19580
+ lines.push(`${indent} return [${parts.join(", ")}]`);
19581
+ lines.push(`${indent}}`);
19582
+ }
19583
+ const call = `mapArrayLazy(() => ${o.arrayExpr}, ${o.containerVar}, ${o.keyFn}, {`;
19584
+ lines.push(`${indent}${o.guardContainer ? `if (${o.containerVar}) ` : ""}${call}`);
19585
+ const b1 = `${indent} `;
19586
+ const b2 = `${indent} `;
19587
+ lines.push(`${b1}createRow: (__e, ${o.indexParam}) => {`);
19588
+ lines.push(`${b2}const ${paramHead} = () => __e.item`);
19589
+ const cloneExpr = useHoisted ? hoistedCloneExpr(tplVar, o.skeletonTemplate) : `(() => { ${emitTemplateCloneInline(o.template)} })()`;
19590
+ lines.push(`${b2}const __el = ${cloneExpr}`);
19591
+ if (hasRefs) {
19592
+ lines.push(`${b2}const __r = __e.refs = [${refParts(lazyRow, "__el", useHoisted ? paths ?? null : null, freshPlanVar).join(", ")}]`);
19593
+ }
19594
+ if (hasBindings) {
19595
+ lines.push(`${b2}const __l = __e.last = []`);
19596
+ for (const a of lazyRow.attrs)
19597
+ emitAttrBinding(lines, b2, a, "create");
19598
+ const createDoor = `__r[${lazyRow.writerIndex}]`;
19599
+ for (const t of lazyRow.texts)
19600
+ emitTextBinding(lines, b2, t, createDoor, "create", rwDoor);
19601
+ }
19602
+ lines.push(`${b2}return __el`);
19603
+ lines.push(`${b1}},`);
19604
+ const itemAttrs = lazyRow.attrs.filter((a) => a.readsItem);
19605
+ const itemTexts = lazyRow.texts.filter((t) => t.readsItem);
19606
+ if (itemAttrs.length === 0 && itemTexts.length === 0) {
19607
+ lines.push(`${b1}applyItem: () => {},`);
19608
+ } else {
19609
+ lines.push(`${b1}applyItem: (__e) => {`);
19610
+ lines.push(`${b2}const ${paramHead} = () => __e.item`);
19611
+ lines.push(`${b2}const __r = __e.refs ?? (__e.refs = ${claimVar}(__e))`);
19612
+ lines.push(`${b2}const __l = __e.last ?? (__e.last = [])`);
19613
+ for (const a of itemAttrs)
19614
+ emitAttrBinding(lines, b2, a, "item");
19615
+ if (itemTexts.length > 0) {
19616
+ lines.push(`${b2}const __d = ${doorAccess(lazyRow, lazyRow.writerIndex, adoptedPlanVar)}`);
19617
+ for (const t of itemTexts)
19618
+ emitTextBinding(lines, b2, t, "__d", "item", rwDoor);
19619
+ }
19620
+ lines.push(`${b1}},`);
19621
+ }
19622
+ const outerAttrs = lazyRow.attrs.filter((a) => a.readsOuter);
19623
+ const outerTexts = lazyRow.texts.filter((t) => t.readsOuter);
19624
+ if (outerAttrs.length > 0 || outerTexts.length > 0) {
19625
+ const b3 = `${indent} `;
19626
+ lines.push(`${b1}applyOuter: (__es, __seed) => {`);
19627
+ for (const g of lazyRow.outerPrimeGetters)
19628
+ lines.push(`${b2}${g}()`);
19629
+ lines.push(`${b2}for (const __e of __es) {`);
19630
+ lines.push(`${b3}const ${paramHead} = () => __e.item`);
19631
+ lines.push(`${b3}const __r = __e.refs ?? (__e.refs = ${claimVar}(__e))`);
19632
+ lines.push(`${b3}const __l = __e.last ?? (__e.last = [])`);
19633
+ for (const a of outerAttrs)
19634
+ emitAttrBinding(lines, b3, a, "outer");
19635
+ if (outerTexts.length > 0) {
19636
+ lines.push(`${b3}const __d = ${doorAccess(lazyRow, lazyRow.writerIndex, adoptedPlanVar)}`);
19637
+ for (const t of outerTexts)
19638
+ emitTextBinding(lines, b3, t, "__d", "outer", rwDoor);
19639
+ }
19640
+ lines.push(`${b2}}`);
19641
+ lines.push(`${b1}},`);
19642
+ }
19643
+ lines.push(`${indent}}, '${o.markerId}')`);
19644
+ }
19645
+ function refParts(lazyRow, elVar, skeletonPaths, planVar, deferDoor = false) {
19646
+ const parts = [];
19647
+ for (const slotId of lazyRow.attrSlotIds) {
19648
+ const path = skeletonPaths?.elementPaths.get(slotId);
19649
+ parts.push(path ? pathExpr(elVar, path) : `qsa(${elVar}, '[bf="${slotId}"]')`);
19650
+ }
19651
+ if (lazyRow.texts.length > 0) {
19652
+ parts.push(deferDoor ? "null" : `${doorCtor(lazyRow)}(${elVar}, ${planVar})`);
19653
+ }
19654
+ return parts;
19655
+ }
19656
+ function doorCtor(lazyRow) {
19657
+ return lazyRow.textNeedsRead ? "lazyClaimSlots" : "lazySlots";
19658
+ }
19659
+ function doorAccess(lazyRow, writerIndex, adoptedPlanVar) {
19660
+ const slot = `__r[${writerIndex}]`;
19661
+ return `${slot} ?? (${slot} = ${doorCtor(lazyRow)}(__e.primaryEl, ${adoptedPlanVar}))`;
19662
+ }
19663
+ function dedupGuard(ordinal) {
19664
+ return `!(${ordinal} in __l) || !Object.is(__l[${ordinal}], __x)`;
19665
+ }
19666
+ function emitAttrBinding(lines, ind, a, mode) {
19667
+ lines.push(`${ind}{ const __t = __r[${a.refIndex}]`);
19668
+ lines.push(`${ind}if (__t) {`);
19669
+ lines.push(`${ind} const __x = ${a.wrappedExpression}`);
19670
+ const guard = mode === "create" ? null : mode === "item" ? dedupGuard(a.ordinal) : `__seed ? (${seedDiffersExpr("__t", a)}) : (${dedupGuard(a.ordinal)})`;
19671
+ const writeIndent = guard ? `${ind} ` : `${ind} `;
19672
+ if (guard)
19673
+ lines.push(`${ind} if (${guard}) {`);
19674
+ for (const stmt of emitAttrUpdate("__t", a.attrName, "__x", a.meta)) {
19675
+ lines.push(`${writeIndent}${stmt}`);
19676
+ }
19677
+ if (guard)
19678
+ lines.push(`${ind} }`);
19679
+ lines.push(`${ind} __l[${a.ordinal}] = __x`);
19680
+ lines.push(`${ind}} }`);
19681
+ }
19682
+ function emitTextBinding(lines, ind, t, doorExpr, mode, rwDoor) {
19683
+ lines.push(`${ind}{ const __x = ${t.wrappedExpression}`);
19684
+ const writeOf = (valueExpr) => rwDoor ? `${doorExpr}.write('${t.slotId}', ${valueExpr})` : `${doorExpr}('${t.slotId}', ${valueExpr})`;
19685
+ if (mode === "outer") {
19686
+ lines.push(`${ind}if (__seed) {`);
19687
+ lines.push(`${ind} const __s = textOrNode(__x)`);
19688
+ lines.push(`${ind} if (${doorExpr}.read('${t.slotId}') !== __s) ${writeOf("__s")}`);
19689
+ lines.push(`${ind}} else if (${dedupGuard(t.ordinal)}) ${writeOf("textOrNode(__x)")}`);
19690
+ } else if (mode === "item") {
19691
+ lines.push(`${ind}if (${dedupGuard(t.ordinal)}) ${writeOf("textOrNode(__x)")}`);
19692
+ } else {
19693
+ lines.push(`${ind}${writeOf("textOrNode(__x)")}`);
19694
+ }
19695
+ lines.push(`${ind}__l[${t.ordinal}] = __x }`);
19696
+ }
19697
+ function seedDiffersExpr(target, a) {
19698
+ const html = toHtmlAttrName(a.attrName);
19699
+ if (a.attrName === "dangerouslySetInnerHTML" || html === "dangerouslySetInnerHTML")
19700
+ return "true";
19701
+ if (html === "style")
19702
+ return `${target}.getAttribute('style') !== styleToCss(__x)`;
19703
+ if (html === "class")
19704
+ return `${target}.getAttribute('class') !== (__x != null ? String(__x) : null)`;
19705
+ if (html === "value")
19706
+ return `${target}.value !== String(__x)`;
19707
+ if (isBooleanAttr(html))
19708
+ return `${target}.${html} !== !!(__x)`;
19709
+ if (a.meta.presenceOrUndefined) {
19710
+ const written = html.startsWith("aria-") ? "true" : "";
19711
+ return `${target}.getAttribute('${html}') !== (__x ? '${written}' : null)`;
19712
+ }
19713
+ return `${target}.getAttribute('${html}') !== (__x != null ? String(__x) : null)`;
19714
+ }
19715
+
19063
19716
  // src/ir-to-client-js/control-flow/stringify/loop.ts
19064
19717
  function emitLoopChildRefs(lines, refs, opts) {
19065
19718
  if (refs.length === 0)
@@ -19074,22 +19727,6 @@ function emitLoopChildRefs(lines, refs, opts) {
19074
19727
  lines.push(`${indent}if (${varName}) ${emitRefCall(ref.callback, varName)} }`);
19075
19728
  }
19076
19729
  }
19077
- function emitPreambleRegionEffects(lines, regions, mapPreambleWrapped, opts) {
19078
- if (regions.length === 0)
19079
- return;
19080
- const { indent, elVar } = opts;
19081
- for (const region of regions) {
19082
- const v = varSlotId(region.slotId);
19083
- lines.push(`${indent}{ let __last_${v}`);
19084
- lines.push(`${indent}createEffect(() => {`);
19085
- if (mapPreambleWrapped)
19086
- lines.push(`${indent} ${mapPreambleWrapped}`);
19087
- lines.push(`${indent} const __html_${v} = ${region.valueExpr}`);
19088
- lines.push(`${indent} if (__last_${v} === undefined) { __last_${v} = __html_${v}; return }`);
19089
- lines.push(`${indent} if (__html_${v} !== __last_${v}) { __last_${v} = __html_${v}; patchSlotRange(${elVar}, '${region.slotId}', __html_${v}) }`);
19090
- lines.push(`${indent}}) }`);
19091
- }
19092
- }
19093
19730
  function stringifyLoop(lines, plan) {
19094
19731
  switch (plan.kind) {
19095
19732
  case "static":
@@ -19153,6 +19790,23 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
19153
19790
  stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr);
19154
19791
  return;
19155
19792
  }
19793
+ if (plan.lazyRow) {
19794
+ stringifyLazyRowLoop(lines, {
19795
+ indent: topIndent,
19796
+ containerVar,
19797
+ guardContainer: false,
19798
+ markerId,
19799
+ arrayExpr,
19800
+ keyFn,
19801
+ paramHead,
19802
+ indexParam,
19803
+ template,
19804
+ skeletonTemplate,
19805
+ skeletonPaths: plan.skeletonPaths,
19806
+ lazyRow: plan.lazyRow
19807
+ });
19808
+ return;
19809
+ }
19156
19810
  const hoistedTpl = !bodyIsMultiRoot && skeletonTemplate ? skeletonTemplate : null;
19157
19811
  const tplVar = `__tpl_${markerId.replace(/[^A-Za-z0-9_$]/g, "_")}`;
19158
19812
  if (hoistedTpl) {
@@ -19188,26 +19842,34 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
19188
19842
  ...reactiveEffects?.attrSlots.map((s) => s.slotId) ?? [],
19189
19843
  ...childRefs.map((r) => r.childSlotId)
19190
19844
  ];
19191
- const textSlotIds = reactiveEffects?.outerTexts.map((t) => t.slotId) ?? [];
19192
- if (elementSlotIds.length > 0 || textSlotIds.length > 0) {
19193
- const built = buildSkeletonPathPlan(plan.skeletonPaths, "__el", { elementSlotIds, textSlotIds });
19845
+ if (elementSlotIds.length > 0) {
19846
+ const built = buildSkeletonPathPlan(plan.skeletonPaths, "__el", { elementSlotIds, textSlotIds: [] });
19194
19847
  if (built.arrayElems.length > 0) {
19195
19848
  pathPlan = built;
19196
19849
  lines.push(`${bodyIndent}const __p = __existing ? null : [${built.arrayElems.join(", ")}]`);
19197
19850
  }
19198
19851
  }
19199
19852
  }
19200
- if (reactiveEffects !== null) {
19853
+ const textClaimPathExprs = new Map;
19854
+ if (hoistedTpl && plan.skeletonPaths) {
19855
+ for (const text of reactiveEffects?.outerTexts ?? []) {
19856
+ const path = plan.skeletonPaths.textMarkerPaths.get(text.slotId);
19857
+ if (path)
19858
+ textClaimPathExprs.set(text.slotId, `__existing ? [] : [${path.join(", ")}]`);
19859
+ }
19860
+ }
19861
+ if (reactiveEffects !== null || preambleRegions.length > 0) {
19201
19862
  stringifyReactiveEffects(lines, reactiveEffects, {
19202
19863
  indent: bodyIndent,
19203
19864
  elVar: "__el",
19204
19865
  bodyIsMultiRoot,
19205
19866
  elementIndexBySlot: pathPlan?.elementIndexBySlot,
19206
- textIndexBySlot: pathPlan?.textIndexBySlot
19867
+ textClaimPathExprs,
19868
+ preambleRegions,
19869
+ mapPreambleWrapped
19207
19870
  });
19208
19871
  }
19209
19872
  emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot });
19210
- emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: bodyIndent, elVar: "__el" });
19211
19873
  lines.push(`${bodyIndent}return __el`);
19212
19874
  lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
19213
19875
  }
@@ -19316,10 +19978,13 @@ function stringifyStaticLoop(lines, plan) {
19316
19978
  }
19317
19979
  lines.push(` }`);
19318
19980
  }
19319
- for (const text of texts) {
19320
- const vn = `__rt_${varSlotId(text.slotId)}`;
19321
- lines.push(` { const [${vn}] = $t(__iterEl, '${text.slotId}')`);
19322
- lines.push(` if (${vn}) createEffect(() => { ${vn}.textContent = String(${text.expression}) }${profileBindingId(pc, text.slotId)}) }`);
19981
+ if (texts.length > 0) {
19982
+ const slots = texts.map((t) => ({ id: t.slotId, kind: "text", path: [] }));
19983
+ const writer = claimWriterVarName(slots, varSlotId);
19984
+ lines.push(` const ${writer} = lazySlots(__iterEl, ${claimPlanLiteral(slots)})`);
19985
+ for (const text of texts) {
19986
+ lines.push(` createEffect(() => { ${writer}('${text.slotId}', String(${text.expression})) }${profileBindingId(pc, text.slotId)})`);
19987
+ }
19323
19988
  }
19324
19989
  emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__iterEl", bodyIsMultiRoot: false });
19325
19990
  lines.push(` }`);
@@ -19374,13 +20039,18 @@ function emitReactive(lines, inner, indent, pc) {
19374
20039
  if (inner.childLevels.length > 0) {
19375
20040
  stringifyInnerLoops(lines, inner.childLevels, `${indent} `, pc);
19376
20041
  }
19377
- for (const text of emit.reactiveTexts) {
20042
+ const conditionalTexts = emit.reactiveTexts.filter((t) => t.insideConditional);
20043
+ const plainTexts = emit.reactiveTexts.filter((t) => !t.insideConditional);
20044
+ for (const text of conditionalTexts) {
19378
20045
  const bf = profileBindingId(pc, text.slotId);
19379
- if (text.insideConditional) {
19380
- lines.push(`${indent} createEffect(() => { const [__rt] = $t(__innerEl${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) }${bf})`);
19381
- } else {
19382
- lines.push(`${indent} { const [__rt] = $t(__innerEl${uid}, '${text.slotId}')`);
19383
- lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }${bf}) }`);
20046
+ lines.push(`${indent} createEffect(() => { claimSlots(__innerEl${uid}, [{ id: '${text.slotId}', kind: 'text', path: [] }]).write('${text.slotId}', String(${text.wrappedExpression})) }${bf})`);
20047
+ }
20048
+ if (plainTexts.length > 0) {
20049
+ const slots = plainTexts.map((t) => ({ id: t.slotId, kind: "text", path: [] }));
20050
+ const writer = claimWriterVarName(slots, varSlotId);
20051
+ lines.push(`${indent} const ${writer} = lazySlots(__innerEl${uid}, ${claimPlanLiteral(slots)})`);
20052
+ for (const text of plainTexts) {
20053
+ lines.push(`${indent} createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${profileBindingId(pc, text.slotId)})`);
19384
20054
  }
19385
20055
  }
19386
20056
  for (const attr of emit.reactiveAttrs) {
@@ -19730,6 +20400,23 @@ function emitPlain(lines, plan) {
19730
20400
  stringifyEventDelegation(lines, eventDelegation);
19731
20401
  return;
19732
20402
  }
20403
+ if (plan.lazyRow) {
20404
+ stringifyLazyRowLoop(lines, {
20405
+ indent: " ",
20406
+ containerVar,
20407
+ guardContainer: true,
20408
+ markerId,
20409
+ arrayExpr,
20410
+ keyFn,
20411
+ paramHead,
20412
+ indexParam,
20413
+ template,
20414
+ lazyRow: plan.lazyRow
20415
+ });
20416
+ lines.push(` }))`);
20417
+ stringifyEventDelegation(lines, eventDelegation);
20418
+ return;
20419
+ }
19733
20420
  if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0 && preambleRegions.length === 0) {
19734
20421
  const cloneExpr = emitTemplateCloneInline(template);
19735
20422
  if (mapPreambleWrapped) {
@@ -19751,11 +20438,16 @@ function emitPlain(lines, plan) {
19751
20438
  indent: " ",
19752
20439
  singleRootLayout: "inline"
19753
20440
  });
19754
- if (reactiveEffects !== null) {
19755
- stringifyReactiveEffects(lines, reactiveEffects, { indent: " ", elVar: "__el", bodyIsMultiRoot });
20441
+ if (reactiveEffects !== null || preambleRegions.length > 0) {
20442
+ stringifyReactiveEffects(lines, reactiveEffects, {
20443
+ indent: " ",
20444
+ elVar: "__el",
20445
+ bodyIsMultiRoot,
20446
+ preambleRegions,
20447
+ mapPreambleWrapped
20448
+ });
19756
20449
  }
19757
20450
  emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__el", bodyIsMultiRoot });
19758
- emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: " ", elVar: "__el" });
19759
20451
  lines.push(` return __el`);
19760
20452
  lines.push(` }, '${markerId}'${loopBfId})`);
19761
20453
  }
@@ -19841,13 +20533,13 @@ function emitArmBody(lines, body, mode, indent, profileComponentName) {
19841
20533
  }
19842
20534
  lines.push(`${indent}} }`);
19843
20535
  }
19844
- for (const te of body.textEffects) {
19845
- const v = varSlotId(te.slotId);
19846
- lines.push(`${indent}let __anchor_${v} = $t(__branchScope, '${te.slotId}')[0]`);
19847
- lines.push(`${indent}__disposers.push(createDisposableEffect(() => {`);
19848
- lines.push(`${indent} const __val = ${te.expression}`);
19849
- lines.push(`${indent} __anchor_${v} = __bfText(__anchor_${v}, __val)`);
19850
- lines.push(`${indent}}${bindingBfId(te.slotId)}))`);
20536
+ if (body.textEffects.length > 0) {
20537
+ const slots = body.textEffects.map((te) => ({ id: te.slotId, kind: "markup", path: [] }));
20538
+ const writer = claimWriterVarName(slots, varSlotId);
20539
+ lines.push(`${indent}const ${writer} = lazySlots(__branchScope, ${claimPlanLiteral(slots)})`);
20540
+ for (const te of body.textEffects) {
20541
+ lines.push(`${indent}__disposers.push(createDisposableEffect(() => { ${writer}('${te.slotId}', escapeTextOrNode(${te.expression})) }${bindingBfId(te.slotId)}))`);
20542
+ }
19851
20543
  }
19852
20544
  if (body.loops.length > 0) {
19853
20545
  stringifyBranchLoops(lines, body.loops);
@@ -19923,7 +20615,7 @@ function buildComponentLoopPlan(elem, profileComponentName) {
19923
20615
  // src/ir-to-client-js/control-flow/plan/build-loop.ts
19924
20616
  function buildLoopPlan(elem, opts) {
19925
20617
  if (elem.bodyIsItemConditional) {
19926
- return buildPlainLoopPlan(elem, opts.profileComponentName);
20618
+ return buildPlainLoopPlan(elem, opts.profileComponentName, opts.lazyScope);
19927
20619
  }
19928
20620
  if (elem.isStaticArray) {
19929
20621
  return buildStaticLoopPlan(elem, opts.unsafeLocalNames, opts.profileComponentName);
@@ -19935,9 +20627,9 @@ function buildLoopPlan(elem, opts) {
19935
20627
  if (elem.childComponent) {
19936
20628
  return buildComponentLoopPlan(elem, opts.profileComponentName);
19937
20629
  }
19938
- return buildPlainLoopPlan(elem, opts.profileComponentName);
20630
+ return buildPlainLoopPlan(elem, opts.profileComponentName, opts.lazyScope);
19939
20631
  }
19940
- function buildPlainLoopPlan(elem, profileComponentName) {
20632
+ function buildPlainLoopPlan(elem, profileComponentName, lazyScope) {
19941
20633
  const wrap = (expr) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings);
19942
20634
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings);
19943
20635
  const hasReactive2 = elem.bindings.reactiveAttrs.length > 0 || elem.bindings.reactiveTexts.length > 0 || elem.bindings.conditionals.length > 0;
@@ -19964,27 +20656,43 @@ function buildPlainLoopPlan(elem, profileComponentName) {
19964
20656
  preambleRegions: []
19965
20657
  };
19966
20658
  }
20659
+ const arrayExpr = buildChainedArrayExpr(elem);
20660
+ const indexParam = elem.index || "__idx";
20661
+ const mapPreambleWrapped = elem.preamble ? renderPreamble(elem.preamble, {
20662
+ transformJs: wrap,
20663
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
20664
+ }) : "";
20665
+ const preambleRegions = buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings);
19967
20666
  return {
19968
20667
  kind: "plain",
19969
20668
  rowConstruction: "string-template",
19970
20669
  containerVar: `_${varSlotId(elem.slotId)}`,
19971
20670
  markerId: elem.markerId,
19972
20671
  profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
19973
- arrayExpr: buildChainedArrayExpr(elem),
20672
+ arrayExpr,
19974
20673
  keyFn: loopKeyFn(elem),
19975
20674
  paramHead,
19976
20675
  paramUnwrap,
19977
- indexParam: elem.index || "__idx",
19978
- mapPreambleWrapped: elem.preamble ? renderPreamble(elem.preamble, {
19979
- transformJs: wrap,
19980
- renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
19981
- }) : "",
20676
+ indexParam,
20677
+ lazyRow: buildLazyRowPlan({
20678
+ loop: elem,
20679
+ arrayExpr,
20680
+ indexParam,
20681
+ paramUnwrap,
20682
+ mapPreambleWrapped,
20683
+ preambleRegionCount: preambleRegions.length,
20684
+ callSite: "plain",
20685
+ flatMapLeafItem: false,
20686
+ anchored: elem.bodyIsItemConditional ?? false,
20687
+ scope: lazyScope
20688
+ }) ?? undefined,
20689
+ mapPreambleWrapped,
19982
20690
  template: elem.template,
19983
20691
  skeletonTemplate: elem.skeletonTemplate,
19984
20692
  skeletonPaths: elem.skeletonPaths,
19985
20693
  reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
19986
20694
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
19987
- preambleRegions: buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings),
20695
+ preambleRegions,
19988
20696
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
19989
20697
  anchored: elem.bodyIsItemConditional ?? false,
19990
20698
  anchorKeyExpr: elem.key ? wrap(elem.key) : elem.index || "__idx"
@@ -20039,7 +20747,7 @@ function buildStaticLoopMaterialize(elem, unsafeLocalNames) {
20039
20747
  function emitConditionalUpdates(lines, ctx) {
20040
20748
  const profileComponentName = ctx.profile ? ctx.componentName : undefined;
20041
20749
  for (const elem of ctx.conditionalElements) {
20042
- const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "dom", profileComponentName });
20750
+ const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "dom", profileComponentName, lazyScope: buildLazyRowScopeInfo(ctx) });
20043
20751
  stringifyInsert(lines, plan, { leadingIndent: " ", bodyIndent: " " });
20044
20752
  lines.push("");
20045
20753
  }
@@ -20047,17 +20755,19 @@ function emitConditionalUpdates(lines, ctx) {
20047
20755
  function emitClientOnlyConditionals(lines, ctx) {
20048
20756
  const profileComponentName = ctx.profile ? ctx.componentName : undefined;
20049
20757
  for (const elem of ctx.clientOnlyConditionals) {
20050
- const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "raw", profileComponentName });
20758
+ const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "raw", profileComponentName, lazyScope: buildLazyRowScopeInfo(ctx) });
20051
20759
  lines.push(` // @client conditional: ${elem.slotId}`);
20052
20760
  stringifyInsert(lines, plan, { leadingIndent: " ", bodyIndent: " " });
20053
20761
  lines.push("");
20054
20762
  }
20055
20763
  }
20056
20764
  function emitLoopUpdates(lines, ctx, unsafeLocalNames) {
20765
+ const lazyScope = buildLazyRowScopeInfo(ctx);
20057
20766
  for (const elem of ctx.loopElements) {
20058
20767
  const plan = buildLoopPlan(elem, {
20059
20768
  unsafeLocalNames,
20060
- profileComponentName: ctx.profile ? ctx.componentName : undefined
20769
+ profileComponentName: ctx.profile ? ctx.componentName : undefined,
20770
+ lazyScope
20061
20771
  });
20062
20772
  internalInvariant(!(elem.preamble && elem.preamble.builderNames.length > 0 && plan.rowConstruction === "dom-ops"), `loop variant '${plan.kind}' declares dom-ops row construction but received a JSX-bearing preamble — add a Phase-1 refusal (or wire renderPreamble support) for this shape`);
20063
20773
  stringifyLoop(lines, plan);
@@ -20079,7 +20789,6 @@ function emitLoopEventDelegation(lines, elem, kind, profileComponentName) {
20079
20789
  // src/ir-to-client-js/element-refs.ts
20080
20790
  function generateElementRefs(ctx) {
20081
20791
  const regularSlots = new Set;
20082
- const textSlots = new Set;
20083
20792
  const componentSlots = new Set;
20084
20793
  const conditionalSlotIds = collectConditionalSlotIds(ctx);
20085
20794
  for (const elem of ctx.interactiveElements) {
@@ -20087,11 +20796,6 @@ function generateElementRefs(ctx) {
20087
20796
  regularSlots.add(elem.slotId);
20088
20797
  }
20089
20798
  }
20090
- for (const elem of ctx.dynamicElements) {
20091
- if (!elem.insideConditional) {
20092
- textSlots.add(elem.slotId);
20093
- }
20094
- }
20095
20799
  for (const elem of ctx.conditionalElements) {
20096
20800
  regularSlots.add(elem.slotId);
20097
20801
  }
@@ -20120,11 +20824,10 @@ function generateElementRefs(ctx) {
20120
20824
  for (const slotId of componentSlots) {
20121
20825
  regularSlots.delete(slotId);
20122
20826
  }
20123
- if (regularSlots.size === 0 && textSlots.size === 0 && componentSlots.size === 0)
20827
+ if (regularSlots.size === 0 && componentSlots.size === 0)
20124
20828
  return "";
20125
20829
  const refLines = [];
20126
20830
  emitSlotRefs(refLines, [...regularSlots], "$");
20127
- emitSlotRefs(refLines, [...textSlots], "$t");
20128
20831
  emitSlotRefs(refLines, [...componentSlots], "$c");
20129
20832
  return refLines.join(`
20130
20833
  `);
@@ -20680,6 +21383,111 @@ ${externalImports.join(`
20680
21383
  return generatedCode.replace(IMPORT_PLACEHOLDER, allImports);
20681
21384
  }
20682
21385
 
21386
+ // src/ir-to-client-js/client-only-elision.ts
21387
+ function decideClientOnlyElision(root) {
21388
+ walkNode(root, [], new Set, { bailed: false });
21389
+ }
21390
+ function walkNode(node, path, forceCloseAncestors, state) {
21391
+ if (state.bailed)
21392
+ return;
21393
+ if (node.type === "element") {
21394
+ if (!elementIsPathSafe(node.tag, flattenSkeletonChildren(node.children))) {
21395
+ state.bailed = true;
21396
+ return;
21397
+ }
21398
+ const groupIdx = skeletonForceCloseGroup(node.tag);
21399
+ if (groupIdx >= 0 && forceCloseAncestors.has(groupIdx)) {
21400
+ state.bailed = true;
21401
+ return;
21402
+ }
21403
+ const nextAncestors = groupIdx >= 0 ? new Set([...forceCloseAncestors, groupIdx]) : forceCloseAncestors;
21404
+ walkChildren(flattenSkeletonChildren(node.children), path, nextAncestors, state);
21405
+ } else if (node.type === "fragment") {
21406
+ walkChildren(flattenSkeletonChildren(node.children), path, forceCloseAncestors, state);
21407
+ }
21408
+ }
21409
+ function walkChildren(children, parentPath, forceCloseAncestors, state) {
21410
+ let frozen = false;
21411
+ let idx = 0;
21412
+ let pendingText = false;
21413
+ for (let i = 0;i < children.length; i++) {
21414
+ if (state.bailed)
21415
+ return;
21416
+ const child = children[i];
21417
+ switch (child.type) {
21418
+ case "text": {
21419
+ if (child.value === "")
21420
+ continue;
21421
+ if (!pendingText)
21422
+ idx += 1;
21423
+ pendingText = true;
21424
+ continue;
21425
+ }
21426
+ case "expression": {
21427
+ if (child.expr === "null" || child.expr === "undefined")
21428
+ continue;
21429
+ if (child.clientOnly && child.slotId) {
21430
+ const adjacent = isTextLike(children[i - 1]) || isTextLike(children[i + 1]);
21431
+ if (!frozen && !adjacent) {
21432
+ markElided(child, [...parentPath, idx]);
21433
+ frozen = true;
21434
+ } else {
21435
+ frozen = true;
21436
+ }
21437
+ idx += 1;
21438
+ pendingText = false;
21439
+ continue;
21440
+ }
21441
+ frozen = true;
21442
+ idx += 1;
21443
+ pendingText = false;
21444
+ continue;
21445
+ }
21446
+ case "element": {
21447
+ if (!elementIsPathSafe(child.tag, flattenSkeletonChildren(child.children))) {
21448
+ state.bailed = true;
21449
+ return;
21450
+ }
21451
+ if (!frozen) {
21452
+ walkNode(child, [...parentPath, idx], forceCloseAncestors, state);
21453
+ }
21454
+ idx += 1;
21455
+ pendingText = false;
21456
+ continue;
21457
+ }
21458
+ case "fragment":
21459
+ continue;
21460
+ default:
21461
+ frozen = true;
21462
+ idx += 1;
21463
+ pendingText = false;
21464
+ continue;
21465
+ }
21466
+ }
21467
+ }
21468
+ function isTextLike(node) {
21469
+ if (!node)
21470
+ return false;
21471
+ if (node.type === "text")
21472
+ return node.value !== "";
21473
+ if (node.type === "expression")
21474
+ return node.expr !== "null" && node.expr !== "undefined";
21475
+ return false;
21476
+ }
21477
+ function markElided(expr, path) {
21478
+ expr.markerless = true;
21479
+ expr.elidedPath = path;
21480
+ }
21481
+ function elementIsPathSafe(tag, flatChildren) {
21482
+ if (SKELETON_PATH_HAZARD_TAGS.has(tag))
21483
+ return false;
21484
+ if (VOID_ELEMENTS.has(tag) && flatChildren.length > 0)
21485
+ return false;
21486
+ if (tag === "tr" && hasForeignTableRowContent(flatChildren))
21487
+ return false;
21488
+ return true;
21489
+ }
21490
+
20683
21491
  // src/css-layer-prefixer.ts
20684
21492
  function prefixClass(cls, layerName) {
20685
21493
  if (!cls || cls.startsWith("layer-"))
@@ -21972,7 +22780,7 @@ function checkRichTypeMethodCalls(root, metadata, errors) {
21972
22780
  return;
21973
22781
  const matchers = prepareLoweringMatchers(metadata);
21974
22782
  const seen = new Set;
21975
- walkNode(root, metadata, EMPTY_BINDINGS2, matchers, errors, seen);
22783
+ walkNode2(root, metadata, EMPTY_BINDINGS2, matchers, errors, seen);
21976
22784
  }
21977
22785
  function isLoweringClaimed(matchers, callee, args) {
21978
22786
  return matchers.some((m) => m(callee, args) !== null);
@@ -22109,7 +22917,7 @@ function walkAttrValue(value, clientOnly, loc, meta, bindings, matchers, errors,
22109
22917
  walkTemplateParts(value.parts, loc, meta, bindings, matchers, errors, seen);
22110
22918
  }
22111
22919
  }
22112
- function walkNode(node, meta, bindings, matchers, errors, seen) {
22920
+ function walkNode2(node, meta, bindings, matchers, errors, seen) {
22113
22921
  if (node.type === "expression") {
22114
22922
  if (!node.clientOnly && node.parsed)
22115
22923
  checkExpr(node.parsed, node.loc, meta, bindings, matchers, errors, seen);
@@ -22135,12 +22943,12 @@ function walkNode(node, meta, bindings, matchers, errors, seen) {
22135
22943
  case "fragment":
22136
22944
  case "provider":
22137
22945
  for (const child of node.children)
22138
- walkNode(child, meta, bindings, matchers, errors, seen);
22946
+ walkNode2(child, meta, bindings, matchers, errors, seen);
22139
22947
  break;
22140
22948
  case "async":
22141
- walkNode(node.fallback, meta, bindings, matchers, errors, seen);
22949
+ walkNode2(node.fallback, meta, bindings, matchers, errors, seen);
22142
22950
  for (const child of node.children)
22143
- walkNode(child, meta, bindings, matchers, errors, seen);
22951
+ walkNode2(child, meta, bindings, matchers, errors, seen);
22144
22952
  break;
22145
22953
  case "loop": {
22146
22954
  if (node.clientOnly)
@@ -22153,35 +22961,35 @@ function walkNode(node, meta, bindings, matchers, errors, seen) {
22153
22961
  if (node.index)
22154
22962
  loopBindings.set(node.index, null);
22155
22963
  for (const child of node.children)
22156
- walkNode(child, meta, loopBindings, matchers, errors, seen);
22964
+ walkNode2(child, meta, loopBindings, matchers, errors, seen);
22157
22965
  if (node.childComponent) {
22158
22966
  for (const child of node.childComponent.children)
22159
- walkNode(child, meta, loopBindings, matchers, errors, seen);
22967
+ walkNode2(child, meta, loopBindings, matchers, errors, seen);
22160
22968
  }
22161
22969
  for (const nested of node.nestedComponents ?? []) {
22162
22970
  for (const child of nested.children)
22163
- walkNode(child, meta, loopBindings, matchers, errors, seen);
22971
+ walkNode2(child, meta, loopBindings, matchers, errors, seen);
22164
22972
  }
22165
22973
  for (const seg of node.flatMapCallback?.segments ?? []) {
22166
22974
  if (seg.kind === "jsx")
22167
- walkNode(seg.ir, meta, loopBindings, matchers, errors, seen);
22975
+ walkNode2(seg.ir, meta, loopBindings, matchers, errors, seen);
22168
22976
  }
22169
22977
  for (const seg of node.preamble?.segments ?? []) {
22170
22978
  if (seg.kind === "jsx")
22171
- walkNode(seg.ir, meta, loopBindings, matchers, errors, seen);
22979
+ walkNode2(seg.ir, meta, loopBindings, matchers, errors, seen);
22172
22980
  }
22173
22981
  break;
22174
22982
  }
22175
22983
  case "conditional":
22176
22984
  if (node.clientOnly)
22177
22985
  break;
22178
- walkNode(node.whenTrue, meta, bindings, matchers, errors, seen);
22179
- walkNode(node.whenFalse, meta, bindings, matchers, errors, seen);
22986
+ walkNode2(node.whenTrue, meta, bindings, matchers, errors, seen);
22987
+ walkNode2(node.whenFalse, meta, bindings, matchers, errors, seen);
22180
22988
  break;
22181
22989
  case "if-statement":
22182
- walkNode(node.consequent, meta, bindings, matchers, errors, seen);
22990
+ walkNode2(node.consequent, meta, bindings, matchers, errors, seen);
22183
22991
  if (node.alternate)
22184
- walkNode(node.alternate, meta, bindings, matchers, errors, seen);
22992
+ walkNode2(node.alternate, meta, bindings, matchers, errors, seen);
22185
22993
  break;
22186
22994
  }
22187
22995
  }
@@ -22247,6 +23055,7 @@ function compileMultipleComponents(source, filePath, componentNames, options) {
22247
23055
  };
22248
23056
  componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
22249
23057
  checkRichTypeMethodCalls(componentIR.root, componentIR.metadata, errors);
23058
+ decideClientOnlyElision(componentIR.root);
22250
23059
  if (options.cssLayerPrefix) {
22251
23060
  applyCssLayerPrefix(componentIR, options.cssLayerPrefix);
22252
23061
  }
@@ -22586,6 +23395,7 @@ function compileJSX(source, filePath, options) {
22586
23395
  };
22587
23396
  componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
22588
23397
  checkRichTypeMethodCalls(componentIR.root, componentIR.metadata, errors);
23398
+ decideClientOnlyElision(componentIR.root);
22589
23399
  if (ctx.importedClientSignalNames.size > 0) {
22590
23400
  const sources = new Set;
22591
23401
  for (const imp of ctx.imports) {
@@ -26653,6 +27463,7 @@ export {
26653
27463
  diffStaticBudget,
26654
27464
  diffProfiles,
26655
27465
  describeFallback,
27466
+ decideClientOnlyElision,
26656
27467
  dangerousInnerHtmlMetacharViolation,
26657
27468
  dangerousInnerHtmlDiagnostic,
26658
27469
  createProgramForFile,