@barefootjs/jsx 0.27.0 → 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 (50) hide show
  1. package/dist/index.js +620 -26
  2. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +6 -0
  3. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
  4. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts +2 -1
  5. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
  6. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts +7 -0
  7. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts.map +1 -1
  8. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts +107 -0
  9. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -0
  10. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts +9 -1
  11. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  12. package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts +203 -0
  13. package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts.map +1 -0
  14. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +10 -0
  15. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/control-flow/stringify/branch-loop.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts +98 -0
  18. package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -0
  19. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +5 -0
  20. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  21. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
  23. package/dist/ir-to-client-js/imports.d.ts +2 -2
  24. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  25. package/package.json +2 -2
  26. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +162 -36
  27. package/src/__tests__/client-js-generation.test.ts +4 -1
  28. package/src/__tests__/composite-branch-loop.test.ts +12 -3
  29. package/src/__tests__/conditional-mapArray-key.test.ts +7 -1
  30. package/src/__tests__/create-selector.test.ts +21 -7
  31. package/src/__tests__/lazy-row-eligibility.test.ts +692 -0
  32. package/src/__tests__/loop-branch-bare-expression-reactive-text.test.ts +98 -0
  33. package/src/__tests__/loop-fallback-wrap.test.ts +31 -12
  34. package/src/__tests__/loop-hoisted-template.test.ts +10 -6
  35. package/src/__tests__/static-loop-csr-materialize.test.ts +6 -1
  36. package/src/ir-to-client-js/collect-elements.ts +48 -13
  37. package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +6 -0
  38. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +35 -12
  39. package/src/ir-to-client-js/control-flow/plan/build-insert.ts +9 -2
  40. package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +305 -0
  41. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +44 -12
  42. package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +440 -0
  43. package/src/ir-to-client-js/control-flow/plan/loop.ts +10 -0
  44. package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +22 -0
  45. package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +478 -0
  46. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +6 -1
  47. package/src/ir-to-client-js/control-flow/stringify/loop.ts +23 -0
  48. package/src/ir-to-client-js/control-flow.ts +7 -2
  49. package/src/ir-to-client-js/imports.ts +8 -2
  50. package/src/jsx-to-ir.ts +18 -1
@@ -2,9 +2,9 @@
2
2
  * Import detection and DOM import management.
3
3
  */
4
4
  import type { ComponentIR } from '../types.ts';
5
- export declare const RUNTIME_IMPORT_CANDIDATES: readonly ['createSignal', 'createMemo', 'createEffect', 'onCleanup', 'onMount', 'hydrate', 'insert', 'getLoopChildren', 'getLoopNodes', 'mapArray', 'mapArrayAnchored', 'patchLeaf', 'createDisposableEffect', 'createComponent', 'renderChild', 'registerComponent', 'registerTemplate', 'initChild', 'upsertChild', 'createPortal', 'provideContext', 'createContext', 'useContext', 'forwardProps', 'applyRestAttrs', 'splitProps', 'spreadAttrs', 'styleToCss', 'escapeAttr', 'escapeText', 'escapeTextOrNode', 'qsa', 'qsaItem', 'qsaChildScope', 'qsaChildScopes', 'upsertChildItem', '__slot', '__bfSlot', '__bfText', 'claimSlots', 'lazySlots', 'beginTurn', 'endTurn', 'date', 'formatDate'];
5
+ export declare const RUNTIME_IMPORT_CANDIDATES: readonly ['createSignal', 'createMemo', 'createEffect', 'onCleanup', 'onMount', 'hydrate', 'insert', 'getLoopChildren', 'getLoopNodes', 'mapArray', 'mapArrayAnchored', 'mapArrayLazy', 'patchLeaf', 'createDisposableEffect', 'createComponent', 'renderChild', 'registerComponent', 'registerTemplate', 'initChild', 'upsertChild', 'createPortal', 'provideContext', 'createContext', 'useContext', 'forwardProps', 'applyRestAttrs', 'splitProps', 'spreadAttrs', 'styleToCss', 'escapeAttr', 'escapeText', 'escapeTextOrNode', 'qsa', 'qsaItem', 'qsaChildScope', 'qsaChildScopes', 'upsertChildItem', '__slot', '__bfSlot', '__bfText', 'claimSlots', 'lazySlots', 'lazyClaimSlots', 'textOrNode', 'beginTurn', 'endTurn', 'date', 'formatDate'];
6
6
  /** @deprecated Use RUNTIME_IMPORT_CANDIDATES */
