@kudzujs/core 0.16.27 → 0.16.29
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.
|
|
17
|
+
**Current release: [0.16.29](https://github.com/kudzujs/kudzu/releases/tag/v0.16.29).** Write reusable components, native event handlers, and pure collection expressions; Kudzu specializes supported source into static content and direct browser updates, excluding unused style and conditional capabilities.
|
|
18
18
|
|
|
19
19
|
- [Documentation](https://kudzujs.cloud/docs)
|
|
20
20
|
- [Installation guide](https://kudzujs.cloud/docs#install)
|
|
@@ -138,6 +138,13 @@ interactions, labels, status updates, keyboard focus, and responsive layouts in
|
|
|
138
138
|
browser as needed. Binding comments and inert templates mean raw HTML string
|
|
139
139
|
matches or tag stripping are not reliable checks of rendered text.
|
|
140
140
|
|
|
141
|
+
Normal builds also scan generated route HTML after `afterBuild`, reporting counts
|
|
142
|
+
with/without script or modulepreload text markers and unreadable files, with up to
|
|
143
|
+
five paths per category (long paths are truncated). This is a literal text scan,
|
|
144
|
+
not proof of script freedom: comments and inert content can match, inline event
|
|
145
|
+
attributes are not checked, and extra public HTML files are outside the route
|
|
146
|
+
inventory. Quiet and JSON builds omit this summary.
|
|
147
|
+
|
|
141
148
|
Keep checks bounded and report concise counts and failing paths rather than
|
|
142
149
|
repeatedly dumping minified artifacts. A missing tool or truncated response is
|
|
143
150
|
not a pass. After source changes, rerun affected checks. Optional compiler/artifact
|
|
@@ -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(",")
|
|
@@ -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,9 +58,9 @@ 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)
|
|
@@ -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)
|
package/framework/build.mjs
CHANGED
|
@@ -59,13 +59,14 @@ export async function buildWithSession(project, { changedFiles, quiet = false, m
|
|
|
59
59
|
try {
|
|
60
60
|
await recoverOutput(outputDirectory, backupOutput)
|
|
61
61
|
await rm(stagedOutput, { recursive: true, force: true })
|
|
62
|
-
const { result, pageCount, behaviorCount, cache, inspectionData, explanation } = await buildInto(project, stagedOutput, { changedFiles, minify, quiet, retainCache, explanationRoute })
|
|
62
|
+
const { result, pageCount, behaviorCount, cache, inspectionData, explanation, htmlSummary } = await buildInto(project, stagedOutput, { changedFiles, minify, quiet, retainCache, explanationRoute })
|
|
63
63
|
const promoteStarted = project.timings ? performance.now() : 0
|
|
64
64
|
await promoteOutput(stagedOutput, outputDirectory, backupOutput)
|
|
65
65
|
addTiming(project.timings, "writeMs", promoteStarted)
|
|
66
66
|
project.buildCache = retainCache ? cache : undefined
|
|
67
67
|
if (!quiet) {
|
|
68
68
|
console.log(`Built ${pageCount} page(s), ${behaviorCount} interactive page(s) into dist/`)
|
|
69
|
+
console.log(htmlSummary)
|
|
69
70
|
console.log("Browser behavior and accessibility need verification: https://kudzujs.cloud/docs#build")
|
|
70
71
|
}
|
|
71
72
|
return explanationRoute ? explanation : inspection ? createInspectionReport(inspectionData) : result
|
|
@@ -460,7 +461,7 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet
|
|
|
460
461
|
const generated = generateEffectRuntime(await readFile(new URL("./effect-runtime.js", import.meta.url), "utf8"), capabilityIR)
|
|
461
462
|
await writeBundledJavaScript(join(familyDirectory, "kudzu-effect.js"), generated.source, minify, generated.define)
|
|
462
463
|
}
|
|
463
|
-
if (bindings.
|
|
464
|
+
if (bindings.style || lists.styleCount) await writeJavaScript(join(familyDirectory, "kudzu-style.js"), await readFile(new URL("./style.js", import.meta.url), "utf8"), minify)
|
|
464
465
|
if (bindings.count) {
|
|
465
466
|
const generated = generateBindingRuntime(await readFile(new URL("./binding-runtime.js", import.meta.url), "utf8"), capabilityIR, family.navigation)
|
|
466
467
|
await writeBundledJavaScript(join(familyDirectory, "kudzu-binding.js"), generated.source, minify, generated.define)
|
|
@@ -553,6 +554,34 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet
|
|
|
553
554
|
if (typeof config.afterBuild !== "function") throw new Error("kudzu.config afterBuild must be a function")
|
|
554
555
|
await config.afterBuild({ root, outDir: outputDirectory, sourceDir: sourceDirectory, base, routes: plans.map(plan => plan.route), plans, rewrites: sortedRewrites, artifacts })
|
|
555
556
|
}
|
|
557
|
+
let htmlSummary
|
|
558
|
+
if (!quiet) {
|
|
559
|
+
const counts = { "With markers": 0, Unreadable: 0 }
|
|
560
|
+
const samples = { "With markers": [], Unreadable: [] }
|
|
561
|
+
// Scan final route files, not compiler closure metadata or a previous build's scratch report.
|
|
562
|
+
const outputs = routeRecords.map(record => record.output).sort()
|
|
563
|
+
for (let offset = 0; offset < outputs.length; offset += 64) {
|
|
564
|
+
const batch = outputs.slice(offset, offset + 64)
|
|
565
|
+
const categories = await Promise.all(batch.map(async output => {
|
|
566
|
+
try {
|
|
567
|
+
const html = await readFile(join(outputDirectory, output, "index.html"), "utf8")
|
|
568
|
+
if (/<script(?=[\s/>])|<link(?=[\s/>])[^>]*\bmodulepreload\b/i.test(html)) return "With markers"
|
|
569
|
+
} catch {
|
|
570
|
+
return "Unreadable"
|
|
571
|
+
}
|
|
572
|
+
}))
|
|
573
|
+
for (const [index, category] of categories.entries()) {
|
|
574
|
+
if (!category) continue
|
|
575
|
+
counts[category]++
|
|
576
|
+
if (samples[category].length < 5) {
|
|
577
|
+
const path = join("dist", batch[index], "index.html").replaceAll(sep, "/")
|
|
578
|
+
samples[category].push(JSON.stringify(path.length > 240 ? `${path.slice(0, 240)}...` : path))
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
htmlSummary = `Route HTML scan: ${routeRecords.length - counts["With markers"] - counts.Unreadable} without script/modulepreload text markers, ${counts["With markers"]} with markers, ${counts.Unreadable} unreadable.`
|
|
583
|
+
for (const category of Object.keys(counts)) if (counts[category]) htmlSummary += `\n ${category}: ${samples[category].join(", ")}${counts[category] > 5 ? ` (+${counts[category] - 5} more)` : ""}`
|
|
584
|
+
}
|
|
556
585
|
addTiming(project.timings, "writeMs", writeStarted)
|
|
557
586
|
|
|
558
587
|
let explanation
|
|
@@ -570,6 +599,7 @@ async function buildInto(project, outputDirectory, { changedFiles, minify, quiet
|
|
|
570
599
|
result: { sourceResults, incremental },
|
|
571
600
|
inspectionData: { sourceFiles: sourceFiles.map(file => relative(root, file).replaceAll(sep, "/")), sourceResults, compatibility, artifacts },
|
|
572
601
|
explanation,
|
|
602
|
+
htmlSummary,
|
|
573
603
|
pageCount: routeRecords.length,
|
|
574
604
|
behaviorCount,
|
|
575
605
|
cache: { pageRenders, pageSources, placeholders, sourceResults: sourceResultsByFile }
|
|
@@ -13,7 +13,7 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
|
|
|
13
13
|
for (const plan of plans) assertRouteIR(plan, { concrete: true })
|
|
14
14
|
const commandEvents = new Set()
|
|
15
15
|
const nativeEvents = new Set()
|
|
16
|
-
const bindings = { count: 0, text: false, svgConditions: false }
|
|
16
|
+
const bindings = { count: 0, text: false, style: false, conditions: false, svgConditions: false }
|
|
17
17
|
const lists = {
|
|
18
18
|
count: 0,
|
|
19
19
|
styleCount: 0,
|
|
@@ -50,6 +50,8 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
|
|
|
50
50
|
if (event.native) nativeEvents.add(event.event)
|
|
51
51
|
}
|
|
52
52
|
bindings.text ||= plan.bindings.some(binding => binding.target === "text")
|
|
53
|
+
bindings.style ||= plan.bindings.some(binding => binding.target === "style")
|
|
54
|
+
bindings.conditions ||= plan.conditions.length > 0
|
|
53
55
|
bindings.svgConditions ||= plan.conditions.some(condition => condition.svg)
|
|
54
56
|
effects.any ||= plan.effects.length > 0
|
|
55
57
|
effects.derivedDependencies ||= plan.effects.some(effect => effect.dependencyExpressions?.length)
|
|
@@ -118,7 +120,7 @@ export function assertCapabilityIR(capabilityIR, records, options = {}) {
|
|
|
118
120
|
if (!sections.every(name => isRecord(capabilityIR[name]))) throw new Error("Invalid CapabilityIR v1 structure")
|
|
119
121
|
if (!["behaviors", "regularBehaviors", "regularStateSeeds", "dependencyStateSeeds"].every(name => isCount(capabilityIR.routes[name]))) throw new Error("Invalid CapabilityIR v1 route counts")
|
|
120
122
|
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")
|
|
123
|
+
if (!isCount(capabilityIR.bindings.count) || !["text", "style", "conditions", "svgConditions"].every(name => typeof capabilityIR.bindings[name] === "boolean")) throw new Error("Invalid CapabilityIR v1 bindings")
|
|
122
124
|
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
125
|
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
126
|
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")
|
|
@@ -29,12 +29,18 @@ export function generateBindingRuntime(source, capabilityIR, navigable) {
|
|
|
29
29
|
assertCapabilityIR(capabilityIR)
|
|
30
30
|
let runtime = replaceRequired(source, '"./shared-runtime.js"', '"./kudzu.js"', "shared runtime import", "binding-runtime.js")
|
|
31
31
|
runtime = replaceRequired(runtime, '"./serialization.js"', '"./kudzu-serialization.js"', "serialization import", "binding-runtime.js")
|
|
32
|
-
runtime =
|
|
32
|
+
runtime = capabilityIR.bindings.style
|
|
33
|
+
? replaceRequired(runtime, '"./style.js"', '"./kudzu-style.js"', "style import", "binding-runtime.js")
|
|
34
|
+
: replaceSequenceRequired(runtime, [
|
|
35
|
+
['import { serializeStyle } from "./style.js"\n', "", "unused style import"],
|
|
36
|
+
[' } 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"]
|
|
37
|
+
], "binding-runtime.js")
|
|
33
38
|
if (navigable) runtime = specializeNavigationTextDescriptors(runtime)
|
|
34
39
|
return {
|
|
35
40
|
source: runtime,
|
|
36
41
|
define: {
|
|
37
42
|
"globalThis.__KUDZU_TEXT_BINDINGS__": String(capabilityIR.bindings.text),
|
|
43
|
+
"globalThis.__KUDZU_CONDITIONS__": String(capabilityIR.bindings.conditions),
|
|
38
44
|
"globalThis.__KUDZU_SVG_CONDITIONS__": String(capabilityIR.bindings.svgConditions),
|
|
39
45
|
"globalThis.__KUDZU_CAPTURE_STATE__": String(capabilityIR.captures.nestedState)
|
|
40
46
|
}
|