@farm.js/react 0.1.0-beta.3 → 0.1.0-beta.5

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.
package/README.md CHANGED
@@ -23,7 +23,27 @@ export default defineConfig({
23
23
  ```
24
24
 
25
25
  `compiler: true` uses inference: every application TSX/JSX component is considered, eligible
26
- components are compiled, and everything else keeps the normal React path.
26
+ components are compiled with hybrid reactivity, and everything else keeps the normal React path.
27
+
28
+ Hybrid reactivity starts from the compiler's proven state-to-binding dependency lists, then marks
29
+ only multi-state short-circuit bindings for runtime read tracking. For
30
+ `enabled ? activeValue : inactiveValue`, an update to the inactive value does not schedule the
31
+ binding. The runtime uses direct subscription indexes rather than proxies or a component-wide
32
+ scan. Use `compiler: { reactivity: "static" }` to retain only the complete build-time dependency
33
+ lists for comparison or diagnosis.
34
+
35
+ Compiler output also selects its runtime capabilities at build time. A direct counter imports only
36
+ the cell scheduler and direct-binding core; conditionals, keyed rows and LIS, nested host ranges,
37
+ and component islands are retained only when a compiled component in that module needs them. Plain
38
+ keyed rows omit the larger recursive-host and row-conditional extensions. This selection is
39
+ automatic and does not add a configuration option or asynchronous runtime loading. The legacy
40
+ `createCompiledComponent` export remains a complete compatibility entry for hand-authored
41
+ definitions, while generated code uses the tree-shakable feature entry.
42
+
43
+ The persisted production-size fixtures and regression gate live in
44
+ [`RUNTIME_SIZE_RESULTS.md`](./RUNTIME_SIZE_RESULTS.md). The recorded direct-only runtime premium is
45
+ 73.6% smaller than the complete compatibility runtime premium; the feature-heavy keyed benchmark
46
+ application removes 6,185 gzip bytes from its previous compiler-on build.
27
47
 
28
48
  For selective adoption, use annotation mode:
29
49
 
@@ -34,6 +54,7 @@ renderer: react({
34
54
  mode: "annotation",
35
55
  directive: "use compiler",
36
56
  onUnsupported: "warn",
57
+ reactivity: "hybrid",
37
58
  },
38
59
  },
39
60
  }),
@@ -56,6 +77,7 @@ The current compiler handles components that it can prove have:
56
77
  - one host-element root and a statically known host-element tree around supported conditional,
57
78
  keyed-list, and React component-island boundaries;
58
79
  - an identifier props parameter or flat object destructuring with aliases and defaults;
80
+ - build-time dependency cells for referenced flat primitive props, with runtime identity guards;
59
81
  - top-level `useState` declarations;
60
82
  - optional compiler-safe derived `const` values declared after state and in source order;
61
83
  - whitelisted `Boolean`, `Number`, `String`, and deterministic `Math` calculations;
@@ -67,19 +89,25 @@ The current compiler handles components that it can prove have:
67
89
  - item-keyed `collection.map(...)` children and explicit `List` boundaries at statically known
68
90
  container locations, including supported non-mutating collection pipelines;
69
91
  - inline synchronous events inside otherwise eligible host-only keyed rows;
70
- - row-local logical and ternary host branches inside stable keyed-row containers;
92
+ - multiple and recursively nested logical or ternary host branches inside non-interactive keyed
93
+ rows, including branches beside static host siblings;
71
94
  - stable module-level child components with compiler-safe props;
72
95
  - React-managed event handlers; and
73
96
  - no refs, effects, or unsupported dynamic child structures.
74
97
 
75
- The generated component preserves React ownership of initial placement, props, events, SSR, and
76
- hydration. Local state cells batch updates into a microtask and patch only compiler-known DOM
77
- targets. Proven host containers can transfer child ownership after mount for host-only conditional
78
- branches and ranges, plus non-interactive host-only keyed rows. An interactive row or a row with
79
- local conditional slots uses a hybrid boundary instead: React retains its events, conditional
80
- Fibers, and structural reconciliation, while Farm patches same-key bindings and refreshes only
81
- changed row-local branches. React still creates or hydrates all initial DOM. Anything outside those
82
- narrow contracts uses a small React-owned boundary or the complete original component.
98
+ The generated component preserves React ownership of initial placement, parent update commits,
99
+ identity-bearing props, events, SSR, and hydration. Local state cells batch updates into a
100
+ microtask; eligible flat destructured primitive props join the same dependency index after React
101
+ commits their parent update. The runtime skips unchanged binding output and patches only
102
+ compiler-known DOM targets. Objects, arrays, functions, symbols, React elements, `children`, and
103
+ identifier props retain the normal React prop-render path. Proven host containers
104
+ can transfer child ownership after mount for host-only conditional
105
+ branches and ranges, plus non-interactive host-only keyed rows and their recursively nested host
106
+ conditions. An interactive row uses a hybrid boundary instead: React retains its events,
107
+ conditional Fibers, and structural reconciliation, while Farm patches same-key bindings and
108
+ refreshes only changed React-owned row-local branches. React still creates or hydrates all initial
109
+ DOM. Anything outside those narrow contracts uses a small React-owned boundary or the complete
110
+ original component.
83
111
 
84
112
  For a common dedicated conditional, no new component or annotation is required:
85
113
 
@@ -155,7 +183,7 @@ insert, removal, or reorder asks React to reconcile the rows, adopts the committ
155
183
  reapplies current bindings, and then resumes direct same-key patches. This avoids creating eventful
156
184
  DOM outside React's Fiber tree.
157
185
 
158
- A keyed row may also contain dedicated conditional slots:
186
+ A non-interactive keyed row may also transfer its safe host conditions to the same keyed instance:
159
187
 
160
188
  ```tsx
161
189
  <ul>
@@ -163,7 +191,16 @@ A keyed row may also contain dedicated conditional slots:
163
191
  <li key={item.id}>
164
192
  <span>{item.label}</span>
165
193
  <div className="status-slot">
166
- {item.done ? <strong>{item.label} complete</strong> : <span>In progress</span>}
194
+ <i>State</i>
195
+ {item.done ? (
196
+ <article>
197
+ <strong>{item.label} complete</strong>
198
+ <div>{item.details && <small>{item.description}</small>}</div>
199
+ </article>
200
+ ) : (
201
+ <span>In progress</span>
202
+ )}
203
+ <b>Prepared</b>
167
204
  </div>
168
205
  <section className="details-slot">{item.expanded && <p>{item.description}</p>}</section>
169
206
  </li>
@@ -171,13 +208,53 @@ A keyed row may also contain dedicated conditional slots:
171
208
  </ul>
172
209
  ```
173
210
 
174
- Each conditional must be the only meaningful child of its persistent lowercase host container.
175
- The compiler records its test and branch bindings per row key. A same-key collection update compares
176
- those prepared snapshots and asks React to render only the conditional boundaries whose selected
177
- branch or branch values changed. Other conditional rows and the outer map stay untouched. Inserts,
178
- removals, and reorders remain React structural commits for these rows, after which Farm re-adopts
179
- the host skeleton and resumes row-local updates. This keeps empty branches, SSR, hydration,
180
- recoverable hydration errors, error boundaries, and Fast Refresh inside React's ownership.
211
+ The compiler prepares the row descriptor, static sibling positions, branch factories, and
212
+ text/attribute/style bindings at build time. React creates or hydrates the initial list. After
213
+ mount, a same-key update patches the active branch in place or mounts, replaces, or removes only the
214
+ changed branch. Multiple and deeper host-only conditions share that row scope. Insertions and
215
+ removals create or clean one row scope, while reorders use the normal LIS pass. The component body
216
+ and map callback do not rerun, and no wrapper or hydration marker is added.
217
+
218
+ This compiler-owned tier requires a lowercase non-interactive host row. Hooks, custom components,
219
+ branch events, refs, SVG, fragments, spreads, dangerous HTML, controlled inputs inside a condition,
220
+ and unproven expressions stay on React. An otherwise eligible row with inline events continues to
221
+ use the hybrid boundary: React owns its conditional Fibers and structural commits, while Farm
222
+ compares per-key snapshots and refreshes only changed slots. Duplicate runtime keys or invalid
223
+ adopted DOM switch the complete list to React.
224
+
225
+ An eligible outer keyed row may also own nested keyed ranges:
226
+
227
+ ```tsx
228
+ <div>
229
+ {projects.map((project) => (
230
+ <section key={project.id}>
231
+ <h2>{project.name}</h2>
232
+ <ul>
233
+ <li>Tasks</li>
234
+ {project.tasks.map((task) => (
235
+ <li key={task.id}>{task.title}</li>
236
+ ))}
237
+ <li>End</li>
238
+ </ul>
239
+ </section>
240
+ ))}
241
+ </div>
242
+ ```
243
+
244
+ Every outer key receives an isolated inner key table and LIS pass. Moving a project preserves its
245
+ task scope; reordering one task list touches only that project. Surviving project, task, and static
246
+ sibling elements keep their DOM identity, and removing an outer row cleans its inner scope. The
247
+ initial contract accepts non-interactive lowercase inner rows from `.map(...)` or inline `List`,
248
+ including multiple ranges separated by static host siblings. Inner events or forms, Hooks, custom
249
+ components, fragments, refs, SVG, spreads, dangerous HTML, and index keys keep the complete outer
250
+ row on React's fallback. Additional keyed levels that satisfy the same host-only contract are
251
+ analyzed recursively instead of using a fixed nesting limit.
252
+
253
+ For example, `boards → columns → cards → tags` becomes a tree of keyed scopes. Each stable parent
254
+ key owns its child key table and LIS pass. Moving a board carries its column, card, and tag scopes;
255
+ reordering cards reconciles only the selected column. Block IDs stay globally unique while runtime
256
+ instances are isolated by their complete parent-key path. Removing any parent cleans every
257
+ descendant subscription before its DOM leaves the document.
181
258
 
182
259
  A keyed collection may use derived locals or an inline chain of `filter`, `slice`, `toSorted`, and
183
260
  `toReversed`:
@@ -211,10 +288,9 @@ the TypeScript `lib` with ES2023 and target a runtime that supports them when us
211
288
 
212
289
  React remains the fallback and compatibility boundary. An interactive map beside static children,
213
290
  a non-host sibling between ranges, a row with a non-inline or async handler, a file input or dynamic
214
- form control shape, a fragment, a ref, a custom component, or a conditional mixed directly beside
215
- other children in the same host slot uses the existing React-owned keyed boundary. The outer
216
- compiled component can still be skipped, but React reconciles that list's rows and owns their
217
- events, lifecycle, and state.
291
+ form control shape, a fragment, a ref, a custom component, or any unsupported nested dynamic
292
+ structure uses the existing React-owned keyed boundary. The outer compiled component can still be
293
+ skipped, but React reconciles that list's rows and owns their events, lifecycle, and state.
218
294
 
219
295
  Non-interactive host maps can share a nested or component-root container with static host siblings:
220
296
 
@@ -315,21 +391,37 @@ Conditional blocks have three safe ownership levels. One or more proven host-onl
315
391
  may become compiler-owned ranges among static host siblings, including at the exact component root.
316
392
  A dedicated container with exactly one conditional child uses the smaller compiler-owned branch
317
393
  boundary. The more general React-owned conditional path accepts supported events, nested host
318
- conditionals, keyed lists, and component islands. Keys, custom components, fragments, refs, SVG,
319
- attribute spreads, `dangerouslySetInnerHTML`, nested dynamic blocks, and branch events keep React
320
- ownership. An empty ternary branch may be `null` or `false`. The inactive branch is described at
321
- build time but is never pre-mounted or cached. If a logical `&&` evaluates to a number such as `0`,
394
+ conditionals, keyed lists, and component islands. A compiler-owned host branch may now recursively
395
+ contain eligible host-only conditional ranges and non-interactive keyed ranges in nested host
396
+ containers. Each nested range receives its own dependency entry and updates without rerunning or
397
+ replacing the outer branch. When conditionals and keyed maps/`List` ranges are direct siblings in
398
+ one safe host container, the compiler emits one ordered mixed-range controller. It mounts or removes
399
+ conditional branches, applies independent LIS reconciliation to each keyed range, and preserves
400
+ static siblings in place. Stateful leaf text, attributes, `className`, and individual inline style
401
+ properties on those siblings are compiled into stable segment/sibling/path bindings. Their address
402
+ does not shift when a nearby condition mounts or a keyed range inserts, removes, or reorders rows,
403
+ so the same controller patches them from the current state snapshot without replacing the sibling
404
+ or rerunning the owner. The same mixed layout may recurse inside safe branches and keyed rows.
405
+ Keys on branches, custom components, fragments, refs, SVG, attribute spreads,
406
+ `dangerouslySetInnerHTML`, branch events, interactive rows, and unsupported mixed children keep
407
+ React ownership. An empty ternary branch may be `null` or `false`. The
408
+ inactive branch is described at build time but is never pre-mounted or cached. If a logical `&&`
409
+ evaluates to a number such as `0`,
322
410
  or adopted DOM no longer matches its descriptor, the affected container remounts through React so
323
411
  JavaScript, hydration, and React rendering semantics remain exact.
324
412
 
325
413
  All component-level boundary types share one block graph and one ID sequence. A nested
326
414
  binding records its nearest conditional parent. If one state flush affects both an outer
327
415
  conditional and its descendants, the runtime refreshes the mounted outer boundary once and skips
328
- the redundant descendant refreshes. React unmounts inner boundaries normally, their subscriptions
329
- are removed, and a later remount reads the latest compiler-cell values. Host-only keyed rows have
330
- separate runtime instances per key. Row-local conditional IDs are scoped to that instance and
331
- paired with its stable key, so two rows can use the same build-time slot ID without sharing data or
332
- subscriptions. Unsupported row subtrees stay complete React-owned keyed boundaries.
416
+ the redundant descendant refreshes. React-owned inner boundaries unmount normally. Compiler-owned
417
+ host scopes explicitly remove every nested subscription before their outer DOM is removed, and a
418
+ later mount reads the latest compiler-cell values. Surviving keyed rows move with the existing LIS
419
+ algorithm. Duplicate runtime keys, invalid adoption, or an unsafe logical value transfer the outer
420
+ container back to React; descendant dependencies remain subscribed so that fallback stays live.
421
+ Host-only keyed rows have separate runtime instances per key. Row-local conditional and keyed-range
422
+ IDs are scoped to that instance and paired with its stable key, so two rows can use the same
423
+ build-time slot ID without sharing data or subscriptions. Unsupported row subtrees stay complete
424
+ React-owned keyed boundaries.
333
425
 
334
426
  Unsupported components fall back to React by default. Use `onUnsupported: "warn"` for diagnostics
335
427
  or `onUnsupported: "error"` while tightening an annotated migration.
@@ -357,8 +449,12 @@ Application and prototype calls, dynamic style objects, handlers outside JSX eve
357
449
  computed, and rest props patterns, async handlers, unkeyed or index-keyed lists, chained maps,
358
450
  unsupported conditional roots, effects, and more advanced hook support intentionally stay on React
359
451
  in this release. Compiler-owned keyed rows support either one dedicated host-only map/`List` or
360
- one or more non-interactive ranges in a nested or component-root host container. Inline synchronous
361
- events and dedicated row-local host conditional slots can use the single-list hybrid path, but
362
- branch events, interactive ranges beside siblings, nested dynamic blocks, conditionals mixed with
363
- siblings in one slot, non-inline or async row handlers, unsupported form shapes, custom components,
364
- fragments, refs, SVG, and duplicate runtime keys keep or switch to React ownership.
452
+ one or more non-interactive ranges in a nested or component-root host container. A dedicated
453
+ non-interactive row may also contain multiple recursive logical or ternary host branches beside
454
+ stateful static host siblings, plus recursively nested non-interactive keyed ranges scoped by every
455
+ stable parent key. Static sibling bindings support safe leaf text, attributes, `className`, and
456
+ individual style properties; lifecycle-sensitive shapes continue to fall back. Inline synchronous
457
+ row events continue to use the hybrid React-owned row path. Branch
458
+ or nested-row events, interactive ranges beside siblings, non-inline or async row handlers,
459
+ unsupported form shapes, custom components, fragments, refs, SVG, and duplicate runtime keys keep
460
+ or switch to React ownership.