@kudzujs/core 0.4.10 → 0.4.12
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 +52 -29
- package/framework/README.md +1 -1
- package/framework/build.mjs +38 -3
- package/framework/core.d.ts +7 -0
- package/framework/core.mjs +29 -2
- package/framework/list-runtime.js +1 -4
- package/framework/native-runtime.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,6 +139,26 @@ return <>
|
|
|
139
139
|
|
|
140
140
|
Kudzu resolves `current` when the handler reads it, so removed conditional elements return `null` without a component runtime. Refs must initialize with `null`; callback refs, mutable value refs, and refs inside keyed lists are not supported.
|
|
141
141
|
|
|
142
|
+
## Context
|
|
143
|
+
|
|
144
|
+
Create a context to pass static or reactive values through component layers without prop drilling:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
const ThemeContext = createContext("light")
|
|
148
|
+
|
|
149
|
+
function Toolbar() {
|
|
150
|
+
const theme = useContext(ThemeContext)
|
|
151
|
+
return <button className={`theme-${theme}`}>{theme}</button>
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function App() {
|
|
155
|
+
const [theme] = useState("dark")
|
|
156
|
+
return <ThemeContext.Provider value={theme}><Toolbar /></ThemeContext.Provider>
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The default value applies outside a Provider, nested Providers override their parent, and native handlers read the latest direct state value. Static Provider values must be serializable. Reactive Provider values must be a direct `useState` value; setters and objects containing reactive values are not supported.
|
|
161
|
+
|
|
142
162
|
## Conditional DOM
|
|
143
163
|
|
|
144
164
|
Inline child `&&` and ternary expressions insert and remove bounded DOM ranges directly. A menu bar needs only state setters:
|
|
@@ -181,7 +201,7 @@ const [items, setItems] = useState([
|
|
|
181
201
|
{ id: 2, name: "Pine", done: true }
|
|
182
202
|
])
|
|
183
203
|
|
|
184
|
-
|
|
204
|
+
const rows = items.map(item =>
|
|
185
205
|
<li
|
|
186
206
|
key={item.id}
|
|
187
207
|
className={item.done ? "done" : "active"}
|
|
@@ -191,12 +211,14 @@ const [items, setItems] = useState([
|
|
|
191
211
|
{item.name.toUpperCase()}
|
|
192
212
|
<button onClick={() => setItems(items.filter(entry => entry.id !== item.id))}>Remove</button>
|
|
193
213
|
</li>
|
|
194
|
-
)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return <ul>{rows}</ul>
|
|
195
217
|
```
|
|
196
218
|
|
|
197
|
-
Kudzu emits initial items as static HTML, then adds, removes, updates, styles, and moves keyed elements directly. Existing keys move without remounting, preserving uncontrolled descendant state. Direct `item.<field>` reads use compact markers; derived item expressions compile to external ESM evaluators. Item-local handlers use direct DOM listeners and receive the latest JSON-safe item for their key, including after updates, additions, and reorders. The item remains stored once in shared list state; handler descriptors carry a placeholder that the list runtime fills when mounting or updating the keyed root.
|
|
219
|
+
Kudzu emits initial items as static HTML, then adds, removes, updates, styles, and moves keyed elements directly. The map may appear directly in JSX or in one top-level immutable `const` rendered once as a JSX child. Existing keys move without remounting, preserving uncontrolled descendant state. Direct `item.<field>` reads use compact markers; derived item expressions compile to external ESM evaluators. Item-local handlers use direct DOM listeners and receive the latest JSON-safe item for their key, including after updates, additions, and reorders. The item remains stored once in shared list state; handler descriptors carry a placeholder that the list runtime fills when mounting or updating the keyed root.
|
|
198
220
|
|
|
199
|
-
Each item must be an ordinary plain object with a unique string or finite-number key; nested data may contain only JSON-safe arrays, ordinary plain objects, and primitive values. Null-prototype objects are rejected to preserve JSON round-trip parity. The current syntax requires a
|
|
221
|
+
Each item must be an ordinary plain object with a unique string or finite-number key; nested data may contain only JSON-safe arrays, ordinary plain objects, and primitive values. Null-prototype objects are rejected to preserve JSON round-trip parity. The current syntax requires a local-state `.map`, one identifier callback parameter, one intrinsic JSX root, and `key={item.<field>}`. A list alias may only be rendered once and cannot be read by other JavaScript. Derived expressions must be pure and synchronous: item reads, literals, operators, templates, approved read-only string/array methods, deterministic `Math` methods, and `String`/`Number`/`Boolean` conversion are supported. Component state, locals, imported helpers, browser globals, Promise values, mutation, arbitrary calls, and prototype-sensitive properties are rejected. Nested conditions or lists, item spreads, component tags, refs, and `dangerouslySetInnerHTML` remain unsupported. Keyed rows must be placed inside an explicit `<tbody>`, `<thead>`, or `<tfoot>`.
|
|
200
222
|
|
|
201
223
|
## Normal JavaScript
|
|
202
224
|
|
|
@@ -259,6 +281,7 @@ Supported:
|
|
|
259
281
|
- Reactive standard, `aria-*`, and `data-*` attributes
|
|
260
282
|
- Reactive object `style` attributes
|
|
261
283
|
- Object DOM refs in native event handlers
|
|
284
|
+
- Default, nested, and reactive context providers
|
|
262
285
|
- Controlled `value` and `checked` form properties
|
|
263
286
|
- Conditional child `&&` and ternary DOM patches
|
|
264
287
|
- Top-level immutable JSX locals
|
|
@@ -266,7 +289,7 @@ Supported:
|
|
|
266
289
|
|
|
267
290
|
Not implemented yet:
|
|
268
291
|
|
|
269
|
-
- Block-scoped JSX locals and
|
|
292
|
+
- Block-scoped JSX locals and reusable keyed-list aliases
|
|
270
293
|
- Server actions and request-time SSR
|
|
271
294
|
- Imported client helpers and React package islands
|
|
272
295
|
- HMR and framework DevTools
|
|
@@ -281,13 +304,13 @@ Same counter with initial value `7` and increment/decrement buttons:
|
|
|
281
304
|
|
|
282
305
|
| Framework | Initial content | Initial JS gzip | Total output | Clean build |
|
|
283
306
|
|---|---:|---:|---:|---:|
|
|
284
|
-
| Kudzu | Yes | 393 B | 1.1 KB | **
|
|
285
|
-
| Astro | Yes | **158 B** | **365 B** |
|
|
286
|
-
| Svelte CSR | No | 10.5 KB | 26.9 KB |
|
|
287
|
-
| Qwik CSR | No | 20.6 KB | 57.8 KB |
|
|
288
|
-
| Vue CSR | No | 24.0 KB | 60.3 KB |
|
|
289
|
-
| React CSR | No | 59.2 KB | 189.0 KB |
|
|
290
|
-
| Next.js | Yes | 182.1 KB | 652.2 KB |
|
|
307
|
+
| Kudzu | Yes | 393 B | 1.1 KB | **409 ms** |
|
|
308
|
+
| Astro | Yes | **158 B** | **365 B** | 893 ms |
|
|
309
|
+
| Svelte CSR | No | 10.5 KB | 26.9 KB | 867 ms |
|
|
310
|
+
| Qwik CSR | No | 20.6 KB | 57.8 KB | 600 ms |
|
|
311
|
+
| Vue CSR | No | 24.0 KB | 60.3 KB | 785 ms |
|
|
312
|
+
| React CSR | No | 59.2 KB | 189.0 KB | 1032 ms |
|
|
313
|
+
| Next.js | Yes | 182.1 KB | 652.2 KB | 3082 ms |
|
|
291
314
|
|
|
292
315
|
Astro produces the smallest hand-authored counter. Kudzu's advantage in this fixture is React-shaped state code with a sub-1 KB runtime, not the smallest possible JavaScript.
|
|
293
316
|
|
|
@@ -297,29 +320,29 @@ Same content and CSS across every fixture:
|
|
|
297
320
|
|
|
298
321
|
| Framework | Initial content | Initial JS gzip | Total output | Clean build |
|
|
299
322
|
|---|---:|---:|---:|---:|
|
|
300
|
-
| Kudzu | Yes | **0 B** | 3.2 KB | **
|
|
301
|
-
| Astro | Yes | **0 B** | **3.0 KB** |
|
|
302
|
-
| Svelte CSR | No | 10.2 KB | 27.2 KB |
|
|
303
|
-
| Qwik CSR | No | 20.2 KB | 59.6 KB |
|
|
304
|
-
| Vue CSR | No | 24.2 KB | 62.3 KB |
|
|
305
|
-
| React CSR | No | 59.8 KB | 192.3 KB |
|
|
306
|
-
| Next.js | Yes | 182.6 KB | 663.6 KB |
|
|
323
|
+
| Kudzu | Yes | **0 B** | 3.2 KB | **422 ms** |
|
|
324
|
+
| Astro | Yes | **0 B** | **3.0 KB** | 1081 ms |
|
|
325
|
+
| Svelte CSR | No | 10.2 KB | 27.2 KB | 902 ms |
|
|
326
|
+
| Qwik CSR | No | 20.2 KB | 59.6 KB | 633 ms |
|
|
327
|
+
| Vue CSR | No | 24.2 KB | 62.3 KB | 810 ms |
|
|
328
|
+
| React CSR | No | 59.8 KB | 192.3 KB | 1110 ms |
|
|
329
|
+
| Next.js | Yes | 182.6 KB | 663.6 KB | 3126 ms |
|
|
307
330
|
|
|
308
331
|
### 1,000-item Keyed List
|
|
309
332
|
|
|
310
|
-
The list starts with 1,000 keyed items, then updates every label, reverses the order, removes odd IDs, and adds 500 items. Browser timings are medians from seven fresh headless Chrome runs.
|
|
333
|
+
The list starts with 1,000 keyed items, then updates every label, reverses the order, removes odd IDs, and adds 500 items. Browser timings are medians from seven fresh headless Chrome runs, measured when a DOM observer sees each expected result rather than at the next animation frame.
|
|
311
334
|
|
|
312
335
|
| Framework | Initial content | Initial JS gzip | Total output | Build | Update | Reverse | Remove | Add | Operations total |
|
|
313
336
|
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
314
|
-
| Astro | Yes | **324 B** | **43.6 KB** |
|
|
315
|
-
| Kudzu | Yes | 5.0 KB | 60.3 KB | **
|
|
316
|
-
|
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
|
|
|
320
|
-
|
|
|
321
|
-
|
|
322
|
-
Astro is the hand-authored native DOM baseline in the interactive fixtures. React, Vue, Svelte, and Qwik used client-rendered fixtures, while Kudzu and Astro emitted initial HTML; Qwik therefore did not exercise its SSR resumability advantage.
|
|
337
|
+
| Astro | Yes | **324 B** | **43.6 KB** | 826 ms | **4.1 ms** | **3.7 ms** | **1.4 ms** | **3.3 ms** | **12.5 ms** |
|
|
338
|
+
| Kudzu | Yes | 5.0 KB | 60.3 KB | **452 ms** | 7.1 ms | 7.5 ms | 1.9 ms | 7.0 ms | 23.5 ms |
|
|
339
|
+
| Next.js | Yes | 182.2 KB | 695.2 KB | 3002 ms | 7.5 ms | 12.3 ms | 4.1 ms | 7.8 ms | 31.7 ms |
|
|
340
|
+
| Vue CSR | No | 24.3 KB | 61.3 KB | 765 ms | 11.4 ms | 9.8 ms | 4.4 ms | 7.0 ms | 32.6 ms |
|
|
341
|
+
| React CSR | No | 59.3 KB | 189.4 KB | 1039 ms | 9.9 ms | 13.4 ms | 4.7 ms | 6.1 ms | 34.1 ms |
|
|
342
|
+
| Svelte CSR | No | 12.9 KB | 33.1 KB | 845 ms | 6.2 ms | 42.9 ms | 4.6 ms | 6.2 ms | 59.9 ms |
|
|
343
|
+
| Qwik CSR | No | 22.2 KB | 64.1 KB | 630 ms | 10.7 ms | 27.5 ms | 39.2 ms | 22.2 ms | 99.6 ms |
|
|
344
|
+
|
|
345
|
+
Astro is the hand-authored native DOM baseline in the interactive fixtures. React, Vue, Svelte, and Qwik used client-rendered fixtures, while Kudzu and Astro emitted initial HTML; Qwik therefore did not exercise its SSR resumability advantage. Kudzu's keyed-list operations total 23.5 ms, 11.0 ms behind the hand-authored Astro baseline and 10.6 ms ahead of React across all four operations.
|
|
323
346
|
|
|
324
347
|
Benchmark snapshot collected on July 22, 2026 with Node 24.14.0 on an Intel i5-9500. These results compare the selected one-page fixtures, not ecosystem maturity, browser interaction speed beyond the listed operations, or each framework's full rendering options. Build times vary with machine load and filesystem cache.
|
|
325
348
|
|
package/framework/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Framework Internals
|
|
2
2
|
|
|
3
3
|
- `build.mjs`: TSX compilation, file routes, behavior extraction, static HTML output, and the development server.
|
|
4
|
-
- `core.mjs`: server-side JSX rendering, state slots, behavior metadata, and serializable capture validation.
|
|
4
|
+
- `core.mjs`: server-side JSX rendering, state slots, context providers, behavior metadata, and serializable capture validation.
|
|
5
5
|
- `jsx-runtime.mjs`: automatic JSX runtime used by TypeScript.
|
|
6
6
|
- `runtime.js`: command-only runtime for direct state-to-text patches.
|
|
7
7
|
- `shared-runtime.js`: command runtime with capability commit and DOM lifecycle hooks, emitted only when needed.
|
package/framework/build.mjs
CHANGED
|
@@ -324,6 +324,8 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
324
324
|
const functions = new Map()
|
|
325
325
|
const jsxLocalDeclarations = new Map()
|
|
326
326
|
const jsxLocalsByFunction = new Map()
|
|
327
|
+
const listLocalDeclarations = new WeakSet()
|
|
328
|
+
const listLocalUses = new WeakMap()
|
|
327
329
|
const listValues = new WeakMap()
|
|
328
330
|
const listEventItems = new WeakMap()
|
|
329
331
|
let usesBehavior = false
|
|
@@ -351,7 +353,7 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
351
353
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && isTopLevelConst(node)) {
|
|
352
354
|
const owner = nearestFunction(node)
|
|
353
355
|
const declarations = jsxLocalDeclarations.get(owner) ?? new Map()
|
|
354
|
-
declarations.set(node.name.text, node.initializer)
|
|
356
|
+
declarations.set(node.name.text, { node, initializer: node.initializer })
|
|
355
357
|
jsxLocalDeclarations.set(owner, declarations)
|
|
356
358
|
}
|
|
357
359
|
ts.forEachChild(node, collect)
|
|
@@ -362,7 +364,7 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
362
364
|
let changed = true
|
|
363
365
|
while (changed) {
|
|
364
366
|
changed = false
|
|
365
|
-
for (const [name, initializer] of declarations) {
|
|
367
|
+
for (const [name, { initializer }] of declarations) {
|
|
366
368
|
if (!names.has(name) && isJsxLocalValue(initializer, names)) {
|
|
367
369
|
names.add(name)
|
|
368
370
|
changed = true
|
|
@@ -371,6 +373,25 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
371
373
|
}
|
|
372
374
|
jsxLocalsByFunction.set(owner, names)
|
|
373
375
|
}
|
|
376
|
+
for (const [owner, declarations] of jsxLocalDeclarations) {
|
|
377
|
+
const setters = settersByFunction.get(owner) ?? new Map()
|
|
378
|
+
for (const [name, declaration] of declarations) {
|
|
379
|
+
const parts = keyedListParts(declaration.initializer, setters)
|
|
380
|
+
if (!parts) continue
|
|
381
|
+
const uses = []
|
|
382
|
+
const collectUses = node => {
|
|
383
|
+
if (ts.isJsxExpression(node) && node.initializer === undefined && ts.isIdentifier(node.expression) && node.expression.text === name && nearestFunction(node) === owner) uses.push(node)
|
|
384
|
+
ts.forEachChild(node, collectUses)
|
|
385
|
+
}
|
|
386
|
+
collectUses(owner.body)
|
|
387
|
+
const references = identifierReferenceCount(owner.body, name)
|
|
388
|
+
const position = sourceFile.getLineAndCharacterOfPosition(declaration.node.getStart(sourceFile))
|
|
389
|
+
if (uses.length > 1) throw new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} Keyed list local "${name}" must be rendered exactly once`)
|
|
390
|
+
if (references !== uses.length) throw new Error(`${sourceFile.fileName}:${position.line + 1}:${position.character + 1} Keyed list local "${name}" may only be used as a JSX child`)
|
|
391
|
+
listLocalDeclarations.add(declaration.node)
|
|
392
|
+
if (uses.length) listLocalUses.set(uses[0], parts)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
374
395
|
|
|
375
396
|
const visitor = node => {
|
|
376
397
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text.startsWith(".")) {
|
|
@@ -391,6 +412,10 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
391
412
|
return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, initializer)
|
|
392
413
|
}
|
|
393
414
|
|
|
415
|
+
if (ts.isVariableDeclaration(node) && listLocalDeclarations.has(node)) {
|
|
416
|
+
return factory.updateVariableDeclaration(node, node.name, node.exclamationToken, node.type, factory.createIdentifier("undefined"))
|
|
417
|
+
}
|
|
418
|
+
|
|
394
419
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && jsxLocalsByFunction.get(nearestFunction(node))?.has(node.name.text) && referencesIdentifier(nearestFunction(node).body, node.name.text)) {
|
|
395
420
|
const parts = conditionalParts(node.initializer)
|
|
396
421
|
if (parts) {
|
|
@@ -415,7 +440,7 @@ function createKudzuTransformer(nativeHandlers, reactiveBindings, listExpression
|
|
|
415
440
|
}
|
|
416
441
|
|
|
417
442
|
if (ts.isJsxExpression(node) && node.initializer === undefined && node.expression && (ts.isJsxElement(node.parent) || ts.isJsxFragment(node.parent))) {
|
|
418
|
-
const listParts = keyedListParts(node.expression, settersForNode(node, settersByFunction))
|
|
443
|
+
const listParts = listLocalUses.get(node) ?? keyedListParts(node.expression, settersForNode(node, settersByFunction))
|
|
419
444
|
if (listParts) {
|
|
420
445
|
if (keyedListParentTag(node) === "table") throw new Error("Keyed table rows must be wrapped in <tbody>, <thead>, or <tfoot>")
|
|
421
446
|
validateKeyedList(listParts, sourceFile, settersForNode(node, settersByFunction), listValues, listEventItems)
|
|
@@ -651,6 +676,16 @@ function referencesIdentifier(root, name) {
|
|
|
651
676
|
return found
|
|
652
677
|
}
|
|
653
678
|
|
|
679
|
+
function identifierReferenceCount(root, name) {
|
|
680
|
+
let count = 0
|
|
681
|
+
const visit = node => {
|
|
682
|
+
if (ts.isIdentifier(node) && node.text === name && isReferenceIdentifier(node)) count++
|
|
683
|
+
ts.forEachChild(node, visit)
|
|
684
|
+
}
|
|
685
|
+
visit(root)
|
|
686
|
+
return count
|
|
687
|
+
}
|
|
688
|
+
|
|
654
689
|
function unwrapExpression(node) {
|
|
655
690
|
return ts.isParenthesizedExpression(node) ? unwrapExpression(node.expression) : node
|
|
656
691
|
}
|
package/framework/core.d.ts
CHANGED
|
@@ -8,6 +8,13 @@ export interface RefObject<T> {
|
|
|
8
8
|
|
|
9
9
|
export function useRef<T>(initialValue: null): RefObject<T>
|
|
10
10
|
|
|
11
|
+
export interface Context<T> {
|
|
12
|
+
Provider: (props: { value: T; children?: unknown }) => unknown
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createContext<T>(defaultValue: T): Context<T>
|
|
16
|
+
export function useContext<T>(context: Context<T>): T
|
|
17
|
+
|
|
11
18
|
export function behavior(commands: Array<["add" | "set" | "log", unknown, unknown]>): unknown
|
|
12
19
|
export function nativeBehavior(module: string, handler: string, states: Array<[string, unknown]>, scope: Array<[string, unknown]>): unknown
|
|
13
20
|
export function binding(value: unknown, module: string, handler: string, states: Array<[string, unknown]>, scope: Array<[string, unknown]>): unknown
|
package/framework/core.mjs
CHANGED
|
@@ -10,6 +10,8 @@ const listFieldMarker = Symbol("kudzu.listField")
|
|
|
10
10
|
const listExpressionMarker = Symbol("kudzu.listExpression")
|
|
11
11
|
const listItemMarker = Symbol("kudzu.listItem")
|
|
12
12
|
const refMarker = Symbol("kudzu.ref")
|
|
13
|
+
const contextMarker = Symbol("kudzu.context")
|
|
14
|
+
const contextProviderMarker = Symbol("kudzu.contextProvider")
|
|
13
15
|
const noSelectValue = Symbol("kudzu.no-select-value")
|
|
14
16
|
|
|
15
17
|
let renderContext
|
|
@@ -44,6 +46,23 @@ export function useRef(initialValue) {
|
|
|
44
46
|
return { [refMarker]: true, id: `r${renderContext.nextRef++}`, current: null }
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
export function createContext(defaultValue) {
|
|
50
|
+
const context = { [contextMarker]: true, defaultValue }
|
|
51
|
+
context.Provider = function Provider({ value, children }) {
|
|
52
|
+
return { [contextProviderMarker]: true, context, value, children }
|
|
53
|
+
}
|
|
54
|
+
return context
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function useContext(context) {
|
|
58
|
+
if (!renderContext) throw new Error("useContext() can only run while rendering a Kudzu component")
|
|
59
|
+
if (!context?.[contextMarker]) throw new Error("useContext() requires a Kudzu context")
|
|
60
|
+
for (let index = renderContext.contexts.length - 1; index >= 0; index--) {
|
|
61
|
+
if (renderContext.contexts[index][0] === context) return renderContext.contexts[index][1]
|
|
62
|
+
}
|
|
63
|
+
return context.defaultValue
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
export function behavior(commands) {
|
|
48
67
|
return {
|
|
49
68
|
[behaviorMarker]: true,
|
|
@@ -63,7 +82,7 @@ export function nativeBehavior(module, handler, states, scope) {
|
|
|
63
82
|
if (!signal?.[signalMarker]) throw new Error("A native behavior must target framework state")
|
|
64
83
|
return [name, signal.id]
|
|
65
84
|
})),
|
|
66
|
-
scope: Object.fromEntries(scope.map(([name, value]) => [name, serializeCapture(name, value, new Set())]))
|
|
85
|
+
scope: Object.fromEntries(scope.map(([name, value]) => [name, value?.[signalMarker] ? { type: "state", id: value.id } : serializeCapture(name, value, new Set())]))
|
|
67
86
|
}
|
|
68
87
|
}
|
|
69
88
|
|
|
@@ -197,7 +216,7 @@ function serializeCapture(name, value, seen) {
|
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
export async function renderPage(component, metadata = {}) {
|
|
200
|
-
renderContext = { nextState: 0, nextRef: 0, nextCondition: 0, nextList: 0, conditionDepth: 0, listDepth: 0, listRoot: undefined, listTemplate: false, listFields: undefined, states: {}, textStates: new Set(), conditionStates: new Set(), events: [], bindings: [], conditions: [], lists: [], hasBehaviors: false, hasNativeBehaviors: false, hasBindings: false, hasLists: false, hasListStyles: false }
|
|
219
|
+
renderContext = { nextState: 0, nextRef: 0, nextCondition: 0, nextList: 0, conditionDepth: 0, listDepth: 0, listRoot: undefined, listTemplate: false, listFields: undefined, contexts: [], states: {}, textStates: new Set(), conditionStates: new Set(), events: [], bindings: [], conditions: [], lists: [], hasBehaviors: false, hasNativeBehaviors: false, hasBindings: false, hasLists: false, hasListStyles: false }
|
|
201
220
|
|
|
202
221
|
try {
|
|
203
222
|
const body = await renderNode({ type: component, props: {} })
|
|
@@ -299,6 +318,14 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
299
318
|
return escapeHtml(node)
|
|
300
319
|
}
|
|
301
320
|
if (node instanceof Promise) return renderNode(await node, namespace, selectValue)
|
|
321
|
+
if (node?.[contextProviderMarker]) {
|
|
322
|
+
renderContext.contexts.push([node.context, node.value])
|
|
323
|
+
try {
|
|
324
|
+
return await renderNode(node.children, namespace, selectValue)
|
|
325
|
+
} finally {
|
|
326
|
+
renderContext.contexts.pop()
|
|
327
|
+
}
|
|
328
|
+
}
|
|
302
329
|
if (node?.[conditionalMarker]) {
|
|
303
330
|
const descriptor = bindingDescriptor(node)
|
|
304
331
|
const stateIds = reactiveStateIds(descriptor)
|
|
@@ -105,15 +105,12 @@ function updateList(list) {
|
|
|
105
105
|
next.push([token, node])
|
|
106
106
|
values.set(token, value)
|
|
107
107
|
}
|
|
108
|
-
const removals = parent.ownerDocument.createDocumentFragment()
|
|
109
108
|
for (const [token, node] of list.roots) {
|
|
110
109
|
if (keys.has(token)) continue
|
|
111
110
|
if (list.descriptor.mount) {
|
|
112
111
|
unmountDom(node)
|
|
113
112
|
node.remove()
|
|
114
|
-
} else
|
|
115
|
-
removals.append(node)
|
|
116
|
-
}
|
|
113
|
+
} else node.remove()
|
|
117
114
|
}
|
|
118
115
|
if (added) {
|
|
119
116
|
if (list.descriptor.mount) mountDom(additions)
|
|
@@ -20,7 +20,7 @@ export function createNativeContext(state, stateIds, commit, serializedScope = {
|
|
|
20
20
|
return state.get(stateIds[name])
|
|
21
21
|
},
|
|
22
22
|
scope(name) {
|
|
23
|
-
return scope[name]
|
|
23
|
+
return serializedScope[name]?.type === "state" ? state.get(serializedScope[name].id) : scope[name]
|
|
24
24
|
},
|
|
25
25
|
set(name, value) {
|
|
26
26
|
const id = stateIds[name]
|