@barefootjs/cli 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.
@@ -195,7 +195,7 @@ createEffect(() => {
195
195
  ### 4. Code Generation Order
196
196
 
197
197
  ```javascript
198
- import { $, $t, createEffect, createMemo, createSignal, hydrate, onMount } from '@barefootjs/client'
198
+ import { $, createEffect, createMemo, createSignal, escapeTextOrNode, hydrate, lazySlots, onMount } from '@barefootjs/client/runtime'
199
199
 
200
200
  export function initCounter(__scope, _p = {}) {
201
201
  if (!__scope) return
@@ -216,15 +216,16 @@ export function initCounter(__scope, _p = {}) {
216
216
  const doubled = createMemo(() => count() * 2)
217
217
 
218
218
  // 4. Element references (always destructured, always returns array)
219
- // $() — regular elements: querySelector('[bf="id"]') within scope
220
- // $t() — text nodes: find comment marker <!--bf:id-->
219
+ // $() — regular elements: querySelector('[bf="id"]') within scope
221
220
  const [_s3] = $(__scope, 's3')
222
- const [_s0, _s2] = $t(__scope, 's0', 's2')
223
221
 
224
- // 5. Dynamic text updates
222
+ // 5. Dynamic content updates — the compiler emits a claim plan (data)
223
+ // per slot; lazySlots() claims the <!--bf:id-->…<!--/--> marker
224
+ // range once on first write, then writes through the held reference
225
+ const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'markup', path: [] }])
225
226
  createEffect(() => {
226
227
  const __val = count()
227
- if (_s0 && !__val?.__isSlot) _s0.nodeValue = String(__val ?? '')
228
+ __bfw_s0('s0', escapeTextOrNode(__val))
228
229
  })
229
230
 
230
231
  // 6. Reactive attribute updates
@@ -266,7 +267,7 @@ hydrate('Counter', {
266
267
  Only used imports are included:
267
268
 
268
269
  ```javascript
269
- import { $, $t, createEffect, createMemo, createSignal, hydrate, onMount } from '@barefootjs/client'
270
+ import { $, createEffect, createMemo, createSignal, escapeTextOrNode, hydrate, lazySlots, onMount } from '@barefootjs/client/runtime'
270
271
  ```
271
272
 
272
273
  ### 6. Template Registration
@@ -51,7 +51,7 @@ The compiler detects static arrays and skips reconciliation:
51
51
  const tabs = ['Home', 'About', 'Contact']
52
52
  {tabs.map(tab => <Tab label={tab} />)}
53
53
 
54
- // Dynamic — reconcileElements needed
54
+ // Dynamic — keyed reconciliation (mapArray) needed
55
55
  const [items, setItems] = createSignal([...])
56
56
  {items().map(item => <Item key={item.id} data={item} />)}
57
57
  ```
@@ -82,7 +82,7 @@ export function Counter({ __instanceId, ... }) {
82
82
  Client JS (Phase 2b):
83
83
 
84
84
  ```js
85
- import { $, $t, createEffect, createSignal, hydrate } from '@barefootjs/client'
85
+ import { $, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
86
86
 
87
87
  export function initCounter(__scope, _p = {}) {
88
88
  if (!__scope) return
@@ -90,11 +90,13 @@ export function initCounter(__scope, _p = {}) {
90
90
  const [count, setCount] = createSignal(0)
91
91
 
92
92
  const [_s1] = $(__scope, 's1') // element lookup
93
- const [_s0] = $t(__scope, 's0') // text node lookup
94
93
 
94
+ // Content slot: claimed lazily from the <!--bf:s0-->…<!--/--> marker
95
+ // pair on first write, then updated through the held reference
96
+ const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'markup', path: [] }])
95
97
  createEffect(() => {
96
98
  const __val = count()
97
- if (_s0) _s0.nodeValue = String(__val ?? '')
99
+ __bfw_s0('s0', escapeTextOrNode(__val))
98
100
  })
99
101
 
100
102
  if (_s1) _s1.addEventListener('click', () => { setCount(n => n + 1) })
@@ -102,7 +104,7 @@ export function initCounter(__scope, _p = {}) {
102
104
 
103
105
  hydrate('Counter', {
104
106
  init: initCounter,
105
- template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${(0)}<!--/--></button>`
107
+ template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${escapeText((0))}<!--/--></button>`
106
108
  })
107
109
  ```
108
110
 
@@ -135,9 +137,11 @@ Marker-driven hydration attaches behavior to server-rendered HTML.
135
137
  4. Init function runs per scope — signals, effects, handlers
136
138
  5. Runtime tracks scopes to prevent double initialization
137
139
 
138
- ### Scoped Queries
140
+ ### Scoped Queries and Slot Claiming
139
141
 
140
- `$()` and `$t()` search within a scope, excluding child component scopes:
142
+ Element lookups (`$()`) and content-slot claims (`claimSlots()` /
143
+ `lazySlots()`) both operate within a scope, excluding child component
144
+ scopes:
141
145
 
142
146
  ```html
143
147
  <div bf-s="TodoApp_x1">
@@ -148,4 +152,16 @@ Marker-driven hydration attaches behavior to server-rendered HTML.
148
152
  </div>
149
153
  ```
150
154
 
151
- `$(__scope, 's0')` in TodoApp finds `<h1>`, not the `<span>` inside TodoItem. The `~` prefix marks a child scope excluded from parent queries.
155
+ `$(__scope, 's0')` in TodoApp finds `<h1>`, not the `<span>` inside
156
+ TodoItem. The `~` prefix marks a child scope excluded from parent
157
+ queries.
158
+
159
+ Dynamic *content* (reactive text and markup regions) is not looked up
160
+ per update. Instead the compiler emits a **claim plan** — a data
161
+ description of each slot (`{ id, kind, path }`) — and the runtime claims
162
+ the slot's DOM position **once**: `lazySlots()` scans for the slot's
163
+ `<!--bf:id-->…<!--/-->` marker pair on the first write (skipping child
164
+ scopes, same as `$()`), holds the reference, and every later write goes
165
+ through that held reference with no re-scanning. `claimSlots()` is the
166
+ eager variant used where content may be mutated before the first write
167
+ (streaming, portals).
@@ -63,7 +63,7 @@ export function Counter({ __instanceId, ... }) {
63
63
  **Client script** — Wires up only the interactive parts:
64
64
 
65
65
  ```js
66
- import { $, $t, createEffect, createSignal, hydrate } from '@barefootjs/client'
66
+ import { $, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'
67
67
 
68
68
  export function initCounter(__scope, _p = {}) {
69
69
  if (!__scope) return
@@ -71,11 +71,13 @@ export function initCounter(__scope, _p = {}) {
71
71
  const [count, setCount] = createSignal(0)
72
72
 
73
73
  const [_s1] = $(__scope, 's1') // find element with bf="s1"
74
- const [_s0] = $t(__scope, 's0') // find text node at <!--bf:s0-->
75
74
 
75
+ // Claim the content slot between <!--bf:s0--> and <!--/--> on first
76
+ // write; later writes reuse the claimed reference (no re-scanning)
77
+ const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'markup', path: [] }])
76
78
  createEffect(() => {
77
79
  const __val = count()
78
- if (_s0) _s0.nodeValue = String(__val ?? '')
80
+ __bfw_s0('s0', escapeTextOrNode(__val))
79
81
  })
80
82
 
81
83
  if (_s1) _s1.addEventListener('click', () => { setCount(n => n + 1) })
@@ -83,6 +85,6 @@ export function initCounter(__scope, _p = {}) {
83
85
 
84
86
  hydrate('Counter', {
85
87
  init: initCounter,
86
- template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${(0)}<!--/--></button>`
88
+ template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${escapeText((0))}<!--/--></button>`
87
89
  })
88
90
  ```
@@ -32,23 +32,40 @@ See [JSX Compatibility — Limitations](./jsx-compatibility.md#limitations) for
32
32
 
33
33
  ## How It Works
34
34
 
35
- The compiler skips template generation for the expression. The server outputs a comment marker; the client JS evaluates it:
35
+ The compiler skips template generation for the expression: the server can never evaluate it, so the SSR-rendered width is always zero. The client claims a slot for it and writes the real value once the browser evaluates the expression — the general case behind both compiled shapes below (`spec/slot-unification.md` §4).
36
36
 
37
- **Server output:**
37
+ **Claimed slot (the common case):** a marker pair is still emitted so the client has an anchor comment to claim against.
38
38
 
39
39
  ```html
40
- <!--bf-client:s2--><!--/-->
40
+ <!-- server output -->
41
+ <!--bf:s0--><!--/--> items left
41
42
  ```
42
43
 
43
- **Client JS:**
44
+ ```js
45
+ // client JS
46
+ { const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'text', path: [] }])
47
+ createEffect(() => {
48
+ __bfw_s0('s0', todos().filter(t => !t.done).length)
49
+ }) }
50
+ ```
51
+
52
+ **Markerless elision (Step B):** when the expression is the ONLY content of its own element — not adjacent to other text/expressions, and not inside a loop or conditional branch — the compiler proves a static child-index path to the slot's position and drops the marker pair entirely from both SSR and CSR output. `<strong>{/* @client */ todos().filter(t => !t.done).length}</strong>` from the [TodoApp example](https://github.com/piconic-ai/barefootjs/blob/main/integrations/shared/components/TodoApp.tsx) qualifies:
53
+
54
+ ```html
55
+ <!-- server output -->
56
+ <strong bf="s1"></strong>
57
+ ```
44
58
 
45
59
  ```js
46
- // @client: s2
60
+ // client JS
61
+ { const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'text', path: [0, 0], markerless: true }])
47
62
  createEffect(() => {
48
- updateClientMarker(__scope, 's2', todos().filter(t => !t.done).length)
49
- })
63
+ __bfw_s0('s0', todos().filter(t => !t.done).length)
64
+ }) }
50
65
  ```
51
66
 
67
+ Either way, the claim happens lazily on the first write — nothing is touched until the effect actually runs — and every later write goes through the held reference, never re-scanning the DOM (`packages/client/src/runtime/claim-slots.ts`).
68
+
52
69
 
53
70
  ## Examples
54
71