7
- export declare const DOM_IMPORT_CANDIDATES: readonly ["createSignal", "createMemo", "createEffect", "onCleanup", "onMount", "hydrate", "insert", "getLoopChildren", "getLoopNodes", "mapArray", "mapArrayAnchored", "patchLeaf", "createDisposableEffect", "createComponent", "renderChild", "registerComponent", "registerTemplate", "initChild", "upsertChild", "createPortal", "provideContext", "createContext", "useContext", "forwardProps", "applyRestAttrs", "splitProps", "spreadAttrs", "styleToCss", "escapeAttr", "escapeText", "escapeTextOrNode", "qsa", "qsaItem", "qsaChildScope", "qsaChildScopes", "upsertChildItem", "__slot", "__bfSlot", "__bfText", "claimSlots", "lazySlots", "beginTurn", "endTurn", "date", "formatDate"];
7
+ export declare const DOM_IMPORT_CANDIDATES: readonly ["createSignal", "createMemo", "createEffect", "onCleanup", "onMount", "hydrate", "insert", "getLoopChildren", "getLoopNodes", "mapArray", "mapArrayAnchored", "mapArrayLazy", "patchLeaf", "createDisposableEffect", "createComponent", "renderChild", "registerComponent", "registerTemplate", "initChild", "upsertChild", "createPortal", "provideContext", "createContext", "useContext", "forwardProps", "applyRestAttrs", "splitProps", "spreadAttrs", "styleToCss", "escapeAttr", "escapeText", "escapeTextOrNode", "qsa", "qsaItem", "qsaChildScope", "qsaChildScopes", "upsertChildItem", "__slot", "__bfSlot", "__bfText", "claimSlots", "lazySlots", "lazyClaimSlots", "textOrNode", "beginTurn", "endTurn", "date", "formatDate"];
8
8
  export declare const RUNTIME_MODULE = "@barefootjs/client/runtime";
9
9
  export declare const IMPORT_PLACEHOLDER = "/* __BAREFOOTJS_DOM_IMPORTS__ */";
10
10
  export declare const MODULE_CONSTANTS_PLACEHOLDER = "/* __MODULE_LEVEL_CONSTANTS__ */";
