@kudzujs/core 0.16.28 → 0.16.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
14
14
 
15
15
  > Experimental `0.16.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Current release: [0.16.28](https://github.com/kudzujs/kudzu/releases/tag/v0.16.28).** Write reusable components, native event handlers, and pure collection expressions; Kudzu specializes supported source into static content and direct browser updates.
17
+ **Current release: [0.16.30](https://github.com/kudzujs/kudzu/releases/tag/v0.16.30).** Write reusable components, native event handlers, and pure collection expressions; Kudzu specializes supported source into static content and direct browser updates, retaining only required binding targets and capabilities.
18
18
 
19
19
  - [Documentation](https://kudzujs.cloud/docs)
20
20
  - [Installation guide](https://kudzujs.cloud/docs#install)
@@ -4,11 +4,11 @@ import { serializeStyle } from "./style.js"
4
4
 
5
5
  const imports = new Map()
6
6
  const bindingTargets = new Map()
7
- const conditionTargets = new Map()
7
+ const conditionTargets = globalThis.__KUDZU_CONDITIONS__ && new Map()
8
8
  const mountedBindings = new WeakSet()
9
- const mountedConditions = new WeakSet()
9
+ const mountedConditions = globalThis.__KUDZU_CONDITIONS__ && new WeakSet()
10
10
  const bindingRegistrations = new WeakMap()
11
- const conditionRegistrations = new WeakMap()
11
+ const conditionRegistrations = globalThis.__KUDZU_CONDITIONS__ && new WeakMap()
12
12
  const textDescriptors = globalThis.__KUDZU_TEXT_BINDINGS__ && typeof document !== "undefined" ? JSON.parse(document.body.dataset.kTextBindings ?? "[]") : []
13
13
  const bindingTypes = ["class", "disabled", "value", "checked", "style"]
14
14
  const bindingSelector = [...bindingTypes.map(target => `[data-k-bind-${target}]`), "[data-k-bind-attrs]"].join(",")
@@ -16,25 +16,25 @@ const bindingSelector = [...bindingTypes.map(target => `[data-k-bind-${target}]`
16
16
  export function patchBinding(node, target, value) {
17
17
  if (globalThis.__KUDZU_TEXT_BINDINGS__ && target === "text") {
18
18
  patchText(node, value)
19
- } else if (target === "disabled") {
19
+ } else if (globalThis.__KUDZU_DISABLED_BINDINGS__ !== false && target === "disabled") {
20
20
  node.toggleAttribute("disabled", Boolean(value))
21
- } else if (target === "checked") {
21
+ } else if (globalThis.__KUDZU_CHECKED_BINDINGS__ !== false && target === "checked") {
22
22
  node.checked = Boolean(value)
23
- } else if (target === "value") {
23
+ } else if (globalThis.__KUDZU_VALUE_BINDINGS__ !== false && target === "value") {
24
24
  const next = value == null ? "" : String(value)
25
25
  if (node.value !== next) node.value = next
26
26
  } else if (target === "style") {
27
27
  const style = serializeStyle(value)
28
28
  if (style) node.setAttribute("style", style)
29
29
  else node.removeAttribute("style")
30
- } else if (target === "class" && (value == null || value === false)) {
30
+ } else if (globalThis.__KUDZU_CLASS_BINDINGS__ !== false && target === "class" && (value == null || value === false)) {
31
31
  node.removeAttribute("class")
32
- } else if (target === "class") {
32
+ } else if (globalThis.__KUDZU_CLASS_BINDINGS__ !== false && target === "class") {
33
33
  if (node.namespaceURI === "http://www.w3.org/1999/xhtml") node.className = String(value)
34
34
  else node.setAttribute("class", String(value))
35
- } else if (value == null || (value === false && !isStringBooleanAttribute(target))) {
35
+ } else if (globalThis.__KUDZU_ATTRIBUTE_BINDINGS__ !== false && (value == null || (value === false && !isStringBooleanAttribute(target)))) {
36
36
  node.removeAttribute(target)
37
- } else {
37
+ } else if (globalThis.__KUDZU_ATTRIBUTE_BINDINGS__ !== false) {
38
38
  node.setAttribute(target, value === true && !isStringBooleanAttribute(target) ? "" : String(value))
39
39
  }
40
40
  }
@@ -47,6 +47,7 @@ function commitBindings(id) {
47
47
  else patchBinding(binding.node, binding.target, binding.read())
48
48
  }
49
49
  }
50
+ if (!globalThis.__KUDZU_CONDITIONS__) return
50
51
  const conditions = conditionTargets.get(id)
51
52
  if (!conditions) return
52
53
  for (const condition of conditions) {
@@ -57,23 +58,23 @@ function commitBindings(id) {
57
58
 
58
59
  registerCommitter(commitBindings)
59
60
  registerMountHook(mountBindings, "bindings")
60
- registerMountHook(mountConditions, "conditions")
61
+ if (globalThis.__KUDZU_CONDITIONS__) registerMountHook(mountConditions, "conditions")
61
62
  registerUnmountHook(unmountBindings, "bindings")
62
- registerUnmountHook(unmountConditions, "conditions")
63
+ if (globalThis.__KUDZU_CONDITIONS__) registerUnmountHook(unmountConditions, "conditions")
63
64
  registerStateReleaseHook(releaseBindings)
64
65
 
65
66
  if (typeof document !== "undefined") mountDom(document)
66
67
 
67
68
  function mountBindings(root) {
68
- for (const node of matching(root, bindingSelector)) {
69
+ if (globalThis.__KUDZU_ELEMENT_BINDINGS__ !== false) for (const node of matching(root, bindingSelector)) {
69
70
  if (mountedBindings.has(node)) continue
70
71
  mountedBindings.add(node)
71
72
  const registrations = []
72
73
  bindingRegistrations.set(node, registrations)
73
- const descriptors = bindingTypes.flatMap(target => node.hasAttribute(`data-k-bind-${target}`)
74
+ const descriptors = globalThis.__KUDZU_PROPERTY_BINDINGS__ !== false ? bindingTypes.flatMap(target => node.hasAttribute(`data-k-bind-${target}`)
74
75
  ? [[target, JSON.parse(node.dataset[`kBind${capitalize(target)}`])]]
75
- : [])
76
- if (node.dataset.kBindAttrs) descriptors.push(...JSON.parse(node.dataset.kBindAttrs).map(({ target, ...descriptor }) => [target, descriptor]))
76
+ : []) : []
77
+ if (globalThis.__KUDZU_ATTRIBUTE_BINDINGS__ !== false && node.dataset.kBindAttrs) descriptors.push(...JSON.parse(node.dataset.kBindAttrs).map(({ target, ...descriptor }) => [target, descriptor]))
77
78
  for (const [target, descriptor] of descriptors) {
78
79
  if (descriptor.state) {
79
80
  const binding = { node, target, read: Object.hasOwn(descriptor, "truthy") ? () => browserState.get(descriptor.state) ? descriptor.truthy : descriptor.falsy : () => browserState.get(descriptor.state) }
@@ -167,7 +168,7 @@ function updateCondition(condition) {
167
168
  condition.end.parentNode.insertBefore(fragment, condition.end)
168
169
  condition.current = next
169
170
  if (condition.mount) for (const node of nodes) mountDom(node)
170
- const select = condition.start.closest("select[data-k-bind-value]")
171
+ const select = globalThis.__KUDZU_VALUE_BINDINGS__ !== false && condition.start.closest("select[data-k-bind-value]")
171
172
  if (select) {
172
173
  for (const binding of new Set((bindingRegistrations.get(select) ?? []).map(([, entry]) => entry))) {
173
174
  patchBinding(binding.node, binding.target, binding.read())
@@ -176,7 +177,7 @@ function updateCondition(condition) {
176
177
  }
177
178
 
178
179
  function unmountBindings(root) {
179
- for (const node of matching(root, bindingSelector)) {
180
+ if (globalThis.__KUDZU_ELEMENT_BINDINGS__ !== false) for (const node of matching(root, bindingSelector)) {
180
181
  for (const [id, binding] of bindingRegistrations.get(node) ?? []) bindingTargets.get(id)?.delete(binding)
181
182
  bindingRegistrations.delete(node)
182
183
  mountedBindings.delete(node)
@@ -206,6 +207,7 @@ function releaseBindings(id) {
206
207
  bindingRegistrations.delete(binding.node)
207
208
  }
208
209
  bindingTargets.delete(id)
210
+ if (!globalThis.__KUDZU_CONDITIONS__) return
209
211
  for (const condition of conditionTargets.get(id) ?? []) {
210
212
  const registration = conditionRegistrations.get(condition.start)
211
213
  for (const [conditionId, entry] of registration?.registrations ?? []) conditionTargets.get(conditionId)?.delete(entry)
@@ -461,7 +461,7 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet
461
461
  const generated = generateEffectRuntime(await readFile(new URL("./effect-runtime.js", import.meta.url), "utf8"), capabilityIR)
462
462
  await writeBundledJavaScript(join(familyDirectory, "kudzu-effect.js"), generated.source, minify, generated.define)
463
463
  }
464
- if (bindings.count || lists.styleCount) await writeJavaScript(join(familyDirectory, "kudzu-style.js"), await readFile(new URL("./style.js", import.meta.url), "utf8"), minify)
464
+ if (bindings.style || lists.styleCount) await writeJavaScript(join(familyDirectory, "kudzu-style.js"), await readFile(new URL("./style.js", import.meta.url), "utf8"), minify)
465
465
  if (bindings.count) {
466
466
  const generated = generateBindingRuntime(await readFile(new URL("./binding-runtime.js", import.meta.url), "utf8"), capabilityIR, family.navigation)
467
467
  await writeBundledJavaScript(join(familyDirectory, "kudzu-binding.js"), generated.source, minify, generated.define)
@@ -1,6 +1,8 @@
1
1
  import { assertRouteBuildRecord } from "./route-build-record.mjs"
2
2
  import { assertJsonSafe, assertRouteIR } from "./route-ir.mjs"
3
3
 
4
+ const propertyBindings = ["class", "disabled", "value", "checked", "style"]
5
+
4
6
  export function usesRouteDependencyRuntime({ plan, navigable, hasBindings, hasLists }, validate = true) {
5
7
  if (validate) assertRouteIR(plan)
6
8
  const hasDependencies = plan.effects.some(effect => effect.dependencies?.length)
@@ -13,7 +15,7 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
13
15
  for (const plan of plans) assertRouteIR(plan, { concrete: true })
14
16
  const commandEvents = new Set()
15
17
  const nativeEvents = new Set()
16
- const bindings = { count: 0, text: false, svgConditions: false }
18
+ const bindings = { count: 0, text: false, style: false, conditions: false, svgConditions: false }
17
19
  const lists = {
18
20
  count: 0,
19
21
  styleCount: 0,
@@ -50,6 +52,8 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
50
52
  if (event.native) nativeEvents.add(event.event)
51
53
  }
52
54
  bindings.text ||= plan.bindings.some(binding => binding.target === "text")
55
+ bindings.style ||= plan.bindings.some(binding => binding.target === "style")
56
+ bindings.conditions ||= plan.conditions.length > 0
53
57
  bindings.svgConditions ||= plan.conditions.some(condition => condition.svg)
54
58
  effects.any ||= plan.effects.length > 0
55
59
  effects.derivedDependencies ||= plan.effects.some(effect => effect.dependencyExpressions?.length)
@@ -90,6 +94,9 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
90
94
  dependencyStateSeeds: routeEntries.filter(route => route.hasStateSeed && route.usesDependencyRuntime).length
91
95
  }
92
96
  bindings.count = routeEntries.filter(route => route.hasBindings).length
97
+ const bindingTargets = new Set(plans.flatMap(plan => plan.bindings.map(binding => binding.target)))
98
+ bindings.properties = propertyBindings.filter(target => bindingTargets.has(target))
99
+ bindings.attributes = [...bindingTargets].some(target => target !== "text" && !propertyBindings.includes(target))
93
100
  lists.count = routeEntries.filter(route => route.hasLists).length
94
101
  lists.styleCount = routeEntries.filter(route => route.hasListStyles).length
95
102
  lists.generalRowHooks ||= lists.rowRefs || lists.complexRowState
@@ -118,7 +125,9 @@ export function assertCapabilityIR(capabilityIR, records, options = {}) {
118
125
  if (!sections.every(name => isRecord(capabilityIR[name]))) throw new Error("Invalid CapabilityIR v1 structure")
119
126
  if (!["behaviors", "regularBehaviors", "regularStateSeeds", "dependencyStateSeeds"].every(name => isCount(capabilityIR.routes[name]))) throw new Error("Invalid CapabilityIR v1 route counts")
120
127
  if (!["command", "native"].every(name => Array.isArray(capabilityIR.events[name]) && capabilityIR.events[name].every(event => typeof event === "string")) || typeof capabilityIR.events.hasNativeHandlers !== "boolean") throw new Error("Invalid CapabilityIR v1 events")
121
- if (!isCount(capabilityIR.bindings.count) || !["text", "svgConditions"].every(name => typeof capabilityIR.bindings[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 bindings")
128
+ if (!isCount(capabilityIR.bindings.count) || !["text", "style", "conditions", "svgConditions", "attributes"].every(name => typeof capabilityIR.bindings[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 bindings")
129
+ if (!Array.isArray(capabilityIR.bindings.properties) || capabilityIR.bindings.properties.some(target => !propertyBindings.includes(target)) || new Set(capabilityIR.bindings.properties).size !== capabilityIR.bindings.properties.length) throw new Error("Invalid CapabilityIR v1 binding properties")
130
+ if (capabilityIR.bindings.style !== capabilityIR.bindings.properties.includes("style")) throw new Error("CapabilityIR binding style flag does not match its properties")
122
131
  const listFlags = ["conditions", "svg", "deepConditions", "textRanges", "attributes", "events", "expressions", "expressionAttributes", "seeds", "effects", "rowHooks", "rowRefs", "complexRowState", "nested", "selectors", "calculated", "static", "indexes", "stableFastPaths", "generalRowHooks", "asyncParts", "mounts"]
123
132
  if (!isCount(capabilityIR.lists.count) || !isCount(capabilityIR.lists.styleCount) || !listFlags.every(name => typeof capabilityIR.lists[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 lists")
124
133
  if (!["any", "derivedDependencies", "itemDependencies", "captures", "navigable", "navigableOwners"].every(name => typeof capabilityIR.effects[name] === "boolean") || !["nestedState", "setter"].every(name => typeof capabilityIR.captures[name] === "boolean") || !["shared", "dependency"].every(name => typeof capabilityIR.runtime[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 effect, capture, or runtime flags")
@@ -27,14 +27,32 @@ export function generateEffectRuntime(source, capabilityIR) {
27
27
 
28
28
  export function generateBindingRuntime(source, capabilityIR, navigable) {
29
29
  assertCapabilityIR(capabilityIR)
30
+ const { properties, attributes } = capabilityIR.bindings
30
31
  let runtime = replaceRequired(source, '"./shared-runtime.js"', '"./kudzu.js"', "shared runtime import", "binding-runtime.js")
31
32
  runtime = replaceRequired(runtime, '"./serialization.js"', '"./kudzu-serialization.js"', "serialization import", "binding-runtime.js")
32
- runtime = replaceRequired(runtime, '"./style.js"', '"./kudzu-style.js"', "style import", "binding-runtime.js")
33
+ runtime = replaceRequired(runtime, /const bindingTypes = \[[^\n]+\]/, `const bindingTypes = ${JSON.stringify(properties)}`, "binding properties", "binding-runtime.js")
34
+ const selectors = [...properties.map(target => `[data-k-bind-${target}]`), ...(attributes ? ["[data-k-bind-attrs]"] : [])]
35
+ // The existing generic selector is smaller when all five property kinds are used.
36
+ if (properties.length !== 5 || !attributes) runtime = replaceRequired(runtime, /^const bindingSelector = .*$/m, `const bindingSelector = ${JSON.stringify(selectors.join(","))}`, "binding selectors", "binding-runtime.js")
37
+ runtime = capabilityIR.bindings.style
38
+ ? replaceRequired(runtime, '"./style.js"', '"./kudzu-style.js"', "style import", "binding-runtime.js")
39
+ : replaceSequenceRequired(runtime, [
40
+ ['import { serializeStyle } from "./style.js"\n', "", "unused style import"],
41
+ [' } else if (target === "style") {\n const style = serializeStyle(value)\n if (style) node.setAttribute("style", style)\n else node.removeAttribute("style")\n', "", "unused style branch"]
42
+ ], "binding-runtime.js")
33
43
  if (navigable) runtime = specializeNavigationTextDescriptors(runtime)
34
44
  return {
35
45
  source: runtime,
36
46
  define: {
37
47
  "globalThis.__KUDZU_TEXT_BINDINGS__": String(capabilityIR.bindings.text),
48
+ "globalThis.__KUDZU_ELEMENT_BINDINGS__": String(selectors.length > 0),
49
+ "globalThis.__KUDZU_PROPERTY_BINDINGS__": String(properties.length > 0),
50
+ "globalThis.__KUDZU_ATTRIBUTE_BINDINGS__": String(attributes),
51
+ "globalThis.__KUDZU_CLASS_BINDINGS__": String(properties.includes("class")),
52
+ "globalThis.__KUDZU_DISABLED_BINDINGS__": String(properties.includes("disabled")),
53
+ "globalThis.__KUDZU_CHECKED_BINDINGS__": String(properties.includes("checked")),
54
+ "globalThis.__KUDZU_VALUE_BINDINGS__": String(properties.includes("value")),
55
+ "globalThis.__KUDZU_CONDITIONS__": String(capabilityIR.bindings.conditions),
38
56
  "globalThis.__KUDZU_SVG_CONDITIONS__": String(capabilityIR.bindings.svgConditions),
39
57
  "globalThis.__KUDZU_CAPTURE_STATE__": String(capabilityIR.captures.nestedState)
40
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.16.28",
3
+ "version": "0.16.30",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",