@kudzujs/core 0.6.26 → 0.6.27
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 +5 -5
- package/framework/core.mjs +10 -3
- package/framework/list-runtime.js +21 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -426,12 +426,12 @@ The nested collection must be `parent.<field>`. Its child row may be intrinsic o
|
|
|
426
426
|
|
|
427
427
|
Initial child rows remain complete HTML. Kudzu stores one child row prototype, including inert condition branches and descriptors, and reuses it across parent rows.
|
|
428
428
|
|
|
429
|
-
- HTML: 676,086 B before sharing,
|
|
430
|
-
- Total deploy output: 693,806 B before sharing,
|
|
431
|
-
- Compressed-file sum:
|
|
432
|
-
- Initial JavaScript: 6.
|
|
429
|
+
- HTML: 676,086 B before sharing, 339,586 B after sharing.
|
|
430
|
+
- Total deploy output: 693,806 B before sharing, 359,035 B after sharing.
|
|
431
|
+
- Compressed-file sum: 24,067 B. Sharing text and attribute patch metadata saves 29,576 B raw over `v0.6.26` while adding 147 B gzip because the removed metadata compressed well.
|
|
432
|
+
- Initial JavaScript: 6.9 KB gzip. Routes without nested lists compile out prototype and marker lookup.
|
|
433
433
|
|
|
434
|
-
In the matched 100-parent/1,000-child fixture, Kudzu measured 1.
|
|
434
|
+
In the matched 100-parent/1,000-child fixture, Kudzu measured 1.4/0.5/5.4/0.7 ms for child update and condition change, child reverse, parent reverse, and parent removal. Hand-written Astro/native measured 0.5/0.3/3.6/0.2 ms, Svelte 2.2/0.9/5.4/0.9 ms, Vue 4.0/2.1/4.6/1.8 ms, and React 8.5/3.9/6.8/3.8 ms. Kudzu and Astro emit initial rows while the CSR targets do not, so artifact sizes are not architecture-equivalent.
|
|
435
435
|
|
|
436
436
|
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>`.
|
|
437
437
|
|
package/framework/core.mjs
CHANGED
|
@@ -546,7 +546,9 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
546
546
|
if (node?.[listMarker]) return renderList(node, namespace, selectValue)
|
|
547
547
|
if (node?.[listFieldMarker]) {
|
|
548
548
|
if (renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) renderContext.listFields?.add(node.field)
|
|
549
|
-
const marker = renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch
|
|
549
|
+
const marker = renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch
|
|
550
|
+
? sharedInitialListMarker() ? " data-k-list-text" : ` data-k-list-text="${escapeAttribute(node.field)}"`
|
|
551
|
+
: ""
|
|
550
552
|
return `<template${marker}></template>${escapeHtml(node.value ?? "")}<template data-k-list-text-end></template>`
|
|
551
553
|
}
|
|
552
554
|
if (node?.[listExpressionMarker]) {
|
|
@@ -727,12 +729,12 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
727
729
|
}
|
|
728
730
|
|
|
729
731
|
if (attributeBindings.length) attributes += ` data-k-bind-attrs='${escapeJsonAttribute(attributeBindings)}'`
|
|
730
|
-
if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listAttributes.length) attributes += ` data-k-list-attrs='${escapeJsonAttribute(listAttributes)}'`
|
|
732
|
+
if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listAttributes.length) attributes += sharedInitialListMarker() ? " data-k-list-attrs" : ` data-k-list-attrs='${escapeJsonAttribute(listAttributes)}'`
|
|
731
733
|
if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listExpressionAttributes.length) attributes += ` data-k-list-expression-attrs='${escapeJsonAttribute(listExpressionAttributes)}'`
|
|
732
734
|
if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && listEvents.length) attributes += ` data-k-list-events='${escapeJsonAttribute(listEvents)}'`
|
|
733
735
|
if ((renderContext.listTemplate || renderContext.listInitialMarkers || renderContext.listConditionalBranch) && directListText) {
|
|
734
736
|
renderContext.listFields?.add(directListText.field)
|
|
735
|
-
attributes += ` data-k-list-text="${escapeAttribute(directListText.field)}"`
|
|
737
|
+
attributes += sharedInitialListMarker() ? " data-k-list-text" : ` data-k-list-text="${escapeAttribute(directListText.field)}"`
|
|
736
738
|
}
|
|
737
739
|
|
|
738
740
|
if (tag === "option" && selectValue !== noSelectValue && String(optionValue(props)) === (selectValue == null ? "" : String(selectValue))) attributes += " selected"
|
|
@@ -746,6 +748,11 @@ async function renderNode(node, namespace, selectValue = noSelectValue) {
|
|
|
746
748
|
return `<${tag}${attributes}>${children}</${tag}>`
|
|
747
749
|
}
|
|
748
750
|
|
|
751
|
+
function sharedInitialListMarker() {
|
|
752
|
+
const owner = renderContext.listRoot ?? renderContext.listRowRoot
|
|
753
|
+
return renderContext.listInitialMarkers && !renderContext.listTemplate && !renderContext.listConditionalBranch && owner?.descriptor.ownerField
|
|
754
|
+
}
|
|
755
|
+
|
|
749
756
|
async function renderList(node, namespace, selectValue) {
|
|
750
757
|
if (namespace) throw new Error(`Reactive keyed lists are not supported inside ${namespace}`)
|
|
751
758
|
const ownerRoot = node.ownerField ? renderContext.listRoot ?? renderContext.listRowRoot : undefined
|
|
@@ -12,6 +12,7 @@ const conditionOwners = __KUDZU_LIST_CONDITIONS__ ? new WeakMap() : undefined
|
|
|
12
12
|
const conditionTemplates = __KUDZU_LIST_CONDITIONS__ ? new WeakMap() : undefined
|
|
13
13
|
const itemPartsSelector = `[data-k-list-text]${__KUDZU_LIST_ATTRIBUTES__ ? ",[data-k-list-attrs]" : ""}${__KUDZU_LIST_EVENTS__ ? ",[data-k-list-events]" : ""}${__KUDZU_LIST_EXPRESSIONS__ ? ",[data-k-list-expression]" : ""}${__KUDZU_LIST_EXPRESSION_ATTRIBUTES__ ? ",[data-k-list-expression-attrs]" : ""}${__KUDZU_LIST_CONDITIONS__ ? ",[data-k-list-condition]" : ""}${__KUDZU_LIST_EFFECTS__ ? ",[data-k-effects]" : ""}`
|
|
14
14
|
const childPrototypes = __KUDZU_NESTED_LISTS__ ? new WeakMap() : undefined
|
|
15
|
+
const itemPartPlans = __KUDZU_NESTED_LISTS__ ? new WeakMap() : undefined
|
|
15
16
|
|
|
16
17
|
function commitLists(id) {
|
|
17
18
|
const lists = listTargets.get(id)
|
|
@@ -44,6 +45,7 @@ function mountLists(root) {
|
|
|
44
45
|
const parts = listItemPartPlan(templateRoot, descriptor.nested)
|
|
45
46
|
for (const root of roots) {
|
|
46
47
|
if (__KUDZU_LIST_CONDITIONS__ && descriptor.conditions) {
|
|
48
|
+
if (__KUDZU_NESTED_LISTS__ && descriptor.ownerField) itemPartPlans.set(root, parts)
|
|
47
49
|
const initialParts = listItemParts(root, descriptor.nested)
|
|
48
50
|
mapListConditionTemplates(parts.conditions, initialParts.conditions)
|
|
49
51
|
} else mapListItemParts(parts, root, descriptor.nested)
|
|
@@ -423,7 +425,7 @@ function listItemParts(root, nested = false) {
|
|
|
423
425
|
parts = { directTexts: [], texts: [], attributes: [], events: [], expressions: [], expressionAttributes: [], conditions: [], effects: [] }
|
|
424
426
|
for (const node of nested ? ownedElements(root).filter(node => node.matches(itemPartsSelector)) : matching(root, itemPartsSelector)) {
|
|
425
427
|
if (node.hasAttribute("data-k-list-text")) (node.tagName === "TEMPLATE" ? parts.texts : parts.directTexts).push([node, node.dataset.kListText])
|
|
426
|
-
if (__KUDZU_LIST_ATTRIBUTES__ && node.hasAttribute("data-k-list-attrs")) parts.attributes.push([node, JSON.parse(node.dataset.kListAttrs)])
|
|
428
|
+
if (__KUDZU_LIST_ATTRIBUTES__ && node.hasAttribute("data-k-list-attrs")) parts.attributes.push([node, node.dataset.kListAttrs ? JSON.parse(node.dataset.kListAttrs) : undefined])
|
|
427
429
|
if (__KUDZU_LIST_EVENTS__ && node.hasAttribute("data-k-list-events")) parts.events.push([node, node.dataset.kListEvents])
|
|
428
430
|
if (__KUDZU_LIST_EXPRESSIONS__ && node.hasAttribute("data-k-list-expression")) parts.expressions.push([node, JSON.parse(node.dataset.kListExpression)])
|
|
429
431
|
if (__KUDZU_LIST_EXPRESSION_ATTRIBUTES__ && node.hasAttribute("data-k-list-expression-attrs")) parts.expressionAttributes.push([node, JSON.parse(node.dataset.kListExpressionAttrs)])
|
|
@@ -434,10 +436,28 @@ function listItemParts(root, nested = false) {
|
|
|
434
436
|
}
|
|
435
437
|
if (__KUDZU_LIST_EFFECTS__ && node.hasAttribute("data-k-effects")) parts.effects.push(node)
|
|
436
438
|
}
|
|
439
|
+
if (__KUDZU_NESTED_LISTS__) hydrateListItemParts(itemPartPlans.get(root), parts)
|
|
437
440
|
itemParts.set(root, parts)
|
|
438
441
|
return parts
|
|
439
442
|
}
|
|
440
443
|
|
|
444
|
+
function hydrateListItemParts(planned, initial) {
|
|
445
|
+
if (!planned) return
|
|
446
|
+
hydrateListItemPartValues(planned.directTexts, initial.directTexts, value => value === "")
|
|
447
|
+
if (__KUDZU_LIST_TEXT_RANGES__) hydrateListItemPartValues(planned.texts, initial.texts, value => value === "")
|
|
448
|
+
if (__KUDZU_LIST_ATTRIBUTES__) hydrateListItemPartValues(planned.attributes, initial.attributes, value => value === undefined)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function hydrateListItemPartValues(planned, initial, missing) {
|
|
452
|
+
let index = 0
|
|
453
|
+
for (const entry of initial) {
|
|
454
|
+
if (!missing(entry[1])) continue
|
|
455
|
+
if (!planned[index]) throw new Error("Keyed list item markers do not match its template")
|
|
456
|
+
entry[1] = planned[index++][1]
|
|
457
|
+
}
|
|
458
|
+
if (index !== planned.length) throw new Error("Keyed list item markers do not match its template")
|
|
459
|
+
}
|
|
460
|
+
|
|
441
461
|
function listItemPartPlan(template, nested = false) {
|
|
442
462
|
const source = nested ? ownedElements(template) : [template, ...template.querySelectorAll("*")]
|
|
443
463
|
const indexes = new Map(source.map((node, index) => [node, index]))
|