@farm.js/react 0.1.0-beta.2 → 0.1.0-beta.21
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 +868 -46
- package/dist/compiler-runtime.cjs +7848 -609
- package/dist/compiler-runtime.cjs.map +1 -1
- package/dist/compiler-runtime.d.mts +290 -4
- package/dist/compiler-runtime.d.ts +290 -4
- package/dist/compiler-runtime.mjs +7735 -610
- package/dist/compiler-runtime.mjs.map +1 -1
- package/dist/index.cjs +20 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.mjs +20 -3
- package/dist/index.mjs.map +1 -1
- package/dist/list.cjs +18 -1
- package/dist/list.cjs.map +1 -1
- package/dist/list.mjs +18 -1
- package/dist/list.mjs.map +1 -1
- package/dist/vite.cjs +5436 -259
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.mjs +5438 -261
- package/dist/vite.mjs.map +1 -1
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -23,7 +23,31 @@ 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
|
+
Plain keyed lists with filter hints and only identity-based selection also use the single-kind
|
|
44
|
+
target runtime. Filtering keeps its existing native-method checks and fallback; mixed Set/Map
|
|
45
|
+
targets, conditional rows, and nested host blocks keep their existing runtime capabilities.
|
|
46
|
+
|
|
47
|
+
The persisted production-size fixtures and regression gate live in
|
|
48
|
+
[`RUNTIME_SIZE_RESULTS.md`](./RUNTIME_SIZE_RESULTS.md). The recorded compiler-selected core runtime
|
|
49
|
+
premium is 85.4% smaller than the complete compatibility runtime premium; the feature-heavy keyed
|
|
50
|
+
benchmark application removes 6,185 gzip bytes from its previous compiler-on build.
|
|
27
51
|
|
|
28
52
|
For selective adoption, use annotation mode:
|
|
29
53
|
|
|
@@ -34,6 +58,7 @@ renderer: react({
|
|
|
34
58
|
mode: "annotation",
|
|
35
59
|
directive: "use compiler",
|
|
36
60
|
onUnsupported: "warn",
|
|
61
|
+
reactivity: "hybrid",
|
|
37
62
|
},
|
|
38
63
|
},
|
|
39
64
|
}),
|
|
@@ -56,29 +81,37 @@ The current compiler handles components that it can prove have:
|
|
|
56
81
|
- one host-element root and a statically known host-element tree around supported conditional,
|
|
57
82
|
keyed-list, and React component-island boundaries;
|
|
58
83
|
- an identifier props parameter or flat object destructuring with aliases and defaults;
|
|
84
|
+
- build-time dependency cells for referenced flat primitive props, with runtime identity guards;
|
|
59
85
|
- top-level `useState` declarations;
|
|
60
86
|
- optional compiler-safe derived `const` values declared after state and in source order;
|
|
61
87
|
- whitelisted `Boolean`, `Number`, `String`, and deterministic `Math` calculations;
|
|
62
88
|
- optional synchronous `const` or function-declaration handlers used by JSX events;
|
|
63
89
|
- state-driven text, attributes, per-property inline styles, and controlled form properties;
|
|
64
90
|
- host-rooted `condition && <element>` and `condition ? <element> : <element>` child blocks at
|
|
65
|
-
statically known locations, including
|
|
91
|
+
statically known locations, including direct ranges among static siblings and at the component
|
|
92
|
+
root;
|
|
66
93
|
- item-keyed `collection.map(...)` children and explicit `List` boundaries at statically known
|
|
67
94
|
container locations, including supported non-mutating collection pipelines;
|
|
68
95
|
- inline synchronous events inside otherwise eligible host-only keyed rows;
|
|
69
|
-
-
|
|
96
|
+
- multiple and recursively nested logical or ternary host branches inside non-interactive keyed
|
|
97
|
+
rows, including branches beside static host siblings;
|
|
70
98
|
- stable module-level child components with compiler-safe props;
|
|
71
99
|
- React-managed event handlers; and
|
|
72
100
|
- no refs, effects, or unsupported dynamic child structures.
|
|
73
101
|
|
|
74
|
-
The generated component preserves React ownership of initial placement,
|
|
75
|
-
hydration. Local state cells batch updates into a
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
102
|
+
The generated component preserves React ownership of initial placement, parent update commits,
|
|
103
|
+
identity-bearing props, events, SSR, and hydration. Local state cells batch updates into a
|
|
104
|
+
microtask; eligible flat destructured primitive props join the same dependency index after React
|
|
105
|
+
commits their parent update. The runtime skips unchanged binding output and patches only
|
|
106
|
+
compiler-known DOM targets. Objects, arrays, functions, symbols, React elements, `children`, and
|
|
107
|
+
identifier props retain the normal React prop-render path. Proven host containers
|
|
108
|
+
can transfer child ownership after mount for host-only conditional
|
|
109
|
+
branches and ranges, plus non-interactive host-only keyed rows and their recursively nested host
|
|
110
|
+
conditions. An interactive row uses a hybrid boundary instead: React retains its events,
|
|
111
|
+
conditional Fibers, and structural reconciliation, while Farm patches same-key bindings and
|
|
112
|
+
refreshes only changed React-owned row-local branches. React still creates or hydrates all initial
|
|
113
|
+
DOM. Anything outside those narrow contracts uses a small React-owned boundary or the complete
|
|
114
|
+
original component.
|
|
82
115
|
|
|
83
116
|
For a common dedicated conditional, no new component or annotation is required:
|
|
84
117
|
|
|
@@ -96,6 +129,26 @@ or hydration, a same-branch update patches those bindings without replacing the
|
|
|
96
129
|
change creates, removes, or replaces only the selected branch. No marker node is added to SSR output.
|
|
97
130
|
More complex conditional shapes keep the existing React-owned conditional boundary.
|
|
98
131
|
|
|
132
|
+
Eligible direct conditionals can also share a host container with static host siblings:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<article data-update={updates}>
|
|
136
|
+
<header>Status</header>
|
|
137
|
+
{loading && <p>Loading {updates}</p>}
|
|
138
|
+
<section>Stable content</section>
|
|
139
|
+
{enabled ? <strong>Enabled</strong> : <span>Disabled</span>}
|
|
140
|
+
<footer>Actions</footer>
|
|
141
|
+
</article>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
That `article` may be the component's exact returned root. Farm adopts the original container and
|
|
145
|
+
counts its direct static elements as anchors for the conditional ranges. Same-branch bindings patch
|
|
146
|
+
in place; branch changes insert, remove, or replace only their range. Multiple changed ranges use
|
|
147
|
+
one state snapshot and reconcile from right to left, including when adjacent ranges are empty. The
|
|
148
|
+
root and every unchanged static sibling keep their DOM identity, and no wrapper or marker is added.
|
|
149
|
+
Branch events, components, fragments, refs, SVG, spreads, nested dynamic blocks, or another
|
|
150
|
+
unsupported direct child keep the container on the React-owned path.
|
|
151
|
+
|
|
99
152
|
For a common keyed map, no new API is required:
|
|
100
153
|
|
|
101
154
|
```tsx
|
|
@@ -113,6 +166,677 @@ Later list updates patch surviving rows by key, create or remove only the change
|
|
|
113
166
|
longest increasing subsequence (LIS) to minimize DOM moves during a reorder. The outer user
|
|
114
167
|
component and the list callback do not rerun for those compiler-cell updates.
|
|
115
168
|
|
|
169
|
+
When a separate state cell or primitive prop is compared with the exact row-key expression using
|
|
170
|
+
`===` or `!==`, the compiler emits a key-directed binding proof. A selection change such as
|
|
171
|
+
`item.id === selectedId` then looks up only the previously selected and newly selected row
|
|
172
|
+
instances. Class, attribute, style, and leaf-text bindings on those rows update without evaluating
|
|
173
|
+
the other keyed instances. The proof requires one reactive target used only in strict comparisons;
|
|
174
|
+
mixed structural dependencies, ambiguous expressions, non-primitive runtime targets, React-owned
|
|
175
|
+
rows, and unsupported shapes keep the complete scan or React fallback. No option is required, and
|
|
176
|
+
the compiler report exposes the emitted binding count as `keyedIdentityTargets`.
|
|
177
|
+
|
|
178
|
+
For multi-selection, an exact local-state expression such as `markedIds.has(item.id)` receives a
|
|
179
|
+
separate Set-membership proof. The runtime compares the previous and next native `Set` snapshots and
|
|
180
|
+
patches only row keys in their symmetric difference. It accepts ordinary native sets containing
|
|
181
|
+
primitive keys. Different fields, extra reactive dependencies, Set subclasses or proxies, object
|
|
182
|
+
members, customized `has` behavior, React-owned rows, nested blocks, and structural dependencies
|
|
183
|
+
stay on the existing complete or React-owned paths. The compiler report exposes this count as
|
|
184
|
+
`keyedMembershipTargets`.
|
|
185
|
+
|
|
186
|
+
For per-row data, an exact local-state expression such as `statusById.get(item.id)` receives a
|
|
187
|
+
separate Map-lookup proof. The runtime compares the previous and next native `Map` snapshots and
|
|
188
|
+
patches only row keys whose mapped primitive value changed. It accepts ordinary native maps with
|
|
189
|
+
primitive keys and primitive or nullish values. Different fields, extra dependencies, Map
|
|
190
|
+
subclasses or proxies, object values, customized `get` behavior, React-owned rows, nested blocks,
|
|
191
|
+
and structural dependencies keep the existing complete or React-owned paths. The compiler report
|
|
192
|
+
exposes this count as `keyedMapLookupTargets`.
|
|
193
|
+
|
|
194
|
+
When a targeted Set or Map is compiler-owned, Farm can also carry the exact mutated keys from a
|
|
195
|
+
proven immutable functional update:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
setMarkedIds((current) => {
|
|
199
|
+
const next = new Set(current);
|
|
200
|
+
if (next.has(id)) next.delete(id);
|
|
201
|
+
else next.add(id);
|
|
202
|
+
return next;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
setStatusById((current) => new Map(current).set(id, "ready"));
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The application still creates its normal immutable collection. The generated update records only
|
|
209
|
+
the native `add`, `delete`, or `set` calls that execute, and the runtime validates those keys
|
|
210
|
+
against the previous committed collection. It can then update the relevant keyed rows without
|
|
211
|
+
iterating every old and new Set member or Map entry first. Queued functional updates compose, and
|
|
212
|
+
the runtime periodically compacts its persistent snapshot so a long update chain stays bounded.
|
|
213
|
+
|
|
214
|
+
This proof is deliberately conservative. The state must start from a compiler-owned native
|
|
215
|
+
`Set`/`Map`; every setter use must be a fresh same-kind replacement or a recognized immutable
|
|
216
|
+
updater; and the collection, updater parameter, and cloned draft must not escape or be mutated
|
|
217
|
+
through unknown code. Shadowed constructors, custom collections, object keys, object Map values,
|
|
218
|
+
direct state mutation, aliased drafts, or a failed runtime check use the existing snapshot or
|
|
219
|
+
React-owned path before compiled bindings run. No option or syntax is added. The compiler report
|
|
220
|
+
counts emitted native mutation sites as `keyedCollectionUpdateHints`.
|
|
221
|
+
|
|
222
|
+
For a direct keyed `useState` collection, the compiler also recognizes conservative same-order
|
|
223
|
+
updates such as:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
setItems((current) =>
|
|
227
|
+
current
|
|
228
|
+
.map((item) => (item.id === targetId ? { ...item, label: nextLabel } : item))
|
|
229
|
+
.map((item) => (item.id === targetId ? { ...item, selected: !item.selected } : item)),
|
|
230
|
+
);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Farm executes every generated native `map()` and then compares the committed and final item
|
|
234
|
+
identities once. It validates final length, keys, and positions and patches each final changed row
|
|
235
|
+
once; intermediate arrays never receive DOM work. Every user `map()` remains O(n); this removes the
|
|
236
|
+
keyed runtime's second full key-and-binding scan. Queued hints compose. The source method lookup,
|
|
237
|
+
native result, callback order, and thrown errors are preserved.
|
|
238
|
+
|
|
239
|
+
The setter may be concise or use a block containing exactly one value-returning `return`. Extra
|
|
240
|
+
updater statements, conditional returns, and missing returns keep complete keyed reconciliation.
|
|
241
|
+
Each stage requires an inline synchronous conditional mapper that returns the original item on at
|
|
242
|
+
least one path and an object-spread replacement on another. The callback may be a concise expression
|
|
243
|
+
or a structured block containing fully returning `if` or `switch` branches and proven local `const`
|
|
244
|
+
aliases. Each alias needs a simple identifier and compiler-safe initializer. A `switch` needs one
|
|
245
|
+
`default`, a complete return from every case, safe discriminant and case expressions, and no trailing
|
|
246
|
+
statements. Consecutive empty labels may share the next fully returning body; partially executed
|
|
247
|
+
fallthrough and dangling labels are rejected. An unsupported mapper anywhere in the chain disables
|
|
248
|
+
the complete setter hint. Custom methods still execute but record no metadata. Key changes, structural
|
|
249
|
+
edits, sparse or subclassed arrays, relevant mixed dependencies, and failed runtime checks use the
|
|
250
|
+
existing complete reconciliation and LIS path. Non-functional setters, derived collections,
|
|
251
|
+
referenced callbacks, mutable or destructured declarations, effectful initializers, other
|
|
252
|
+
intermediate statements, partial or dangling fallthrough, mutating mappers, and other unproven forms
|
|
253
|
+
are simply not hinted. No new option is required. A compiler report counts prepared map calls as
|
|
254
|
+
`keyedMapUpdateHints`. The separate hint runtime capability is imported only when that count is
|
|
255
|
+
nonzero, so direct-only and ordinary keyed builds do not retain it.
|
|
256
|
+
|
|
257
|
+
The same optional runtime recognizes conservative immutable appends on a direct keyed array:
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
setItems((current) => [...current, nextItem]);
|
|
261
|
+
setItems((current) => [...current, ...nextItems]);
|
|
262
|
+
setItems((current) => {
|
|
263
|
+
return [...current, ...nextItems];
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Because every existing item keeps its key and index, Farm validates the committed source and the
|
|
268
|
+
queued append chain, reads keys and descriptors only for the appended suffix, and mounts that
|
|
269
|
+
suffix in one fragment. Existing row DOM is left untouched. The setter may be concise or use a
|
|
270
|
+
block containing exactly one direct value-returning `return`. Extra statements, conditional
|
|
271
|
+
returns, and missing returns keep complete keyed reconciliation. Native arrays are required;
|
|
272
|
+
middle insertion, removal, direct replacement, duplicate keys, rows that read the collection
|
|
273
|
+
itself, and failed runtime checks use complete keyed reconciliation. A key that reads the
|
|
274
|
+
collection prevents hint emission entirely. The compiler report exposes the emitted-site count as
|
|
275
|
+
`keyedArrayAppendHints`.
|
|
276
|
+
|
|
277
|
+
Adjacent removal and append setters can share the same committed-row proof:
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
setItems((current) => current.filter((item) => item.id !== expiredId));
|
|
281
|
+
setItems((current) => [...current, incoming]);
|
|
282
|
+
|
|
283
|
+
setItems((current) => current.slice(start, end));
|
|
284
|
+
setItems((current) => [...current, ...incoming]);
|
|
285
|
+
|
|
286
|
+
setItems((current) => current.slice(start, end));
|
|
287
|
+
setItems((current) => [...current, incoming]);
|
|
288
|
+
setItems((current) =>
|
|
289
|
+
current.map((item) => (item.id === targetId ? { ...item, label: nextLabel } : item)),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
setItems((current) => current.filter((item) => item.id !== expiredId));
|
|
293
|
+
setItems((current) =>
|
|
294
|
+
current.map((item) => (item.id === targetId ? { ...item, label: nextLabel } : item)),
|
|
295
|
+
);
|
|
296
|
+
setItems((current) => [...current, incoming]);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The setters still execute in order and still create their normal native arrays. Before changing the
|
|
300
|
+
DOM, Farm validates the original committed token, every filter or slice survivor, the complete final
|
|
301
|
+
length, and every incoming key. It prepares all incoming keys, descriptors, bindings, and detached
|
|
302
|
+
host rows first; only then does it remove rejected rows, update survivor indexes, and append the new
|
|
303
|
+
suffix in one fragment. Existing survivors are neither rebound nor recreated, and additional
|
|
304
|
+
adjacent append setters are collapsed into the same final suffix. This keeps the unavoidable native
|
|
305
|
+
filter or slice work while removing the keyed runtime's second full scan.
|
|
306
|
+
|
|
307
|
+
One or more immediately adjacent safe same-key `map()` setters may run between the filter or slice
|
|
308
|
+
and the append, after the append, or in both positions. A safe map may also precede the structural
|
|
309
|
+
removal. Farm carries the original survivor indices or retained interval through those native maps
|
|
310
|
+
and records each replacement against its committed row. Before touching the DOM it validates the
|
|
311
|
+
complete dense result, every survivor identity, every changed key and binding, and every incoming
|
|
312
|
+
row. It then removes only rejected rows, patches only changed survivors, and creates the suffix from
|
|
313
|
+
its final values. The owner stays mounted, survivor DOM identity is preserved, and the suffix is
|
|
314
|
+
appended once.
|
|
315
|
+
|
|
316
|
+
Only concise updater results and blocks with one direct value-returning `return` that form one
|
|
317
|
+
direct chain for the same state array are linked. The row and key must be compiler-owned and
|
|
318
|
+
index-independent. A reorder in the structural-append chain, an
|
|
319
|
+
unsupported or non-adjacent map, an unhinted update to that state, another dirty row dependency,
|
|
320
|
+
collection-reading binding, custom, sparse, or subclassed array, nested or React-owned row,
|
|
321
|
+
duplicate final key, or reuse of any committed key in the appended suffix keeps complete React
|
|
322
|
+
reconciliation before a DOM write. A mapped survivor whose key changes also falls back before
|
|
323
|
+
mutation. No API or configuration is added. Compiler reports use the existing
|
|
324
|
+
`keyedArrayFilterHints`, `keyedArraySliceHints`, and `keyedArrayAppendHints` counts, and modules
|
|
325
|
+
without both accepted operations omit the composed runtime.
|
|
326
|
+
|
|
327
|
+
The mirror-image prepend form is supported when the keyed row and key do not read the row index:
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
setItems((current) => [nextItem, ...current]);
|
|
331
|
+
setItems((current) => [...nextItems, ...current]);
|
|
332
|
+
setItems((current) => {
|
|
333
|
+
return [...nextItems, ...current];
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
setItems((current) => current.filter((item) => item.id !== expiredId));
|
|
337
|
+
setItems((current) => [nextItem, ...current]);
|
|
338
|
+
|
|
339
|
+
setItems((current) => current.slice(start, end));
|
|
340
|
+
setItems((current) => [...nextItems, ...current]);
|
|
341
|
+
|
|
342
|
+
setItems((current) => current.filter((item) => item.id !== expiredId));
|
|
343
|
+
setItems((current) => [nextItem, ...current]);
|
|
344
|
+
setItems((current) =>
|
|
345
|
+
current.map((item) => (item.id === targetId ? { ...item, label: nextLabel } : item)),
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
setItems((current) => current.slice(start, end));
|
|
349
|
+
setItems((current) =>
|
|
350
|
+
current.map((item) => (item.id === targetId ? { ...item, label: nextLabel } : item)),
|
|
351
|
+
);
|
|
352
|
+
setItems((current) => [nextItem, ...current]);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Farm validates the committed source and queued prepend chain, creates only the new prefix, inserts
|
|
356
|
+
it before the first existing row, and shifts the stored indexes used by delegated row events.
|
|
357
|
+
When an adjacent filter or bounded slice runs first, the compiler also carries the exact survivor
|
|
358
|
+
positions into the prepend. The runtime removes only rejected rows, preserves every surviving DOM
|
|
359
|
+
node, and creates only the final prefix instead of rescanning and rebinding the whole result.
|
|
360
|
+
Multiple adjacent prepends share the same proof.
|
|
361
|
+
|
|
362
|
+
The setter may be concise or use a block containing exactly one direct value-returning `return`.
|
|
363
|
+
Extra statements, conditional returns, and missing returns keep complete keyed reconciliation.
|
|
364
|
+
|
|
365
|
+
One or more immediately adjacent safe same-key `map()` setters may run after the prepend or between
|
|
366
|
+
the removal and prepend. A safe map may also run immediately before the removal. Farm carries every
|
|
367
|
+
survivor back to its committed row, validates the complete dense result and every final key, then
|
|
368
|
+
patches only changed survivors and creates the prefix from its final mapped values. A surviving DOM
|
|
369
|
+
node keeps its identity even when its item object changes.
|
|
370
|
+
|
|
371
|
+
Index-aware rows, collection-reading bindings or keys, React-owned or nested row structures,
|
|
372
|
+
middle insertion, direct replacement, a reordered structural chain, an unsupported or non-adjacent
|
|
373
|
+
map, duplicate final keys, reuse of any committed key in the new prefix, custom or sparse arrays,
|
|
374
|
+
and failed validation use complete keyed reconciliation before any DOM mutation. A mapped survivor
|
|
375
|
+
whose key changes also falls back before mutation. The compiler report exposes the emitted-site
|
|
376
|
+
count as `keyedArrayPrependHints`; no option or component API is added, and modules without the
|
|
377
|
+
accepted removal, prepend, and map chain omit its mapped runtime.
|
|
378
|
+
|
|
379
|
+
Native slices with compiler-safe bounds can carry their exact retained interval into the same
|
|
380
|
+
removal runtime:
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
setItems((current) => current.slice(1_000));
|
|
384
|
+
setItems((current) => current.slice(0, -1_000));
|
|
385
|
+
setItems((current) => current.slice(2, 8));
|
|
386
|
+
setItems((current) => current.slice(trimCount));
|
|
387
|
+
setItems((current) => current.slice(visible.start, visible.end));
|
|
388
|
+
setItems((current) => {
|
|
389
|
+
return current.slice(visible.start, visible.end);
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
For compiler-owned host rows whose render and key do not read the row index, Farm validates the
|
|
394
|
+
committed source and queued slice chain, preserves every surviving DOM row, and removes only rows
|
|
395
|
+
outside the retained interval. A slice-only chain does not reread surviving keys, descriptors, or
|
|
396
|
+
bindings. One or two safe-integer literals or compiler-safe runtime expressions are required.
|
|
397
|
+
Identifiers, property reads, side-effect-free arithmetic and conditionals, and safe `Math` calls
|
|
398
|
+
are supported. The setter may be concise or use a block containing exactly one direct
|
|
399
|
+
value-returning `return`. Farm preserves native method lookup, argument evaluation, coercion,
|
|
400
|
+
results, and errors, then records metadata only when the evaluated bounds are already safe integers.
|
|
401
|
+
Calls, assignments, updates, unsafe evaluated bounds, updater blocks with extra statements,
|
|
402
|
+
directives, conditional returns, or no value-returning `return`, chained updates, custom slice
|
|
403
|
+
methods, sparse or subclassed arrays, index-aware or collection-reading rows, React-owned structures,
|
|
404
|
+
and failed validation keep complete keyed reconciliation. No option or component is added. The
|
|
405
|
+
compiler report exposes the emitted-site count as `keyedArraySliceHints`.
|
|
406
|
+
|
|
407
|
+
A fixed-size feed can combine that retained tail with a new keyed suffix:
|
|
408
|
+
|
|
409
|
+
```tsx
|
|
410
|
+
setItems((current) => [...current.slice(1), nextItem]);
|
|
411
|
+
setItems((current) => [...current.slice(1_000), ...nextItems]);
|
|
412
|
+
|
|
413
|
+
const trimCount = pageSize * pagesToExpire;
|
|
414
|
+
setItems((current) => [...current.slice(trimCount), ...nextItems]);
|
|
415
|
+
setItems((current) => {
|
|
416
|
+
return [...current.slice(trimCount), ...nextItems];
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Farm executes the ordinary native slice and array construction, then validates the complete hint
|
|
421
|
+
chain back to the committed source, retained item identities, and final incoming keys. Multiple
|
|
422
|
+
rolling setters queued before one compiler flush collapse into one cumulative prefix removal; rows
|
|
423
|
+
introduced by an earlier setter are created only if they remain in the final suffix. Farm removes
|
|
424
|
+
only the expired committed prefix, leaves the retained DOM rows in place, and creates only that
|
|
425
|
+
final incoming suffix. The setter may be concise or use a block containing exactly one direct
|
|
426
|
+
value-returning `return`. This form supports one safe-integer literal or compiler-safe runtime slice
|
|
427
|
+
bound and compiler-owned, index-independent host rows. Identifiers, property reads,
|
|
428
|
+
side-effect-free arithmetic and conditionals, and safe `Math` calls are supported while preserving
|
|
429
|
+
native lookup, evaluation, results, and errors.
|
|
430
|
+
|
|
431
|
+
Immediately adjacent safe same-key maps may run before, after, or between one or more rolling
|
|
432
|
+
setters in the same synchronous setter segment:
|
|
433
|
+
|
|
434
|
+
```tsx
|
|
435
|
+
setItems((current) =>
|
|
436
|
+
current.map((item) => (item.id === editedId ? { ...item, label: nextLabel } : item)),
|
|
437
|
+
);
|
|
438
|
+
setItems((current) => [...current.slice(trimCount), ...nextItems]);
|
|
439
|
+
setItems((current) =>
|
|
440
|
+
current.map((item) => (item.id === selectedId ? { ...item, selected: true } : item)),
|
|
441
|
+
);
|
|
442
|
+
setItems((current) => [...current.slice(secondTrimCount), ...laterItems]);
|
|
443
|
+
setItems((current) =>
|
|
444
|
+
current.map((item) => (item.id === editedId ? { ...item, status: "ready" } : item)),
|
|
445
|
+
);
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Each map may use a compiler-safe nested conditional to update several retained rows.
|
|
449
|
+
The compiler carries mapped survivor identity through the retained tail, creates incoming rows
|
|
450
|
+
from their final mapped values, and patches only changed committed survivors. A single rolling
|
|
451
|
+
setter uses the optional structural append/map runtime. Multiple rolling setters keep their
|
|
452
|
+
cumulative retained-window metadata and mapped provenance through an optional runtime dedicated to
|
|
453
|
+
mapped rolling chains. No new public helper, option, or always-loaded browser code is added.
|
|
454
|
+
|
|
455
|
+
Effectful expressions, reused keys, updater blocks with extra statements, directives, conditional
|
|
456
|
+
returns, or no value-returning `return`, custom slice behavior, collection-reading or index-aware
|
|
457
|
+
rows, nested or React-owned rows, mixed or unhinted queued chains, a statement or unsupported setter
|
|
458
|
+
inside the segment, changed mapped keys, unsafe or no-op evaluated bounds, and failed checks use
|
|
459
|
+
complete keyed reconciliation. Unmapped rolling modules retain the smaller optional all-hint
|
|
460
|
+
runtime. Reports expose each emitted rolling site as `keyedArrayRollingWindowHints`, each rolling
|
|
461
|
+
step retained across a multi-window mapped chain as `keyedArrayMappedRollingWindowChainHints`, and
|
|
462
|
+
each safe map as `keyedMapUpdateHints`.
|
|
463
|
+
|
|
464
|
+
Native known-position updates can avoid a complete keyed scan too:
|
|
465
|
+
|
|
466
|
+
```tsx
|
|
467
|
+
setItems((current) => current.toSpliced(selectedIndex, 0, nextItem));
|
|
468
|
+
setItems((current) => current.toSpliced(selectedIndex, 0, nextA, nextB));
|
|
469
|
+
setItems((current) => current.toSpliced(selectedIndex, 0, ...incomingItems));
|
|
470
|
+
setItems((current) => current.toSpliced(selectedIndex, 1));
|
|
471
|
+
setItems((current) => current.toSpliced(selectedIndex, 25));
|
|
472
|
+
setItems((current) => current.toSpliced(selectedIndex, 1, replacement));
|
|
473
|
+
setItems((current) => current.toSpliced(selectedIndex, 25, ...replacements));
|
|
474
|
+
setItems((current) => current.with(selectedIndex, replacement));
|
|
475
|
+
setItems((current) => {
|
|
476
|
+
return current.toSpliced(selectedIndex, 0, nextItem);
|
|
477
|
+
});
|
|
478
|
+
setItems((current) => {
|
|
479
|
+
return current.with(selectedIndex, replacement);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Two same-key windows may be queued before one compiler flush.
|
|
483
|
+
setItems((current) => current.toSpliced(firstIndex, 25, ...firstRefresh));
|
|
484
|
+
setItems((current) => current.toSpliced(secondIndex, 25, ...secondRefresh));
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
The compiler recognizes a concise functional setter or a setter block containing exactly one direct
|
|
488
|
+
`return`. Its position must be either a safe-integer literal or a compiler-safe runtime expression,
|
|
489
|
+
such as an identifier, property read, arithmetic, conditional, or safe `Math` call. The update must insert
|
|
490
|
+
one or more compiler-safe items with zero removals, remove one item or a fixed positive safe-integer
|
|
491
|
+
literal range, replace one item through either `toSpliced()` or `with()`, or replace a fixed positive
|
|
492
|
+
safe-integer literal window with compiler-safe explicit items or a safe spread. Farm preserves method lookup,
|
|
493
|
+
evaluates the position and remaining arguments once in their original order, executes the ordinary
|
|
494
|
+
native call, and records metadata only after validating the committed native source, result, and
|
|
495
|
+
actual safe-integer position and clamped removal count. A batch requires at least two evaluated
|
|
496
|
+
items at runtime. Exact-window replacement supports a safe spread that evaluates to zero, one, or
|
|
497
|
+
many items. A fresh-key window creates every incoming key, descriptor, binding snapshot, and
|
|
498
|
+
detached row before changing the live DOM, rejects key collisions, and mounts the complete batch
|
|
499
|
+
through one document fragment. It then removes only the replaced window. When the incoming window
|
|
500
|
+
has the same length and the same keys in the same order, Farm prepares every binding read and
|
|
501
|
+
changed DOM target across the complete window first, patches the existing rows in place, and
|
|
502
|
+
updates the stored row objects used by later events. That path creates no descriptors or DOM rows,
|
|
503
|
+
and preserves row identity, focus, and selection. Validated same-key row and window refreshes also
|
|
504
|
+
keep the existing row map and element lookup, and skip redundant cache cleanup instead of
|
|
505
|
+
rebuilding whole-list bookkeeping. Source and key validation still run, and each flush commits the
|
|
506
|
+
new collection token for subsequent updates. Structural changes retain index rebuilding and cache
|
|
507
|
+
cleanup. A window may instead grow or shrink while it
|
|
508
|
+
reorders keys from inside its own removed interval and mixes them with globally fresh keys. Farm
|
|
509
|
+
prepares all reused binding updates, new descriptors, binding snapshots, and detached rows before
|
|
510
|
+
the first DOM write. It removes only retired rows, preserves each reused row, batches adjacent new
|
|
511
|
+
rows in a fragment, updates shifted suffix indexes, and applies LIS only to the reused part of that
|
|
512
|
+
interval so it moves the fewest connected rows needed by the local permutation. Rows outside the
|
|
513
|
+
interval are not rerendered or rebound.
|
|
514
|
+
Multiple length-preserving same-key windows
|
|
515
|
+
queued before one compiler flush compose into one atomic refresh. Fixed-length queued windows may
|
|
516
|
+
mix same-key refreshes with globally new final keys, and both disjoint and overlapping windows are
|
|
517
|
+
supported. An overlapping position uses the last queued value; intermediate identities are never
|
|
518
|
+
mounted. Farm validates the complete chain and final key set, then prepares every touched key,
|
|
519
|
+
binding value, DOM target, new descriptor, binding snapshot, and disconnected DOM row before the
|
|
520
|
+
first write. It patches same-key positions and swaps only final fresh-key positions. Untouched rows
|
|
521
|
+
keep their identity and are not recreated. The runtime otherwise creates one inserted row,
|
|
522
|
+
removes only the known row or range, or patches/replaces one row at that position without rereading
|
|
523
|
+
every existing key, descriptor, and binding. Surviving rows keep their DOM identity. Index-aware or
|
|
524
|
+
collection-reading rows, custom methods, position expressions with user calls or mutations,
|
|
525
|
+
runtime positions that are not safe integers, dynamic, zero, negative, or fractional removal
|
|
526
|
+
counts, other `toSpliced()` forms, updater blocks with extra statements, directives, conditional
|
|
527
|
+
returns, or no value-returning `return`, unsafe incoming expressions, queued chains containing a
|
|
528
|
+
structural window or unhinted intermediate update, existing keys moved from outside the removed
|
|
529
|
+
interval, duplicate final keys, nested or React-owned rows, and failed checks use complete keyed
|
|
530
|
+
reconciliation before the fast path mutates the DOM.
|
|
531
|
+
Reports expose the site count as `keyedArrayPositionHints`. Batch insertion and exact-window
|
|
532
|
+
replacement use progressively separate optional runtime capabilities, so existing single-position
|
|
533
|
+
and batch-only bundles do not retain window validation or replacement code.
|
|
534
|
+
|
|
535
|
+
A direct native reverse can carry its complete permutation to a separate optional runtime:
|
|
536
|
+
|
|
537
|
+
```tsx
|
|
538
|
+
setItems((current) => current.toReversed());
|
|
539
|
+
setItems((current) => {
|
|
540
|
+
return current.toReversed();
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Farm recognizes the concise form and a block containing exactly one direct `return`. It preserves
|
|
545
|
+
the method lookup, call result, and errors, then records metadata only for the native method on an
|
|
546
|
+
ordinary array whose chain starts at the committed collection. One direct reverse
|
|
547
|
+
verifies equal lengths and every reversed item identity before moving the existing keyed elements
|
|
548
|
+
with the minimum `n - 1` connected DOM moves. It does not reread row keys, descriptors, or bindings
|
|
549
|
+
and does not run the generic LIS calculation. Consecutive concise native `toReversed()` and
|
|
550
|
+
`toSorted()` setters queued before one flush compose into one final validated permutation. The
|
|
551
|
+
runtime skips intermediate DOM states. When the proven chain contains only reversals, Farm tracks
|
|
552
|
+
their parity relative to the committed rows: an even count validates exact identity and performs
|
|
553
|
+
no DOM moves, source-item map construction, or LIS; an odd count validates exact reverse and uses
|
|
554
|
+
the same minimum `n - 1` moves as one reverse. A chain containing a sort keeps the general final
|
|
555
|
+
permutation and uses LIS once. A concise updater or a block containing exactly one direct
|
|
556
|
+
value-returning `return` may also chain two or more native reorder operations:
|
|
557
|
+
|
|
558
|
+
```tsx
|
|
559
|
+
setItems((current) => current.toSorted((left, right) => left.rank - right.rank).toReversed());
|
|
560
|
+
setItems((current) => {
|
|
561
|
+
return current.toSorted((left, right) => left.rank - right.rank).toReversed();
|
|
562
|
+
});
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Farm evaluates every lookup and call in JavaScript order, carries the same committed token through
|
|
566
|
+
the pipeline, and reconciles only its final result. Index-aware or collection-reading rows,
|
|
567
|
+
arguments to `toReversed()`, referenced comparators, computed methods, updater blocks with extra
|
|
568
|
+
statements, directives, conditional returns, or no returned value, custom methods, sparse or
|
|
569
|
+
subclassed behavior, structural calls after reordering, an unhinted intermediate update, nested or
|
|
570
|
+
React-owned rows, and failed checks keep complete keyed reconciliation.
|
|
571
|
+
|
|
572
|
+
A compiler-safe `filter()` or bounded `slice()` prefix may precede the native reorder suffix in a
|
|
573
|
+
concise updater or exact one-return block. A filter predicate may be concise or contain exactly one
|
|
574
|
+
direct value-returning `return`:
|
|
575
|
+
|
|
576
|
+
```tsx
|
|
577
|
+
setItems((current) =>
|
|
578
|
+
current
|
|
579
|
+
.filter((item) => {
|
|
580
|
+
return item.visible;
|
|
581
|
+
})
|
|
582
|
+
.toSorted((left, right) => left.rank - right.rank)
|
|
583
|
+
.toReversed(),
|
|
584
|
+
);
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
Farm validates the complete survivor set and final order before the first DOM write, removes only
|
|
588
|
+
rejected rows, and applies LIS once to the survivors. Concise or exact one-return filter/slice and
|
|
589
|
+
reorder setters also compose when queued in that order before one flush. Unsupported calls or a
|
|
590
|
+
structural call after a reorder fall back. Reports count each compiled step in the existing filter,
|
|
591
|
+
slice, sort, or reverse counter, and modules without those operations omit their optional runtimes.
|
|
592
|
+
Farm does not polyfill `Array.prototype.toReversed`.
|
|
593
|
+
|
|
594
|
+
A compiler-safe same-key map may also appear before or after native reorder steps:
|
|
595
|
+
|
|
596
|
+
```tsx
|
|
597
|
+
setItems((current) =>
|
|
598
|
+
current
|
|
599
|
+
.map((item) => (item.id === editedId ? { ...item, label: nextLabel } : item))
|
|
600
|
+
.map((item) => (item.id === editedId ? { ...item, rank: nextRank } : item))
|
|
601
|
+
.toSorted((left, right) => left.rank - right.rank),
|
|
602
|
+
);
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
One callback may select several rows through nested expressions, structured early returns, or a
|
|
606
|
+
fully returning `switch`:
|
|
607
|
+
|
|
608
|
+
```tsx
|
|
609
|
+
setItems((current) =>
|
|
610
|
+
current.map((item) => {
|
|
611
|
+
const matchesReviewed = item.id === reviewedId;
|
|
612
|
+
if (matchesReviewed) return { ...item, status: "reviewed" };
|
|
613
|
+
const matchesEscalated = item.id === escalatedId;
|
|
614
|
+
if (matchesEscalated) {
|
|
615
|
+
return { ...item, status: "escalated", priority: 1 };
|
|
616
|
+
}
|
|
617
|
+
return item;
|
|
618
|
+
}),
|
|
619
|
+
);
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Every condition must be compiler-safe, and every leaf must return the original item or a safe
|
|
623
|
+
object spread of it, with at least one unchanged and one replacement path. Concise expressions and
|
|
624
|
+
blocks made from fully returning `if` or `switch` branches plus local `const` aliases are accepted.
|
|
625
|
+
Each alias must have a simple identifier and compiler-safe initializer. A switch requires one
|
|
626
|
+
`default` and complete returning cases. Consecutive empty labels may share the next body, while
|
|
627
|
+
partial or dangling fallthrough is rejected. Mutable or destructured declarations, effectful
|
|
628
|
+
initializers, other intermediate statements, and ambiguous leaves use complete keyed
|
|
629
|
+
reconciliation. A runtime key change also falls back before any DOM write.
|
|
630
|
+
|
|
631
|
+
The functional setter may be concise or a block containing exactly one direct value-returning
|
|
632
|
+
`return`. Farm executes every native map and reorder call normally. For consecutive accepted maps,
|
|
633
|
+
it checks each native call but compares only the input and final result of that map segment, avoiding
|
|
634
|
+
a full lineage scan for every intermediate array. It verifies the committed token, dense-array
|
|
635
|
+
shape, a one-to-one source-item match, and every final replacement key before touching the DOM, then runs one
|
|
636
|
+
LIS and patches each changed row once. Unchanged rows need no second key or binding read. Queued
|
|
637
|
+
supported map-and-reorder setters compose against the same committed rows. Every accepted map
|
|
638
|
+
requires an inline synchronous conditional mapper whose leaves return either the original item or
|
|
639
|
+
an object-spread replacement, plus index-independent compiler-owned host rows. Concise expressions
|
|
640
|
+
and structured blocks with proven local `const` aliases plus fully returning `if` or `switch`
|
|
641
|
+
branches are supported.
|
|
642
|
+
Changed keys, referenced callbacks, mutable, destructured, or effectful declarations, other
|
|
643
|
+
intermediate statements or partial/dangling fallthrough, unconditional replacements, `thisArg`,
|
|
644
|
+
structural calls in the same chain, computed or custom methods, sparse or subclassed arrays,
|
|
645
|
+
collection-reading bindings, nested or React-owned rows, and failed checks use complete keyed
|
|
646
|
+
reconciliation. Reports use the existing map, sort, and reorder hint counters, and unrelated modules
|
|
647
|
+
do not retain this optional runtime.
|
|
648
|
+
|
|
649
|
+
The maps may also finish the concise pipeline:
|
|
650
|
+
|
|
651
|
+
```tsx
|
|
652
|
+
setItems((current) =>
|
|
653
|
+
current
|
|
654
|
+
.toReversed()
|
|
655
|
+
.map((item) => (item.id === editedId ? { ...item, label: nextLabel } : item))
|
|
656
|
+
.map((item) => (item.id === editedId ? { ...item, rank: nextRank } : item)),
|
|
657
|
+
);
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Farm retains the preceding reorder instead of treating the mapped result as an unhinted snapshot.
|
|
661
|
+
An exact reverse uses the minimum-move reverse path without a general source-item lookup or LIS. A
|
|
662
|
+
sort remains an ambiguous permutation, so Farm performs one validated source lookup and LIS pass.
|
|
663
|
+
Safe maps on both sides of a reorder flatten their replacements back to the same committed rows.
|
|
664
|
+
|
|
665
|
+
Safe maps may also run before, between, or after index-independent `filter` or `slice` steps in one
|
|
666
|
+
concise functional setter or one block with a single direct `return`. A final native reorder is optional:
|
|
667
|
+
|
|
668
|
+
```tsx
|
|
669
|
+
setItems((current) =>
|
|
670
|
+
current
|
|
671
|
+
.filter((item) => item.visible)
|
|
672
|
+
.map((item) => (item.id === editedId ? { ...item, label: nextLabel } : item))
|
|
673
|
+
.slice(0, limit)
|
|
674
|
+
.toSorted((left, right) => left.rank - right.rank),
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
setItems((current) =>
|
|
678
|
+
current
|
|
679
|
+
.map((item) => (item.id === editedId ? { ...item, label: nextLabel } : item))
|
|
680
|
+
.filter((item) => item.visible)
|
|
681
|
+
.slice(0, limit),
|
|
682
|
+
);
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
The structural helpers retain both the committed survivor indices and mapped-item sources across
|
|
686
|
+
each accepted step. The final commit removes rejected rows, performs LIS moves only when a reorder
|
|
687
|
+
needs them, and patches bindings only for changed survivors. Every native callback and method still
|
|
688
|
+
runs in JavaScript order.
|
|
689
|
+
|
|
690
|
+
The structural and map steps may also be split across immediately adjacent setter calls in either
|
|
691
|
+
order:
|
|
692
|
+
|
|
693
|
+
```tsx
|
|
694
|
+
// Map, then structural work.
|
|
695
|
+
setItems((current) =>
|
|
696
|
+
current.map((item) => (item.id === editedId ? { ...item, label: nextLabel } : item)),
|
|
697
|
+
);
|
|
698
|
+
setItems((current) => current.filter((item) => item.visible));
|
|
699
|
+
setItems((current) => current.slice(0, limit));
|
|
700
|
+
|
|
701
|
+
// Structural work, then maps.
|
|
702
|
+
setItems((current) => current.filter((item) => item.visible));
|
|
703
|
+
setItems((current) => current.slice(0, limit));
|
|
704
|
+
setItems((current) =>
|
|
705
|
+
current.map((item) => (item.id === editedId ? { ...item, label: nextLabel } : item)),
|
|
706
|
+
);
|
|
707
|
+
setItems((current) =>
|
|
708
|
+
current.map((item) => (item.id === editedId ? { ...item, rank: nextRank } : item)),
|
|
709
|
+
);
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
Farm links only consecutive calls to the same setter. A leading map retains its first committed
|
|
713
|
+
source through later structural work; a terminal map consumes the saved survivor lineage from the
|
|
714
|
+
preceding filter or slice. Multiple safe steps in either segment may share the proof, and mixed
|
|
715
|
+
map/structural/map sequences compose. Every result and key is validated before the queued commit
|
|
716
|
+
touches the DOM. An intervening statement, another setter, an unsafe callback, a changed key, or
|
|
717
|
+
failed runtime validation keeps complete keyed reconciliation.
|
|
718
|
+
|
|
719
|
+
The survivor proof may continue through adjacent native reorder setters:
|
|
720
|
+
|
|
721
|
+
```tsx
|
|
722
|
+
setItems((current) => current.filter((item) => item.visible));
|
|
723
|
+
setItems((current) =>
|
|
724
|
+
current.map((item) => (item.id === editedId ? { ...item, rank: nextRank } : item)),
|
|
725
|
+
);
|
|
726
|
+
setItems((current) => current.toSorted((left, right) => left.rank - right.rank));
|
|
727
|
+
setItems((current) => current.toReversed());
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
Farm retains the committed survivor positions and same-key replacements until the final queued
|
|
731
|
+
value. The commit removes rejected rows, patches changed survivors, and applies the final order in
|
|
732
|
+
one validated DOM transaction. Exact reverses use the minimum-move reverse path; an arbitrary sort
|
|
733
|
+
uses one source lookup and LIS. Non-adjacent calls, another setter, unsupported syntax, or a failed
|
|
734
|
+
shape/key check keep complete reconciliation.
|
|
735
|
+
|
|
736
|
+
The reorder and safe maps may also be adjacent setter calls in the same synchronous block:
|
|
737
|
+
|
|
738
|
+
```tsx
|
|
739
|
+
setItems((current) => current.toReversed());
|
|
740
|
+
setItems((current) =>
|
|
741
|
+
current.map((item) => (item.id === editedId ? { ...item, label: nextLabel } : item)),
|
|
742
|
+
);
|
|
743
|
+
setItems((current) =>
|
|
744
|
+
current.map((item) => (item.id === editedId ? { ...item, rank: nextRank } : item)),
|
|
745
|
+
);
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
The compiler links only consecutive calls to the same setter. It keeps the reorder token through
|
|
749
|
+
each accepted native map, then validates row keys before applying the same exact-reverse or
|
|
750
|
+
sort-permutation path at commit. Adjacent compiler-safe filter or slice work switches to the
|
|
751
|
+
structural proof above. An intervening statement, another setter, an unsupported map, or a changed
|
|
752
|
+
key keeps the ordinary complete fallback. Standalone map-only components do not retain the
|
|
753
|
+
map-and-reorder runtime.
|
|
754
|
+
|
|
755
|
+
The mirror order is supported as well: two or more adjacent concise same-key map setters may be
|
|
756
|
+
followed by a concise native reverse or sort setter. Farm records their replacement lineage from
|
|
757
|
+
the committed collection and carries it into the final reorder. Only adjacent calls to the same
|
|
758
|
+
setter are linked; intervening work and unsupported updates retain complete reconciliation.
|
|
759
|
+
|
|
760
|
+
Mapped lineage also continues through multiple adjacent native reorder setters. For example,
|
|
761
|
+
`map -> reverse -> sort -> reverse` keeps the same committed-row proof until the final value, then
|
|
762
|
+
performs one validated keyed reconciliation. A concise `map().toReversed()` pipeline may start the
|
|
763
|
+
same chain. Adjacent compiler-safe filter or slice work changes it into a structural chain while
|
|
764
|
+
retaining the committed source. Every updater and native method still executes in order; another
|
|
765
|
+
setter, intervening work, or an unsupported method ends the chain and preserves the complete
|
|
766
|
+
fallback.
|
|
767
|
+
|
|
768
|
+
A reverse before the safe map pipeline may be queued in a separate setter:
|
|
769
|
+
|
|
770
|
+
```tsx
|
|
771
|
+
setItems((current) => current.toReversed());
|
|
772
|
+
setItems((current) =>
|
|
773
|
+
current.map((item) => (item.id === editedId ? { ...item, label: nextLabel } : item)).toReversed(),
|
|
774
|
+
);
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
A direct `toReversed()` suffix is more specific than an arbitrary permutation. When all preceding
|
|
778
|
+
maps retain committed row order, Farm validates the mirrored source-to-result relation and every
|
|
779
|
+
changed binding before touching the DOM, then performs the minimum-move reverse without building a
|
|
780
|
+
second item lookup map or running LIS. Additional exact reversals toggle that proof between reverse
|
|
781
|
+
and identity. Therefore two reversals after safe maps patch changed rows in committed order without
|
|
782
|
+
moving DOM nodes or creating the generic source map; three use the exact reverse path. Exact reverse
|
|
783
|
+
metadata also composes across queued setters: a queued reverse followed by safe maps and another
|
|
784
|
+
reverse retains exact identity, so only changed row bindings are patched. A preceding sort or any
|
|
785
|
+
ambiguous order keeps the general permutation path.
|
|
786
|
+
|
|
787
|
+
A direct native immutable sort can use the same optional reorder runtime:
|
|
788
|
+
|
|
789
|
+
```tsx
|
|
790
|
+
setItems((current) => current.toSorted((left, right) => left.rank - right.rank));
|
|
791
|
+
setLabels((current) => current.toSorted());
|
|
792
|
+
setItems((current) => {
|
|
793
|
+
return current.toSorted((left, right) => left.rank - right.rank);
|
|
794
|
+
});
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
Farm recognizes a concise functional setter or a block containing exactly one direct `return`,
|
|
798
|
+
with no comparator or an inline synchronous comparator from the compiler-safe expression subset.
|
|
799
|
+
It preserves the original method lookup, comparator, stable native result, and errors. After the
|
|
800
|
+
native sort runs, the runtime validates an ordinary dense array whose reorder chain starts at the
|
|
801
|
+
committed collection, equal lengths, and a unique one-to-one item-identity permutation. It then
|
|
802
|
+
uses LIS to move only `n - LIS` keyed DOM nodes without rereading row keys, descriptors, or
|
|
803
|
+
bindings. Multiple concise native sorts and reverses queued in one flush, or chained in one concise
|
|
804
|
+
updater, share the original committed token and reconcile only their final permutation. The native
|
|
805
|
+
sorting work itself is unchanged.
|
|
806
|
+
|
|
807
|
+
Index-aware or collection-reading rows, referenced comparators, updater blocks with extra
|
|
808
|
+
statements, directives, conditional returns, or no returned value, computed or unsupported chained
|
|
809
|
+
calls, custom methods, sparse or subclassed arrays, duplicate item identities, unhinted intermediate
|
|
810
|
+
updates, structural calls after reordering, nested or React-owned rows, and failed checks keep
|
|
811
|
+
complete keyed reconciliation. A compiler-safe filter/slice prefix is supported. Reports count each
|
|
812
|
+
compiled sort step as a `keyedArraySortHints` entry; sort shares the optional reorder runtime, and
|
|
813
|
+
Farm does not polyfill `Array.prototype.toSorted`.
|
|
814
|
+
|
|
815
|
+
Concise or single-return block-bodied immutable filters on a direct keyed array can carry removal
|
|
816
|
+
positions into the same optional runtime:
|
|
817
|
+
|
|
818
|
+
```tsx
|
|
819
|
+
setItems((current) => current.filter((item) => item.id !== removedId));
|
|
820
|
+
setItems((current) => {
|
|
821
|
+
return current.filter((item) => item.id !== removedId);
|
|
822
|
+
});
|
|
823
|
+
setItems((current) =>
|
|
824
|
+
current.filter((item) => {
|
|
825
|
+
return item.id !== removedId;
|
|
826
|
+
}),
|
|
827
|
+
);
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
For compiler-owned host rows whose render and key do not observe the row index, Farm validates the
|
|
831
|
+
native filter chain, every surviving item identity, and every surviving key before removing only
|
|
832
|
+
the rejected DOM rows. It does not recreate descriptors or reread bindings for unchanged rows,
|
|
833
|
+
and queued filters compose before one compiler flush. Index-aware rows or predicates, updater blocks
|
|
834
|
+
with extra statements, directives, conditional returns, or no value-returning `return`, predicate
|
|
835
|
+
blocks with those unsupported shapes, custom filter methods, sparse or subclassed arrays,
|
|
836
|
+
collection-reading bindings or keys, React-owned row structures, mixed dirty dependencies, and
|
|
837
|
+
failed validation use complete keyed reconciliation. No option or new component is required. The
|
|
838
|
+
compiler report exposes the emitted-site count as `keyedArrayFilterHints`.
|
|
839
|
+
|
|
116
840
|
An otherwise eligible host row may also contain an inline synchronous React event:
|
|
117
841
|
|
|
118
842
|
```tsx
|
|
@@ -134,7 +858,7 @@ insert, removal, or reorder asks React to reconcile the rows, adopts the committ
|
|
|
134
858
|
reapplies current bindings, and then resumes direct same-key patches. This avoids creating eventful
|
|
135
859
|
DOM outside React's Fiber tree.
|
|
136
860
|
|
|
137
|
-
A keyed row may also
|
|
861
|
+
A non-interactive keyed row may also transfer its safe host conditions to the same keyed instance:
|
|
138
862
|
|
|
139
863
|
```tsx
|
|
140
864
|
<ul>
|
|
@@ -142,7 +866,16 @@ A keyed row may also contain dedicated conditional slots:
|
|
|
142
866
|
<li key={item.id}>
|
|
143
867
|
<span>{item.label}</span>
|
|
144
868
|
<div className="status-slot">
|
|
145
|
-
|
|
869
|
+
<i>State</i>
|
|
870
|
+
{item.done ? (
|
|
871
|
+
<article>
|
|
872
|
+
<strong>{item.label} complete</strong>
|
|
873
|
+
<div>{item.details && <small>{item.description}</small>}</div>
|
|
874
|
+
</article>
|
|
875
|
+
) : (
|
|
876
|
+
<span>In progress</span>
|
|
877
|
+
)}
|
|
878
|
+
<b>Prepared</b>
|
|
146
879
|
</div>
|
|
147
880
|
<section className="details-slot">{item.expanded && <p>{item.description}</p>}</section>
|
|
148
881
|
</li>
|
|
@@ -150,13 +883,59 @@ A keyed row may also contain dedicated conditional slots:
|
|
|
150
883
|
</ul>
|
|
151
884
|
```
|
|
152
885
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
removals
|
|
158
|
-
|
|
159
|
-
|
|
886
|
+
The compiler prepares the row descriptor, static sibling positions, branch factories, and
|
|
887
|
+
text/attribute/style bindings at build time. React creates or hydrates the initial list. After
|
|
888
|
+
mount, a same-key update patches the active branch in place or mounts, replaces, or removes only the
|
|
889
|
+
changed branch. Multiple and deeper host-only conditions share that row scope. Insertions and
|
|
890
|
+
removals create or clean one row scope, while reorders use the normal LIS pass. The component body
|
|
891
|
+
and map callback do not rerun, and no wrapper or hydration marker is added.
|
|
892
|
+
|
|
893
|
+
This compiler-owned tier requires a lowercase non-interactive host row. Hooks, custom components,
|
|
894
|
+
branch events, refs, SVG, fragments, spreads, dangerous HTML, controlled inputs inside a condition,
|
|
895
|
+
and unproven expressions stay on React. An otherwise eligible row with inline events continues to
|
|
896
|
+
use the hybrid boundary: React owns its conditional Fibers and structural commits, while Farm
|
|
897
|
+
compares per-key snapshots and refreshes only changed slots. Duplicate runtime keys or invalid
|
|
898
|
+
adopted DOM switch the complete list to React.
|
|
899
|
+
|
|
900
|
+
An eligible outer keyed row may also own nested keyed ranges:
|
|
901
|
+
|
|
902
|
+
```tsx
|
|
903
|
+
<div>
|
|
904
|
+
{projects.map((project) => (
|
|
905
|
+
<section key={project.id}>
|
|
906
|
+
<h2>{project.name}</h2>
|
|
907
|
+
<ul>
|
|
908
|
+
<li>Tasks</li>
|
|
909
|
+
{project.tasks.map((task) => (
|
|
910
|
+
<li key={task.id}>{task.title}</li>
|
|
911
|
+
))}
|
|
912
|
+
<li>End</li>
|
|
913
|
+
</ul>
|
|
914
|
+
</section>
|
|
915
|
+
))}
|
|
916
|
+
</div>
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
Every outer key receives an isolated inner key table and LIS pass. Moving a project preserves its
|
|
920
|
+
task scope; reordering one task list touches only that project. Surviving project, task, and static
|
|
921
|
+
sibling elements keep their DOM identity, and removing an outer row cleans its inner scope. The
|
|
922
|
+
initial contract accepts non-interactive lowercase inner rows from `.map(...)` or inline `List`,
|
|
923
|
+
including multiple ranges separated by static host siblings. Inner events or forms, Hooks, custom
|
|
924
|
+
components, fragments, refs, SVG, spreads, dangerous HTML, and index keys keep the complete outer
|
|
925
|
+
row on React's fallback. Additional keyed levels that satisfy the same host-only contract are
|
|
926
|
+
analyzed recursively instead of using a fixed nesting limit.
|
|
927
|
+
|
|
928
|
+
When every inner key stays in the same order, the nested scope reuses its row table and skips
|
|
929
|
+
position-map construction and LIS planning. It still validates all keys and updates every child
|
|
930
|
+
scope and binding. Insertions, removals, and reorders keep the existing structural reconciliation;
|
|
931
|
+
duplicate keys or an invalid child scope keep the existing React fallback. This also applies to
|
|
932
|
+
keyed ranges next to host-only conditions inside a nested mixed range.
|
|
933
|
+
|
|
934
|
+
For example, `boards → columns → cards → tags` becomes a tree of keyed scopes. Each stable parent
|
|
935
|
+
key owns its child key table and LIS pass. Moving a board carries its column, card, and tag scopes;
|
|
936
|
+
reordering cards reconciles only the selected column. Block IDs stay globally unique while runtime
|
|
937
|
+
instances are isolated by their complete parent-key path. Removing any parent cleans every
|
|
938
|
+
descendant subscription before its DOM leaves the document.
|
|
160
939
|
|
|
161
940
|
A keyed collection may use derived locals or an inline chain of `filter`, `slice`, `toSorted`, and
|
|
162
941
|
`toReversed`:
|
|
@@ -190,10 +969,9 @@ the TypeScript `lib` with ES2023 and target a runtime that supports them when us
|
|
|
190
969
|
|
|
191
970
|
React remains the fallback and compatibility boundary. An interactive map beside static children,
|
|
192
971
|
a non-host sibling between ranges, a row with a non-inline or async handler, a file input or dynamic
|
|
193
|
-
form control shape, a fragment, a ref, a custom component, or
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
events, lifecycle, and state.
|
|
972
|
+
form control shape, a fragment, a ref, a custom component, or any unsupported nested dynamic
|
|
973
|
+
structure uses the existing React-owned keyed boundary. The outer compiled component can still be
|
|
974
|
+
skipped, but React reconciles that list's rows and owns their events, lifecycle, and state.
|
|
197
975
|
|
|
198
976
|
Non-interactive host maps can share a nested or component-root container with static host siblings:
|
|
199
977
|
|
|
@@ -290,23 +1068,41 @@ Generated callback refs give directly patched host elements stable private targe
|
|
|
290
1068
|
React-owned component may therefore return `null`, a fragment, or multiple host nodes without
|
|
291
1069
|
shifting an unrelated compiler binding. These refs do not add attributes to SSR output.
|
|
292
1070
|
|
|
293
|
-
Conditional blocks have
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
1071
|
+
Conditional blocks have three safe ownership levels. One or more proven host-only direct branches
|
|
1072
|
+
may become compiler-owned ranges among static host siblings, including at the exact component root.
|
|
1073
|
+
A dedicated container with exactly one conditional child uses the smaller compiler-owned branch
|
|
1074
|
+
boundary. The more general React-owned conditional path accepts supported events, nested host
|
|
1075
|
+
conditionals, keyed lists, and component islands. A compiler-owned host branch may now recursively
|
|
1076
|
+
contain eligible host-only conditional ranges and non-interactive keyed ranges in nested host
|
|
1077
|
+
containers. Each nested range receives its own dependency entry and updates without rerunning or
|
|
1078
|
+
replacing the outer branch. When conditionals and keyed maps/`List` ranges are direct siblings in
|
|
1079
|
+
one safe host container, the compiler emits one ordered mixed-range controller. It mounts or removes
|
|
1080
|
+
conditional branches, applies independent LIS reconciliation to each keyed range, and preserves
|
|
1081
|
+
static siblings in place. Stateful leaf text, attributes, `className`, and individual inline style
|
|
1082
|
+
properties on those siblings are compiled into stable segment/sibling/path bindings. Their address
|
|
1083
|
+
does not shift when a nearby condition mounts or a keyed range inserts, removes, or reorders rows,
|
|
1084
|
+
so the same controller patches them from the current state snapshot without replacing the sibling
|
|
1085
|
+
or rerunning the owner. The same mixed layout may recurse inside safe branches and keyed rows.
|
|
1086
|
+
Keys on branches, custom components, fragments, refs, SVG, attribute spreads,
|
|
1087
|
+
`dangerouslySetInnerHTML`, branch events, interactive rows, and unsupported mixed children keep
|
|
1088
|
+
React ownership. An empty ternary branch may be `null` or `false`. The
|
|
1089
|
+
inactive branch is described at build time but is never pre-mounted or cached. If a logical `&&`
|
|
1090
|
+
evaluates to a number such as `0`,
|
|
1091
|
+
or adopted DOM no longer matches its descriptor, the affected container remounts through React so
|
|
1092
|
+
JavaScript, hydration, and React rendering semantics remain exact.
|
|
301
1093
|
|
|
302
1094
|
All component-level boundary types share one block graph and one ID sequence. A nested
|
|
303
1095
|
binding records its nearest conditional parent. If one state flush affects both an outer
|
|
304
1096
|
conditional and its descendants, the runtime refreshes the mounted outer boundary once and skips
|
|
305
|
-
the redundant descendant refreshes. React
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
1097
|
+
the redundant descendant refreshes. React-owned inner boundaries unmount normally. Compiler-owned
|
|
1098
|
+
host scopes explicitly remove every nested subscription before their outer DOM is removed, and a
|
|
1099
|
+
later mount reads the latest compiler-cell values. Surviving keyed rows move with the existing LIS
|
|
1100
|
+
algorithm. Duplicate runtime keys, invalid adoption, or an unsafe logical value transfer the outer
|
|
1101
|
+
container back to React; descendant dependencies remain subscribed so that fallback stays live.
|
|
1102
|
+
Host-only keyed rows have separate runtime instances per key. Row-local conditional and keyed-range
|
|
1103
|
+
IDs are scoped to that instance and paired with its stable key, so two rows can use the same
|
|
1104
|
+
build-time slot ID without sharing data or subscriptions. Unsupported row subtrees stay complete
|
|
1105
|
+
React-owned keyed boundaries.
|
|
310
1106
|
|
|
311
1107
|
Unsupported components fall back to React by default. Use `onUnsupported: "warn"` for diagnostics
|
|
312
1108
|
or `onUnsupported: "error"` while tightening an annotated migration.
|
|
@@ -323,7 +1119,29 @@ compiler: {
|
|
|
323
1119
|
|
|
324
1120
|
The default path is `.farm/react-compiler.json`. The report covers the production browser graph and
|
|
325
1121
|
contains project-relative module paths, compiled component names, fallback details, and fallback
|
|
326
|
-
reasons aggregated by count.
|
|
1122
|
+
reasons aggregated by count. Its summary and per-module `optimizations` also report
|
|
1123
|
+
`keyedIdentityTargets`, the number of scalar key-directed row bindings;
|
|
1124
|
+
`keyedMapLookupTargets`, the number of native-Map keyed lookup bindings;
|
|
1125
|
+
`keyedMembershipTargets`, the number of native-Set membership bindings;
|
|
1126
|
+
`keyedCollectionUpdateHints`, the number of compiler-proven native Set/Map mutation sites; and
|
|
1127
|
+
`keyedMapUpdateHints`, the number of compiler-proven keyed `map()` update calls; and
|
|
1128
|
+
`keyedArrayAppendHints`, the number of compiler-proven direct keyed-array append sites; and
|
|
1129
|
+
`keyedArrayFilterHints`, the number of compiler-proven keyed-array filter sites, including supported
|
|
1130
|
+
structural reorder pipelines; and
|
|
1131
|
+
`keyedArrayPrependHints`, the number of compiler-proven direct keyed-array prepend sites; and
|
|
1132
|
+
`keyedArrayPositionHints`, the number of compiler-proven native exact-position insertion, single or
|
|
1133
|
+
contiguous-range removal, single-row replacement, or exact-window replacement sites, including
|
|
1134
|
+
guarded compiler-safe runtime positions;
|
|
1135
|
+
and
|
|
1136
|
+
`keyedArrayReorderHints`, the number of compiler-proven native keyed-array reverse steps; and
|
|
1137
|
+
`keyedArraySortHints`, the number of compiler-proven native keyed-array sort steps; and
|
|
1138
|
+
`keyedArrayMappedRollingWindowChainHints`, the number of compiler-proven rolling steps retained
|
|
1139
|
+
across multi-window same-key map chains; and
|
|
1140
|
+
`keyedArrayRollingWindowHints`, the number of compiler-proven retained-tail plus incoming-suffix
|
|
1141
|
+
sites; and
|
|
1142
|
+
`keyedArraySliceHints`, the number of compiler-proven keyed-array slice sites with literal or guarded
|
|
1143
|
+
compiler-safe runtime bounds, including supported structural reorder pipelines. A custom
|
|
1144
|
+
project-relative `reportFile` also enables reporting.
|
|
327
1145
|
|
|
328
1146
|
The runtime test compares the same counter interaction on both paths: ordinary React performs a
|
|
329
1147
|
second component render and commit, while the compiled component remains at one render and one
|
|
@@ -331,11 +1149,15 @@ commit and updates its two bindings directly. This is a deterministic structural
|
|
|
331
1149
|
assertion; it is not presented as a cross-machine timing benchmark.
|
|
332
1150
|
|
|
333
1151
|
Application and prototype calls, dynamic style objects, handlers outside JSX events, nested,
|
|
334
|
-
computed, and rest props patterns, async handlers, unkeyed or index-keyed lists,
|
|
335
|
-
unsupported conditional roots, effects, and more advanced hook support intentionally stay
|
|
336
|
-
in this release. Compiler-owned keyed rows support either one dedicated host-only map/`List` or
|
|
337
|
-
one or more non-interactive ranges in a nested or component-root host container.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
1152
|
+
computed, and rest props patterns, async handlers, unkeyed or index-keyed lists, unsupported map
|
|
1153
|
+
chains, unsupported conditional roots, effects, and more advanced hook support intentionally stay
|
|
1154
|
+
on React in this release. Compiler-owned keyed rows support either one dedicated host-only map/`List` or
|
|
1155
|
+
one or more non-interactive ranges in a nested or component-root host container. A dedicated
|
|
1156
|
+
non-interactive row may also contain multiple recursive logical or ternary host branches beside
|
|
1157
|
+
stateful static host siblings, plus recursively nested non-interactive keyed ranges scoped by every
|
|
1158
|
+
stable parent key. Static sibling bindings support safe leaf text, attributes, `className`, and
|
|
1159
|
+
individual style properties; lifecycle-sensitive shapes continue to fall back. Inline synchronous
|
|
1160
|
+
row events continue to use the hybrid React-owned row path. Branch
|
|
1161
|
+
or nested-row events, interactive ranges beside siblings, non-inline or async row handlers,
|
|
1162
|
+
unsupported form shapes, custom components, fragments, refs, SVG, and duplicate runtime keys keep
|
|
1163
|
+
or switch to React ownership.
|