@kudzujs/core 0.6.22 → 0.6.23
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 +3 -3
- package/framework/build.mjs +45 -7
- package/framework/core.mjs +6 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -401,13 +401,13 @@ One nested keyed map may read a direct array property of its parent item. This s
|
|
|
401
401
|
```tsx
|
|
402
402
|
{categories.map(category => <section key={category.id}>
|
|
403
403
|
<h2>{category.title}</h2>
|
|
404
|
-
<ul>{category.items.map(item => <
|
|
404
|
+
<ul>{category.items.map(item => <ItemCard key={item.id} item={item} />)}</ul>
|
|
405
405
|
</section>)}
|
|
406
406
|
```
|
|
407
407
|
|
|
408
|
-
The nested collection must be `parent.<field
|
|
408
|
+
The nested collection must be `parent.<field>`. Its child row may be intrinsic or a same-file or relative-imported component that specializes to one intrinsic root. Child handlers receive the latest child item, and one level of child-local `&&` or ternary JSX conditions patches bounded DOM without remounting the child row. A second child list, third nesting level, computed collection, parent-item capture from the child row, conditions inside child conditions, child effects, child component tags below the specialized row root, and child row-local state remain unsupported.
|
|
409
409
|
|
|
410
|
-
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 or top-level local or relative-imported row component, and `key={item.<field>}`. State-backed list wrappers use one destructured props parameter, an intrinsic return root, no effects, and a direct local-state prop. Same-file wrappers must be unexported and state-backed at every call; relative default, named/aliased, and direct named re-export wrappers are specialized per qualifying call. Row components accept destructured projected props, top-level single-`const` calculations and inline effects before one intrinsic return. Effect dependencies inside a row may be empty, direct primitive Kudzu state identifiers, or direct `item.<field>` properties whose selected values remain JSON-safe primitives. Whole-item, computed, nested, derived, `__proto__`, `prototype`, and `constructor` dependencies are rejected. 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, imported helpers used inside calculations, browser globals, Promise values, mutation, arbitrary calls, and prototype-sensitive properties are rejected. Package, namespace, and star-export list wrappers, package or namespace row imports, same-file exported rows, reusable aliases, prop spreads/defaults/rest, children, nested item conditions
|
|
410
|
+
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 or top-level local or relative-imported row component, and `key={item.<field>}`. State-backed list wrappers use one destructured props parameter, an intrinsic return root, no effects, and a direct local-state prop. Same-file wrappers must be unexported and state-backed at every call; relative default, named/aliased, and direct named re-export wrappers are specialized per qualifying call. Row components accept destructured projected props, top-level single-`const` calculations and inline effects before one intrinsic return. Effect dependencies inside a direct outer row may be empty, direct primitive Kudzu state identifiers, or direct `item.<field>` properties whose selected values remain JSON-safe primitives. Whole-item, computed, nested, derived, `__proto__`, `prototype`, and `constructor` dependencies are rejected. 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, imported helpers used inside calculations, browser globals, Promise values, mutation, arbitrary calls, and prototype-sensitive properties are rejected. Package, namespace, and star-export list wrappers, package or namespace row imports, same-file exported rows, reusable aliases, prop spreads/defaults/rest, children, conditions nested inside item conditions, component tags below a specialized row root, refs, and `dangerouslySetInnerHTML` remain unsupported. Keyed rows must be placed inside an explicit `<tbody>`, `<thead>`, or `<tfoot>`.
|
|
411
411
|
|
|
412
412
|
The focused wrapper fixture emits 1,393 B raw / 500 B gzip HTML and 10,719 B raw / 4,665 B gzip JavaScript across its route capabilities. After one warm-up, seven clean builds measured 314.1, 325.3, 322.3, 327.2, 336.1, 322.4, and 315.0 ms, with a 322.4 ms median.
|
|
413
413
|
|
package/framework/build.mjs
CHANGED
|
@@ -1974,10 +1974,25 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
1974
1974
|
ts.forEachChild(node, rejectUnsupportedRenderControl)
|
|
1975
1975
|
}
|
|
1976
1976
|
rejectUnsupportedRenderControl(sourceFile)
|
|
1977
|
-
const
|
|
1977
|
+
const nestedComponentCalls = new Set()
|
|
1978
|
+
for (const { parts } of rawRenderedLists) {
|
|
1979
|
+
const collectNestedComponents = node => {
|
|
1980
|
+
if (ts.isJsxExpression(node) && node.expression) {
|
|
1981
|
+
const nested = nestedKeyedListParts(node.expression, parts.item)
|
|
1982
|
+
if (nested) {
|
|
1983
|
+
const tag = jsxTagName(nested.root)
|
|
1984
|
+
if (tag && ts.isIdentifier(tag) && tag.text[0] === tag.text[0].toUpperCase()) nestedComponentCalls.add(nested.root)
|
|
1985
|
+
return
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
ts.forEachChild(node, collectNestedComponents)
|
|
1989
|
+
}
|
|
1990
|
+
collectNestedComponents(parts.root)
|
|
1991
|
+
}
|
|
1992
|
+
const listComponentNames = new Set([...rawRenderedLists.flatMap(({ parts }) => {
|
|
1978
1993
|
const tag = jsxTagName(parts.root)
|
|
1979
1994
|
return tag && ts.isIdentifier(tag) && tag.text[0] === tag.text[0].toUpperCase() ? [tag.text] : []
|
|
1980
|
-
}))
|
|
1995
|
+
}), ...[...nestedComponentCalls].map(call => jsxTagName(call).text)])
|
|
1981
1996
|
const keyedComponentCalls = new Set(rawRenderedLists.map(({ parts }) => parts.root))
|
|
1982
1997
|
for (const call of reducerRowStateCalls) if (!keyedComponentCalls.has(call)) fail(call, "Reducer-dispatch component useState() is only supported in a direct keyed row")
|
|
1983
1998
|
for (const name of listComponentNames) {
|
|
@@ -2001,6 +2016,10 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2001
2016
|
? componentSpecializations.get(call)
|
|
2002
2017
|
: specializeComponentCall(call, component.function, sourceFile, factory, context, fail)
|
|
2003
2018
|
if (specialization.effects.length && !keyedComponentCalls.has(call)) fail(call, "Effectful keyed row components may only be used directly as keyed map rows")
|
|
2019
|
+
if (!local) {
|
|
2020
|
+
specialization.root = mergeSpecializedImports(specialization.root, component.function.getSourceFile(), call)
|
|
2021
|
+
synthesizeTree(specialization.root)
|
|
2022
|
+
}
|
|
2004
2023
|
componentSpecializations.set(call, specialization)
|
|
2005
2024
|
}
|
|
2006
2025
|
if (local) specializedDeclarations.add(component.declaration)
|
|
@@ -2038,7 +2057,7 @@ function createKudzuTransformer(nativeHandlers, effectHandlers, reactiveBindings
|
|
|
2038
2057
|
calculation.parent = callback
|
|
2039
2058
|
validateListExpression(calculation, parts.item, originalParts.root, fail)
|
|
2040
2059
|
}
|
|
2041
|
-
validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions, settersForNode(originalParts.root, settersByFunction), specialization?.rowState, nestedLists)
|
|
2060
|
+
validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions, settersForNode(originalParts.root, settersByFunction), specialization?.rowState, nestedLists, componentSpecializations, factory)
|
|
2042
2061
|
if (specialization?.effects.length) {
|
|
2043
2062
|
usesListEffects = true
|
|
2044
2063
|
const statements = specialization.effects.map(entry => {
|
|
@@ -2520,7 +2539,7 @@ function insideJsxEventHandler(node, root) {
|
|
|
2520
2539
|
return false
|
|
2521
2540
|
}
|
|
2522
2541
|
|
|
2523
|
-
function validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions, setters, rowState, nestedLists) {
|
|
2542
|
+
function validateKeyedList(parts, sourceFile, listValues, listEventItems, listConditions, setters, rowState, nestedLists, componentSpecializations, factory) {
|
|
2524
2543
|
const fail = (node, message) => {
|
|
2525
2544
|
throw sourceNodeError(node, sourceFile, message)
|
|
2526
2545
|
}
|
|
@@ -2550,14 +2569,33 @@ function validateKeyedList(parts, sourceFile, listValues, listEventItems, listCo
|
|
|
2550
2569
|
if (nestedList) fail(expression, "Keyed list rows support one nested keyed list")
|
|
2551
2570
|
if (referenceIdentifiers(nested.callback, item).length) fail(nested.root, "Nested keyed list rows cannot capture the parent item")
|
|
2552
2571
|
nestedList = nested
|
|
2553
|
-
const
|
|
2572
|
+
const specialization = componentSpecializations.get(nested.root)
|
|
2573
|
+
const root = specialization?.root ?? nested.root
|
|
2574
|
+
const callback = root === nested.root ? nested.callback : factory.updateArrowFunction(
|
|
2575
|
+
nested.callback,
|
|
2576
|
+
nested.callback.modifiers,
|
|
2577
|
+
nested.callback.typeParameters,
|
|
2578
|
+
nested.callback.parameters,
|
|
2579
|
+
nested.callback.type,
|
|
2580
|
+
nested.callback.equalsGreaterThanToken,
|
|
2581
|
+
root
|
|
2582
|
+
)
|
|
2583
|
+
if (callback !== nested.callback) {
|
|
2584
|
+
ts.setParentRecursive(callback, false)
|
|
2585
|
+
callback.parent = nested.callback.parent
|
|
2586
|
+
}
|
|
2587
|
+
const nestedParts = { ...nested, root, callback, state: parts.state, nested: true }
|
|
2588
|
+
for (const calculation of specialization?.calculations ?? []) {
|
|
2589
|
+
ts.setParentRecursive(calculation, false)
|
|
2590
|
+
calculation.parent = callback
|
|
2591
|
+
validateListExpression(calculation, nested.item, nested.root, fail)
|
|
2592
|
+
}
|
|
2554
2593
|
nestedLists.set(expression, nestedParts)
|
|
2555
|
-
validateKeyedList(nestedParts, sourceFile, listValues, listEventItems, listConditions, setters, undefined, nestedLists)
|
|
2594
|
+
validateKeyedList(nestedParts, sourceFile, listValues, listEventItems, listConditions, setters, undefined, nestedLists, componentSpecializations, factory)
|
|
2556
2595
|
return
|
|
2557
2596
|
}
|
|
2558
2597
|
const condition = conditionalParts(expression)
|
|
2559
2598
|
if (condition && containsJsx(expression)) {
|
|
2560
|
-
if (parts.nested) fail(node, "Nested keyed list item conditions are not supported")
|
|
2561
2599
|
if (conditionDepth) fail(node, "Nested item conditions are not supported in keyed lists")
|
|
2562
2600
|
if (rowState && referencedStateNames(condition.condition, setters).has(rowState.state)) {
|
|
2563
2601
|
conditionDepth++
|
package/framework/core.mjs
CHANGED
|
@@ -568,6 +568,8 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
568
568
|
}
|
|
569
569
|
if (node?.[listConditionalMarker]) {
|
|
570
570
|
const descriptor = { kind: node.kind, module: node.module, handler: node.handler }
|
|
571
|
+
const owner = renderContext.listRoot ?? renderContext.listRowRoot
|
|
572
|
+
if (owner) owner.conditions = true
|
|
571
573
|
const previousBranch = renderContext.listConditionalBranch
|
|
572
574
|
renderContext.listConditionalBranch = true
|
|
573
575
|
let truthy
|
|
@@ -765,13 +767,14 @@ async function renderList(node, namespace, selectValue) {
|
|
|
765
767
|
renderContext.listRowConditions = []
|
|
766
768
|
renderContext.listRowLists = []
|
|
767
769
|
renderContext.listFields = new Set([node.keyField])
|
|
768
|
-
|
|
769
|
-
renderContext.
|
|
770
|
+
const templateRoot = { id, state: node.items.id, descriptor, template: true, effects: [], item: {}, rowIndexes: { s: 0, c: 0, l: 0 } }
|
|
771
|
+
renderContext.listRoot = templateRoot
|
|
772
|
+
renderContext.listRowRoot = templateRoot
|
|
770
773
|
const template = await renderNode(node.render({}), namespace, selectValue)
|
|
771
774
|
if (template.includes("data-k-native-") || template.includes("data-k-effects=") || template.includes("data-k-list=")) descriptor.mount = true
|
|
772
775
|
if (template.includes("data-k-list=")) descriptor.nested = true
|
|
773
776
|
if (template.includes("data-k-effects=")) descriptor.effects = true
|
|
774
|
-
if (
|
|
777
|
+
if (templateRoot.conditions) descriptor.conditions = true
|
|
775
778
|
if (template.includes("data-k-list-text-end")) descriptor.textRanges = true
|
|
776
779
|
if (template.includes("data-k-list-attrs")) descriptor.attributes = true
|
|
777
780
|
if (template.includes("data-k-list-events")) descriptor.events = true
|