@@ -1 +1 @@
1
- {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/imports.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,aAAa,CAAA;AAItD,eAAO,MAAM,yBAAyB,YACpC,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EACpE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,WAAW,EAAE,wBAAwB,EAC7H,iBAAiB,EAAE,aAAa,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EACrG,cAAc,EACd,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAC/C,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAC3H,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAIxG,YAAY,EAAE,WAAW,EAEzB,WAAW,EAAE,SAAS,EAItB,MAAM,EAIN,YAAY,CACJ,CAAA;AAEV,gDAAgD;AAChD,eAAO,MAAM,qBAAqB,wqBAA4B,CAAA;AAE9D,eAAO,MAAM,cAAc,+BAA+B,CAAA;AAE1D,eAAO,MAAM,kBAAkB,qCAAqC,CAAA;AACpE,eAAO,MAAM,4BAA4B,qCAAqC,CAAA;AAE9E;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAqB3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,EAAE,CAiB/D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmCvH"}
1
+ {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/imports.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,aAAa,CAAA;AAItD,eAAO,MAAM,yBAAyB,YACpC,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EACpE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,cAAc,EAAE,WAAW,EAAE,wBAAwB,EAC7I,iBAAiB,EAAE,aAAa,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EACrG,cAAc,EACd,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAC/C,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAC3H,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAUxG,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAEzD,WAAW,EAAE,SAAS,EAItB,MAAM,EAIN,YAAY,CACJ,CAAA;AAEV,gDAAgD;AAChD,eAAO,MAAM,qBAAqB,wtBAA4B,CAAA;AAE9D,eAAO,MAAM,cAAc,+BAA+B,CAAA;AAE1D,eAAO,MAAM,kBAAkB,qCAAqC,CAAA;AACpE,eAAO,MAAM,4BAA4B,qCAAqC,CAAA;AAE9E;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAqB3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,EAAE,CAiB/D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmCvH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/jsx",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,7 +53,7 @@
53
53
  "directory": "packages/jsx"
54
54
  },
55
55
  "dependencies": {
56
- "@barefootjs/shared": "0.27.0"
56
+ "@barefootjs/shared": "0.28.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@barefootjs/client": ">=0.2.0",
@@ -4380,7 +4380,7 @@ export function Example(_p, __bfKey) { return createComponent('Example', _p, __b
4380
4380
  `;
4381
4381
 
4382
4382
  exports[`docs/core/adapters/hono-adapter.md doc-examples L190 — (no label) 1`] = `
4383
- "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArray } from '@barefootjs/client/runtime'
4383
+ "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
4384
4384
 
4385
4385
  export function initTodoItem(__scope, _p = {}) {
4386
4386
  if (!__scope) return
@@ -4431,11 +4431,32 @@ export function initExample(__scope, _p = {}) {
4431
4431
 
4432
4432
  const __tpl_l0 = document.createElement('template')
4433
4433
  __tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
4434
- mapArray(() => items(), _s1, (item) => String(item), (item, __idx, __existing) => {
4435
- const __el = __existing ?? __tpl_l0.content.firstElementChild.cloneNode(true)
4436
- const __bfw_s0 = lazySlots(__el, [{ id: 's0', kind: 'text', path: __existing ? [] : [0] }])
4437
- createEffect(() => { __bfw_s0('s0', String(item())) })
4438
- return __el
4434
+ const __lzp_l0 = [[0]]
4435
+ const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
4436
+ const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
4437
+ const __lzc_l0 = (__e) => {
4438
+ return [null]
4439
+ }
4440
+ mapArrayLazy(() => items(), _s1, (item) => String(item), {
4441
+ createRow: (__e, __idx) => {
4442
+ const item = () => __e.item
4443
+ const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
4444
+ const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
4445
+ const __l = __e.last = []
4446
+ { const __x = item()
4447
+ __r[0]('s0', textOrNode(__x))
4448
+ __l[0] = __x }
4449
+ return __el
4450
+ },
4451
+ applyItem: (__e) => {
4452
+ const item = () => __e.item
4453
+ const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
4454
+ const __l = __e.last ?? (__e.last = [])
4455
+ const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
4456
+ { const __x = item()
4457
+ if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
4458
+ __l[0] = __x }
4459
+ },
4439
4460
  }, 'l0')
4440
4461
 
4441
4462
  }
@@ -4494,7 +4515,7 @@ export function Counter(_p, __bfKey) { return createComponent('Counter', _p, __b
4494
4515
  `;
4495
4516
 
4496
4517
  exports[`docs/core/adapters/go-template-adapter.md doc-examples L168 — (no label) 1`] = `
4497
- "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArray } from '@barefootjs/client/runtime'
4518
+ "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
4498
4519
 
4499
4520
  export function initTodoItem(__scope, _p = {}) {
4500
4521
  if (!__scope) return
@@ -4545,11 +4566,32 @@ export function initExample(__scope, _p = {}) {
4545
4566
 
4546
4567
  const __tpl_l0 = document.createElement('template')
4547
4568
  __tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
4548
- mapArray(() => items(), _s1, (item) => String(item), (item, __idx, __existing) => {
4549
- const __el = __existing ?? __tpl_l0.content.firstElementChild.cloneNode(true)
4550
- const __bfw_s0 = lazySlots(__el, [{ id: 's0', kind: 'text', path: __existing ? [] : [0] }])
4551
- createEffect(() => { __bfw_s0('s0', String(item())) })
4552
- return __el
4569
+ const __lzp_l0 = [[0]]
4570
+ const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
4571
+ const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
4572
+ const __lzc_l0 = (__e) => {
4573
+ return [null]
4574
+ }
4575
+ mapArrayLazy(() => items(), _s1, (item) => String(item), {
4576
+ createRow: (__e, __idx) => {
4577
+ const item = () => __e.item
4578
+ const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
4579
+ const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
4580
+ const __l = __e.last = []
4581
+ { const __x = item()
4582
+ __r[0]('s0', textOrNode(__x))
4583
+ __l[0] = __x }
4584
+ return __el
4585
+ },
4586
+ applyItem: (__e) => {
4587
+ const item = () => __e.item
4588
+ const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
4589
+ const __l = __e.last ?? (__e.last = [])
4590
+ const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
4591
+ { const __x = item()
4592
+ if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
4593
+ __l[0] = __x }
4594
+ },
4553
4595
  }, 'l0')
4554
4596
 
4555
4597
  }
@@ -4559,7 +4601,7 @@ export function Example(_p, __bfKey) { return createComponent('Example', _p, __b
4559
4601
  `;
4560
4602
 
4561
4603
  exports[`docs/core/adapters/go-template-adapter.md doc-examples L180 — (no label) 1`] = `
4562
- "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArray } from '@barefootjs/client/runtime'
4604
+ "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
4563
4605
 
4564
4606
  export function initTodoItem(__scope, _p = {}) {
4565
4607
  if (!__scope) return
@@ -4610,11 +4652,32 @@ export function initExample(__scope, _p = {}) {
4610
4652
 
4611
4653
  const __tpl_l0 = document.createElement('template')
4612
4654
  __tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
4613
- mapArray(() => items().filter(item => item.active), _s1, (item) => String(item.id), (item, __idx, __existing) => {
4614
- const __el = __existing ?? __tpl_l0.content.firstElementChild.cloneNode(true)
4615
- const __bfw_s0 = lazySlots(__el, [{ id: 's0', kind: 'text', path: __existing ? [] : [0] }])
4616
- createEffect(() => { __bfw_s0('s0', String(item().name)) })
4617
- return __el
4655
+ const __lzp_l0 = [[0]]
4656
+ const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
4657
+ const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
4658
+ const __lzc_l0 = (__e) => {
4659
+ return [null]
4660
+ }
4661
+ mapArrayLazy(() => items().filter(item => item.active), _s1, (item) => String(item.id), {
4662
+ createRow: (__e, __idx) => {
4663
+ const item = () => __e.item
4664
+ const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
4665
+ const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
4666
+ const __l = __e.last = []
4667
+ { const __x = item().name
4668
+ __r[0]('s0', textOrNode(__x))
4669
+ __l[0] = __x }
4670
+ return __el
4671
+ },
4672
+ applyItem: (__e) => {
4673
+ const item = () => __e.item
4674
+ const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
4675
+ const __l = __e.last ?? (__e.last = [])
4676
+ const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
4677
+ { const __x = item().name
4678
+ if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
4679
+ __l[0] = __x }
4680
+ },
4618
4681
  }, 'l0')
4619
4682
 
4620
4683
  }
@@ -5032,7 +5095,7 @@ export function StatementExample(_p, __bfKey) { return createComponent('Statemen
5032
5095
  `;
5033
5096
 
5034
5097
  exports[`docs/core/advanced/compiler-internals.md doc-examples L126 — (no label) 1`] = `
5035
- "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArray } from '@barefootjs/client/runtime'
5098
+ "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
5036
5099
 
5037
5100
  export function initTodoItem(__scope, _p = {}) {
5038
5101
  if (!__scope) return
@@ -5083,11 +5146,32 @@ export function initExample(__scope, _p = {}) {
5083
5146
 
5084
5147
  const __tpl_l0 = document.createElement('template')
5085
5148
  __tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
5086
- mapArray(() => todos().filter(t => !t.done).toSorted((a, b) => a.date - b.date), _s1, (t) => String(t.id), (t, __idx, __existing) => {
5087
- const __el = __existing ?? __tpl_l0.content.firstElementChild.cloneNode(true)
5088
- const __bfw_s0 = lazySlots(__el, [{ id: 's0', kind: 'text', path: __existing ? [] : [0] }])
5089
- createEffect(() => { __bfw_s0('s0', String(t().name)) })
5090
- return __el
5149
+ const __lzp_l0 = [[0]]
5150
+ const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
5151
+ const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
5152
+ const __lzc_l0 = (__e) => {
5153
+ return [null]
5154
+ }
5155
+ mapArrayLazy(() => todos().filter(t => !t.done).toSorted((a, b) => a.date - b.date), _s1, (t) => String(t.id), {
5156
+ createRow: (__e, __idx) => {
5157
+ const t = () => __e.item
5158
+ const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
5159
+ const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
5160
+ const __l = __e.last = []
5161
+ { const __x = t().name
5162
+ __r[0]('s0', textOrNode(__x))
5163
+ __l[0] = __x }
5164
+ return __el
5165
+ },
5166
+ applyItem: (__e) => {
5167
+ const t = () => __e.item
5168
+ const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
5169
+ const __l = __e.last ?? (__e.last = [])
5170
+ const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
5171
+ { const __x = t().name
5172
+ if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
5173
+ __l[0] = __x }
5174
+ },
5091
5175
  }, 'l0')
5092
5176
 
5093
5177
  }
@@ -5159,7 +5243,7 @@ export function StatementExample(_p, __bfKey) { return createComponent('Statemen
5159
5243
  `;
5160
5244
 
5161
5245
  exports[`docs/core/advanced/performance.md doc-examples L67 — ✅ Stable key — DOM nodes reused when list changes 1`] = `
5162
- "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArray } from '@barefootjs/client/runtime'
5246
+ "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
5163
5247
 
5164
5248
  export function initTodoItem(__scope, _p = {}) {
5165
5249
  if (!__scope) return
@@ -5210,11 +5294,32 @@ export function initExample(__scope, _p = {}) {
5210
5294
 
5211
5295
  const __tpl_l0 = document.createElement('template')
5212
5296
  __tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
5213
- mapArray(() => items(), _s1, (item) => String(item.id), (item, __idx, __existing) => {
5214
- const __el = __existing ?? __tpl_l0.content.firstElementChild.cloneNode(true)
5215
- const __bfw_s0 = lazySlots(__el, [{ id: 's0', kind: 'text', path: __existing ? [] : [0] }])
5216
- createEffect(() => { __bfw_s0('s0', String(item().name)) })
5217
- return __el
5297
+ const __lzp_l0 = [[0]]
5298
+ const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
5299
+ const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
5300
+ const __lzc_l0 = (__e) => {
5301
+ return [null]
5302
+ }
5303
+ mapArrayLazy(() => items(), _s1, (item) => String(item.id), {
5304
+ createRow: (__e, __idx) => {
5305
+ const item = () => __e.item
5306
+ const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
5307
+ const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
5308
+ const __l = __e.last = []
5309
+ { const __x = item().name
5310
+ __r[0]('s0', textOrNode(__x))
5311
+ __l[0] = __x }
5312
+ return __el
5313
+ },
5314
+ applyItem: (__e) => {
5315
+ const item = () => __e.item
5316
+ const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
5317
+ const __l = __e.last ?? (__e.last = [])
5318
+ const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
5319
+ { const __x = item().name
5320
+ if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
5321
+ __l[0] = __x }
5322
+ },
5218
5323
  }, 'l0')
5219
5324
 
5220
5325
  }
@@ -5224,7 +5329,7 @@ export function Example(_p, __bfKey) { return createComponent('Example', _p, __b
5224
5329
  `;
5225
5330
 
5226
5331
  exports[`docs/core/advanced/performance.md doc-examples L70 — ❌ Index key — DOM nodes recreated on reorder 1`] = `
5227
- "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArray } from '@barefootjs/client/runtime'
5332
+ "import { $, createComponent, createEffect, createSignal, escapeAttr, escapeText, escapeTextOrNode, hydrate, lazySlots, mapArrayLazy, textOrNode } from '@barefootjs/client/runtime'
5228
5333
 
5229
5334
  export function initTodoItem(__scope, _p = {}) {
5230
5335
  if (!__scope) return
@@ -5275,11 +5380,32 @@ export function initExample(__scope, _p = {}) {
5275
5380
 
5276
5381
  const __tpl_l0 = document.createElement('template')
5277
5382
  __tpl_l0.innerHTML = \`<li data-key=""><!--bf:s0--><!--/--></li>\`
5278
- mapArray(() => items(), _s1, (item, i) => String(i), (item, i, __existing) => {
5279
- const __el = __existing ?? __tpl_l0.content.firstElementChild.cloneNode(true)
5280
- const __bfw_s0 = lazySlots(__el, [{ id: 's0', kind: 'text', path: __existing ? [] : [0] }])
5281
- createEffect(() => { __bfw_s0('s0', String(item().name)) })
5282
- return __el
5383
+ const __lzp_l0 = [[0]]
5384
+ const __lzs_l0 = [{ id: 's0', kind: 'text', path: [] }]
5385
+ const __lzsc_l0 = [{ id: 's0', kind: 'text', path: __lzp_l0[0] }]
5386
+ const __lzc_l0 = (__e) => {
5387
+ return [null]
5388
+ }
5389
+ mapArrayLazy(() => items(), _s1, (item, i) => String(i), {
5390
+ createRow: (__e, i) => {
5391
+ const item = () => __e.item
5392
+ const __el = __tpl_l0.content.firstElementChild.cloneNode(true)
5393
+ const __r = __e.refs = [lazySlots(__el, __lzsc_l0)]
5394
+ const __l = __e.last = []
5395
+ { const __x = item().name
5396
+ __r[0]('s0', textOrNode(__x))
5397
+ __l[0] = __x }
5398
+ return __el
5399
+ },
5400
+ applyItem: (__e) => {
5401
+ const item = () => __e.item
5402
+ const __r = __e.refs ?? (__e.refs = __lzc_l0(__e))
5403
+ const __l = __e.last ?? (__e.last = [])
5404
+ const __d = __r[0] ?? (__r[0] = lazySlots(__e.primaryEl, __lzs_l0))
5405
+ { const __x = item().name
5406
+ if (!(0 in __l) || !Object.is(__l[0], __x)) __d('s0', textOrNode(__x))
5407
+ __l[0] = __x }
5408
+ },
5283
5409
  }, 'l0')
5284
5410
 
5285
5411
  }
@@ -2638,7 +2638,10 @@ describe('Client JS generation', () => {
2638
2638
  const js = result.files.find(f => f.type === 'clientJs')!.content
2639
2639
  // The branch arm's mapArray call must live inside a
2640
2640
  // createDisposableEffect that is pushed into __disposers.
2641
- expect(js).toMatch(/__disposers\.push\(createDisposableEffect\(\(\)\s*=>\s*\{[\s\S]*?mapArray\(/)
2641
+ // `mapArray(` or `mapArrayLazy(` — a lazy-row-eligible branch loop
2642
+ // (spec/slot-unification.md §9.4) uses the latter and has exactly the
2643
+ // same disposal obligation.
2644
+ expect(js).toMatch(/__disposers\.push\(createDisposableEffect\(\(\)\s*=>\s*\{[\s\S]*?mapArray(Lazy)?\(/)
2642
2645
  })
2643
2646
 
2644
2647
  test('nested conditional inside a conditional branch is wrapped in createDisposableEffect', () => {
@@ -53,7 +53,10 @@ describe('composite loops inside conditional branches (#724)', () => {
53
53
  const js = clientJs!.content
54
54
 
55
55
  // Should use mapArray inside the branch's bindEvents
56
- expect(js).toContain('mapArray(')
56
+ // `mapArray(` or `mapArrayLazy(` — a lazy-row-eligible branch loop
57
+ // (spec/slot-unification.md §9.4) uses the latter; either satisfies
58
+ // "keyed reconciliation, not a composite/component shape".
59
+ expect(js).toMatch(/\bmapArray(Lazy)?\(/)
57
60
 
58
61
  // After O-1's mode-collapse PR, child component init goes through the
59
62
  // runtime `upsertChild` helper instead of an emit-side
@@ -96,7 +99,10 @@ describe('composite loops inside conditional branches (#724)', () => {
96
99
  const js = clientJs!.content
97
100
 
98
101
  // Should use mapArray for per-item reactivity
99
- expect(js).toContain('mapArray(')
102
+ // `mapArray(` or `mapArrayLazy(` — a lazy-row-eligible branch loop
103
+ // (spec/slot-unification.md §9.4) uses the latter; either satisfies
104
+ // "keyed reconciliation, not a composite/component shape".
105
+ expect(js).toMatch(/\bmapArray(Lazy)?\(/)
100
106
 
101
107
  // Should NOT use createComponent inside the init body (no child
102
108
  // components in this fixture). The CLI also emits a callable shim
@@ -147,7 +153,10 @@ describe('composite loops inside conditional branches (#724)', () => {
147
153
  const js = clientJs!.content
148
154
 
149
155
  // Should use mapArray for the loop
150
- expect(js).toContain('mapArray(')
156
+ // `mapArray(` or `mapArrayLazy(` — a lazy-row-eligible branch loop
157
+ // (spec/slot-unification.md §9.4) uses the latter; either satisfies
158
+ // "keyed reconciliation, not a composite/component shape".
159
+ expect(js).toMatch(/\bmapArray(Lazy)?\(/)
151
160
 
152
161
  // Should have event delegation (addEventListener + closest pattern)
153
162
  expect(js).toContain(".addEventListener('click', (__bfEvt) => {")
@@ -29,11 +29,17 @@ function clientJs(source: string, file = 'CondKey.tsx'): string {
29
29
  return cjs!.content
30
30
  }
31
31
 
32
+ /**
33
+ * Reconciler call sites, whichever family they use. A lazy-row-eligible loop
34
+ * (spec/slot-unification.md §9.4) emits `mapArrayLazy(`; everything else keeps
35
+ * `mapArray(`. The key expression this suite asserts on is the same third
36
+ * argument in both shapes, so match the family, not one member of it.
37
+ */
32
38
  function mapArrayCalls(content: string): string[] {
33
39
  return content
34
40
  .split('\n')
35
41
  .map((ln) => ln.trim())
36
- .filter((ln) => ln.startsWith('mapArray('))
42
+ .filter((ln) => ln.startsWith('mapArray(') || ln.startsWith('mapArrayLazy('))
37
43
  }
38
44
 
39
45
  describe('mapArray key extraction across conditional branches (#1098)', () => {
@@ -7,9 +7,14 @@
7
7
  * `isSelected(row.id)` as reactive with no analyzer changes — the callee's
8
8
  * type carries the brand, not the call's return type. These tests pin:
9
9
  * - the classic js-framework-benchmark row compiles with `isSelected(row.id)`
10
- * wired to a single per-row `createEffect` (not the legacy
11
- * `selected() === row.id` shape, which would still work but subscribes
12
- * every row to the raw signal)
10
+ * wired through the lazy row graph — ZERO emitted per-row effects, the
11
+ * loop-level effect living in `mapArrayLazy` (spec §9). Until §9.3a
12
+ * was lifted this shape was gate-INELIGIBLE (an opaque local whose call
13
+ * is the reactive read could not be primed) and compiled to one per-row
14
+ * `createEffect`; the runtime re-subscribe seam replaced that, so the
15
+ * loop is emitted like any other. Either way it is never
16
+ * the legacy `selected() === row.id` shape, which subscribes every row
17
+ * to the raw signal.
13
18
  * - the loop-param accessor wrap applies only to the call ARGUMENT
14
19
  * (`row.id` -> `row().id`), leaving the `isSelected` callee untouched
15
20
  * - the hoisted skeleton fast path (#2223) still fires for a selector-driven
@@ -36,7 +41,7 @@ function compile(source: string, filename: string): { clientJs: string; template
36
41
  }
37
42
 
38
43
  describe('createSelector (perf, #2143 gap 5)', () => {
39
- test('isSelected(row.id) in a loop body compiles to one per-row createEffect', () => {
44
+ test('isSelected(row.id) in a loop body compiles through the lazy row graph', () => {
40
45
  const source = `
41
46
  'use client'
42
47
  import { createSignal, createSelector } from '@barefootjs/client'
@@ -66,12 +71,21 @@ describe('createSelector (perf, #2143 gap 5)', () => {
66
71
  // The callee itself is never rewritten to an accessor call.
67
72
  expect(clientJs).not.toContain('isSelected()(')
68
73
 
69
- // Exactly one createEffect wraps the selector-driven class attribute
70
- // not the O(rows) `selected() === row.id` shape.
74
+ // The selector-driven class attribute costs NO emitted effect at all:
75
+ // the loop is lazy-eligible, so its one loop-level effect lives inside
76
+ // `mapArrayLazy` rather than in the emitted row body. It is also never
77
+ // the O(rows) `selected() === row.id` shape.
71
78
  const effectCount = (clientJs.match(/createEffect\(/g) ?? []).length
72
- expect(effectCount).toBe(1)
79
+ expect(effectCount).toBe(0)
80
+ expect(clientJs).toContain('mapArrayLazy(')
73
81
  expect(clientJs).not.toContain('selected() ===')
74
82
 
83
+ // `isSelected` is an opaque local (its CALL is the reactive read). Nothing
84
+ // in the emission marks that: the runtime's re-subscribe seam applies to
85
+ // every loop-level outer effect, so the compiler has no judgement to
86
+ // encode here (§9.3a).
87
+ expect(clientJs).not.toContain('outerNeedsResubscribe')
88
+
75
89
  // The hoisted skeleton fast path (#2223) still fires: a single-line
76
90
  // renderItem clones from a once-per-loop template.
77
91
  expect(clientJs).toMatch(/const __tpl_\w+ = document\.createElement\('template'\)/)