@kudzujs/core 0.8.53 → 0.8.56
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/MIGRATION_ROADMAP.md +2 -1
- package/PERFORMANCE.md +13 -1
- package/README.md +1 -1
- package/RELEASES.md +101 -0
- package/docs/next-architecture/README.md +1 -1
- package/docs/next-architecture/compiler-current-architecture.md +14 -11
- package/docs/next-architecture/large-application-ai-native-roadmap.md +4 -4
- package/docs/next-architecture/versioning.md +4 -1
- package/framework/README.md +4 -0
- package/framework/build.mjs +257 -85
- package/framework/compiler/effect-codegen.mjs +17 -17
- package/framework/compiler/param-codegen.mjs +2 -2
- package/framework/compiler/route-artifact-report.mjs +133 -0
- package/framework/compiler/runtime-family-planner.mjs +48 -0
- package/framework/compiler/source-compiler.mjs +2 -1
- package/framework/compiler/worker-compiler.mjs +23 -4
- package/framework/core.d.ts +2 -0
- package/framework/core.mjs +2 -2
- package/framework/dev-server.mjs +7 -2
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path"
|
|
2
2
|
|
|
3
3
|
export function createParamCodegen({ browserPath, inlineJson, relativeModulePath }) {
|
|
4
|
-
return function printParamEntry(schema, params, searchParams, searchParamsWritable, output,
|
|
4
|
+
return function printParamEntry(schema, params, searchParams, searchParamsWritable, output, runtimeDirectory, base, runtimeName, navigable) {
|
|
5
5
|
const hasSearch = searchParams.length || searchParamsWritable
|
|
6
6
|
const signature = hasSearch ? "pathname, search" : "pathname"
|
|
7
7
|
const prefix = navigable ? `export function initializeParams(${signature}) {\n${searchParamsWritable ? "globalThis.__kSetSearchParams = setSearchParams\n" : ""}` : `${schema ? "let pathname = location.pathname\n" : ""}${hasSearch ? "let search = location.search\n" : ""}`
|
|
@@ -66,7 +66,7 @@ function setSearchParams(update, replace) {
|
|
|
66
66
|
}
|
|
67
67
|
${navigable ? "" : `globalThis.__kSetSearchParams = setSearchParams
|
|
68
68
|
addEventListener("popstate", () => ${searchParams.length ? "initializeSearch(location.search)" : "undefined"})`}` : ""
|
|
69
|
-
return `import { browserState, commitDom } from ${JSON.stringify(relativeModulePath(output, join(
|
|
69
|
+
return `import { browserState, commitDom } from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, runtimeName)))}
|
|
70
70
|
${searchInitializer}${prefix}${pathname}${query}${suffix}${writer}`
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { dirname, relative, resolve, sep } from "node:path"
|
|
2
|
+
import { assetPath } from "./path-helpers.mjs"
|
|
3
|
+
import { assertRouteBuildRecord } from "./route-build-record.mjs"
|
|
4
|
+
import { planRouteCapabilities } from "./route-capability-planner.mjs"
|
|
5
|
+
import { capabilitySignature, planRuntimeFamilies } from "./runtime-family-planner.mjs"
|
|
6
|
+
|
|
7
|
+
export function createRouteArtifactReport(records, {
|
|
8
|
+
base = "",
|
|
9
|
+
handlerMetafile,
|
|
10
|
+
outputDirectory,
|
|
11
|
+
navigationAssets = new Map(),
|
|
12
|
+
runtimeFamilies,
|
|
13
|
+
runtimeFamilyByRecord,
|
|
14
|
+
workerReferences = [],
|
|
15
|
+
workerOutputs = new Map()
|
|
16
|
+
} = {}) {
|
|
17
|
+
for (const record of records) assertRouteBuildRecord(record)
|
|
18
|
+
if (!runtimeFamilies || !runtimeFamilyByRecord) ({ families: runtimeFamilies, familyByRecord: runtimeFamilyByRecord } = planRuntimeFamilies(records))
|
|
19
|
+
const handlerGraph = handlerMetafile ? outputGraph(handlerMetafile, outputDirectory, base) : new Map()
|
|
20
|
+
const routes = records.map(record => {
|
|
21
|
+
const capability = planRouteCapabilities([record], { navigationRouteCount: Number(record.capabilities.navigable) })
|
|
22
|
+
const handlerEntries = [...new Set(record.artifacts.handlers.map(reference => reference.module))].sort()
|
|
23
|
+
const handlerOutputs = closure(handlerEntries, handlerGraph, Boolean(handlerMetafile))
|
|
24
|
+
const workers = workerReferences
|
|
25
|
+
.filter(reference => record.artifacts.effects.some(effect => effect.module === reference.module && effect.handler === reference.handler))
|
|
26
|
+
.map(reference => {
|
|
27
|
+
const output = workerOutputs.get(reference.placeholder)
|
|
28
|
+
if (!output) throw new Error(`Worker output was not recorded: ${reference.root}`)
|
|
29
|
+
return { source: reference.root, entry: output.entry, chunks: [...output.chunks].sort() }
|
|
30
|
+
})
|
|
31
|
+
.sort((left, right) => left.source.localeCompare(right.source) || left.entry.localeCompare(right.entry))
|
|
32
|
+
return {
|
|
33
|
+
route: record.route,
|
|
34
|
+
capability: {
|
|
35
|
+
signature: capabilitySignature(capability),
|
|
36
|
+
manifest: capability
|
|
37
|
+
},
|
|
38
|
+
runtime: routeRuntimeEdges(record, capability, runtimeFamilyByRecord.get(record), base, navigationAssets.get(record.route)),
|
|
39
|
+
handlers: {
|
|
40
|
+
entries: handlerEntries,
|
|
41
|
+
chunks: handlerOutputs.filter(output => !handlerEntries.includes(output))
|
|
42
|
+
},
|
|
43
|
+
workers,
|
|
44
|
+
styles: [...record.artifacts.styles].sort()
|
|
45
|
+
}
|
|
46
|
+
}).sort((left, right) => left.route.localeCompare(right.route))
|
|
47
|
+
const owners = new Map()
|
|
48
|
+
for (const route of routes) {
|
|
49
|
+
for (const path of [...route.handlers.chunks, ...route.workers.flatMap(worker => worker.chunks)]) {
|
|
50
|
+
const paths = owners.get(path) ?? new Set()
|
|
51
|
+
paths.add(route.route)
|
|
52
|
+
owners.set(path, paths)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
version: 2,
|
|
57
|
+
runtimeFamilies: runtimeFamilies.map(family => ({
|
|
58
|
+
id: family.id,
|
|
59
|
+
signature: family.signature,
|
|
60
|
+
navigation: family.navigation,
|
|
61
|
+
routes: records.filter(record => runtimeFamilyByRecord.get(record)?.id === family.id).map(record => record.route).sort(),
|
|
62
|
+
manifest: family.capability,
|
|
63
|
+
requirements: familyRuntimeRequirements(family, base)
|
|
64
|
+
})),
|
|
65
|
+
routes,
|
|
66
|
+
sharedChunks: [...owners].filter(([, routes]) => routes.size > 1).map(([path, routes]) => ({ path, routes: [...routes].sort() })).sort((left, right) => left.path.localeCompare(right.path))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function routeRuntimeEdges(record, capability, family, base, navigationAsset) {
|
|
71
|
+
const entries = Object.values(record.entries).map(path => assetPath(base, `assets/${path}`))
|
|
72
|
+
if (navigationAsset) entries.push(navigationAsset)
|
|
73
|
+
if (!family) return { family: null, entries: [...new Set(entries)].sort(), requirements: [] }
|
|
74
|
+
const modules = []
|
|
75
|
+
const add = name => modules.push(runtimeAsset(base, family.id, name))
|
|
76
|
+
if (record.capabilities.hasBehaviors || record.capabilities.navigable) add(record.capabilities.usesDependencyRuntime ? "kudzu-deps.js" : "kudzu.js")
|
|
77
|
+
if (record.capabilities.hasBindings) add("kudzu-binding.js")
|
|
78
|
+
if (record.capabilities.hasLists) add("kudzu-list.js")
|
|
79
|
+
if (record.capabilities.hasBindings || record.capabilities.hasLists && family.capability.lists.styleCount) add("kudzu-style.js")
|
|
80
|
+
if (capability.effects.derivedDependencies || record.capabilities.hasLists && family.capability.lists.selectors) add("kudzu-collection-selector.js")
|
|
81
|
+
if (record.capabilities.hasBindings || capability.events.hasNativeHandlers || record.capabilities.hasEffects && family.capability.effects.captures) add("kudzu-serialization.js")
|
|
82
|
+
if (record.capabilities.hasEffects) add("kudzu-effect.js")
|
|
83
|
+
if (capability.events.hasNativeHandlers) add("kudzu-native.js")
|
|
84
|
+
return { family: family.id, entries: [...new Set(entries)].sort(), requirements: [...new Set(modules)].sort() }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function familyRuntimeRequirements(family, base) {
|
|
88
|
+
const { bindings, effects, events, lists, runtime } = family.capability
|
|
89
|
+
const names = [runtime.dependency ? "kudzu-deps.js" : "kudzu.js"]
|
|
90
|
+
if (bindings.count || events.hasNativeHandlers || effects.captures) names.push("kudzu-serialization.js")
|
|
91
|
+
if (effects.any) names.push("kudzu-effect.js")
|
|
92
|
+
if (bindings.count || lists.styleCount) names.push("kudzu-style.js")
|
|
93
|
+
if (bindings.count) names.push("kudzu-binding.js")
|
|
94
|
+
if (effects.derivedDependencies || lists.selectors) names.push("kudzu-collection-selector.js")
|
|
95
|
+
if (lists.count) names.push("kudzu-list.js")
|
|
96
|
+
if (events.hasNativeHandlers) names.push("kudzu-native.js")
|
|
97
|
+
return names.map(name => runtimeAsset(base, family.id, name)).sort()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const runtimeAsset = (base, id, name) => assetPath(base, `assets/runtime/${id}/${name}`)
|
|
101
|
+
|
|
102
|
+
function outputGraph(metafile, outputDirectory, base) {
|
|
103
|
+
if (!metafile || typeof metafile !== "object" || !metafile.outputs || typeof metafile.outputs !== "object") throw new Error("Invalid esbuild metafile")
|
|
104
|
+
if (typeof outputDirectory !== "string") throw new Error("Route artifact reporting requires an output directory")
|
|
105
|
+
const paths = new Map()
|
|
106
|
+
for (const output of Object.keys(metafile.outputs)) paths.set(resolve(output), outputUrl(resolve(output), outputDirectory, base))
|
|
107
|
+
const graph = new Map()
|
|
108
|
+
for (const [output, metadata] of Object.entries(metafile.outputs)) {
|
|
109
|
+
const absolute = resolve(output)
|
|
110
|
+
const url = paths.get(absolute)
|
|
111
|
+
const imports = (metadata.imports ?? []).filter(entry => !entry.external).map(entry => paths.get(resolve(entry.path)) ?? paths.get(resolve(dirname(absolute), entry.path))).filter(Boolean)
|
|
112
|
+
graph.set(url, [...new Set(imports)].sort())
|
|
113
|
+
}
|
|
114
|
+
return graph
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function outputUrl(output, outputDirectory, base) {
|
|
118
|
+
const path = relative(outputDirectory, output)
|
|
119
|
+
if (!path || path === ".." || path.startsWith(`..${sep}`)) throw new Error(`Bundled output is outside the build directory: ${output}`)
|
|
120
|
+
return assetPath(base, path.replaceAll(sep, "/"))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function closure(entries, graph, requireEntries) {
|
|
124
|
+
const visited = new Set()
|
|
125
|
+
const visit = output => {
|
|
126
|
+
if (visited.has(output)) return
|
|
127
|
+
if (requireEntries && !graph.has(output)) throw new Error(`Bundled handler entry was not emitted: ${output}`)
|
|
128
|
+
visited.add(output)
|
|
129
|
+
for (const imported of graph.get(output) ?? []) visit(imported)
|
|
130
|
+
}
|
|
131
|
+
for (const entry of entries) visit(entry)
|
|
132
|
+
return [...visited].sort()
|
|
133
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { assertRouteBuildRecord } from "./route-build-record.mjs"
|
|
3
|
+
import { planRouteCapabilities } from "./route-capability-planner.mjs"
|
|
4
|
+
|
|
5
|
+
export function capabilitySignature(capability) {
|
|
6
|
+
return createHash("sha256").update(JSON.stringify(capability)).digest("hex")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function planRuntimeFamilies(records, navigationGroups = []) {
|
|
10
|
+
for (const record of records) assertRouteBuildRecord(record)
|
|
11
|
+
const grouped = new Set()
|
|
12
|
+
const scopes = []
|
|
13
|
+
for (const group of navigationGroups) {
|
|
14
|
+
const groupRecords = group.buildRecords ?? group.records ?? []
|
|
15
|
+
if (!groupRecords.length) continue
|
|
16
|
+
for (const record of groupRecords) {
|
|
17
|
+
if (!records.includes(record)) throw new Error(`Navigation runtime family contains an unknown route: ${record.route}`)
|
|
18
|
+
if (grouped.has(record)) throw new Error(`Route belongs to multiple runtime families: ${record.route}`)
|
|
19
|
+
grouped.add(record)
|
|
20
|
+
}
|
|
21
|
+
scopes.push({ navigation: true, records: groupRecords })
|
|
22
|
+
}
|
|
23
|
+
for (const record of records) if (!grouped.has(record) && record.capabilities.hasBehaviors) scopes.push({ navigation: false, records: [record] })
|
|
24
|
+
|
|
25
|
+
const familiesBySignature = new Map()
|
|
26
|
+
const signaturesById = new Map()
|
|
27
|
+
const familyByRecord = new Map()
|
|
28
|
+
for (const scope of scopes) {
|
|
29
|
+
const capability = planRouteCapabilities(scope.records, { navigationRouteCount: scope.navigation ? scope.records.length : 0 })
|
|
30
|
+
const descriptor = { version: 1, navigation: scope.navigation, capability }
|
|
31
|
+
const signature = capabilitySignature(descriptor)
|
|
32
|
+
const id = signature.slice(0, 16)
|
|
33
|
+
const existingSignature = signaturesById.get(id)
|
|
34
|
+
if (existingSignature && existingSignature !== signature) throw new Error(`Runtime family ID collision: ${id}`)
|
|
35
|
+
signaturesById.set(id, signature)
|
|
36
|
+
let family = familiesBySignature.get(signature)
|
|
37
|
+
if (!family) {
|
|
38
|
+
family = { id, signature, navigation: scope.navigation, capability, records: [] }
|
|
39
|
+
familiesBySignature.set(signature, family)
|
|
40
|
+
}
|
|
41
|
+
for (const record of scope.records) {
|
|
42
|
+
family.records.push(record)
|
|
43
|
+
familyByRecord.set(record, family)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const families = [...familiesBySignature.values()].map(family => ({ ...family, records: [...family.records].sort((left, right) => left.route.localeCompare(right.route)) })).sort((left, right) => left.id.localeCompare(right.id))
|
|
47
|
+
return { families, familyByRecord }
|
|
48
|
+
}
|
|
@@ -24,6 +24,7 @@ import { createZustandPass } from "./zustand-pass.mjs"
|
|
|
24
24
|
|
|
25
25
|
export function createSourceCompiler(project) {
|
|
26
26
|
const { root, sourceDirectory, pagesDirectory, workDirectory, workerCompiler, modules, counters } = project
|
|
27
|
+
const buildDirectory = project.buildDirectory ?? workDirectory
|
|
27
28
|
const { ordinaryRuntimeDependencies, resolveSourceImport, runtimeModuleReference } = project.graph
|
|
28
29
|
const parseSourceFile = (file, source) => modules.read(file, source).sourceFile
|
|
29
30
|
const staticAssetExtensions = new Set([".avif", ".gif", ".ico", ".jpeg", ".jpg", ".otf", ".png", ".svg", ".ttf", ".webp", ".woff", ".woff2"])
|
|
@@ -3166,7 +3167,7 @@ function clientModulePath(file) {
|
|
|
3166
3167
|
}
|
|
3167
3168
|
|
|
3168
3169
|
function compiledPath(file) {
|
|
3169
|
-
return join(
|
|
3170
|
+
return join(buildDirectory, relative(sourceDirectory, file)).replace(/\.(?:ts|tsx)$/, ".mjs")
|
|
3170
3171
|
}
|
|
3171
3172
|
|
|
3172
3173
|
const compileEventCommand = createCommandSpecializer({ isPrimitiveLiteral: isPrimitiveDefaultLiteral })
|
|
@@ -130,7 +130,7 @@ export function createWorkerCompiler({
|
|
|
130
130
|
|
|
131
131
|
const emit = async (references, sourceFiles, assetsDirectory, base, minify) => {
|
|
132
132
|
const roots = [...new Set(references.map(reference => resolve(sourceDirectory, reference.root)))].sort()
|
|
133
|
-
if (!roots.length) return new Map()
|
|
133
|
+
if (!roots.length) return { assets: new Map(), outputs: new Map() }
|
|
134
134
|
await validateGraphs(roots, sourceFiles)
|
|
135
135
|
const workerDirectory = resolve(assetsDirectory, "workers")
|
|
136
136
|
await mkdir(workerDirectory, { recursive: true })
|
|
@@ -152,16 +152,35 @@ export function createWorkerCompiler({
|
|
|
152
152
|
logLevel: "silent"
|
|
153
153
|
})
|
|
154
154
|
const emitted = new Map()
|
|
155
|
+
const outputUrls = new Map(Object.keys(result.metafile.outputs).map(output => {
|
|
156
|
+
const outputFile = resolve(root, output)
|
|
157
|
+
return [outputFile, assetPath(base, relative(resolve(assetsDirectory, ".."), outputFile).replaceAll(sep, "/"))]
|
|
158
|
+
}))
|
|
159
|
+
const outputImports = new Map(Object.entries(result.metafile.outputs).map(([output, metadata]) => {
|
|
160
|
+
const outputFile = resolve(root, output)
|
|
161
|
+
return [outputUrls.get(outputFile), (metadata.imports ?? []).filter(entry => !entry.external).map(entry => outputUrls.get(resolve(root, entry.path)) ?? outputUrls.get(resolve(outputFile, "..", entry.path))).filter(Boolean)]
|
|
162
|
+
}))
|
|
163
|
+
const outputs = new Map()
|
|
155
164
|
for (const [output, metadata] of Object.entries(result.metafile.outputs)) {
|
|
156
165
|
if (!metadata.entryPoint) continue
|
|
157
166
|
const entry = resolve(root, metadata.entryPoint)
|
|
158
167
|
const rootReferences = references.filter(reference => resolve(sourceDirectory, reference.root) === entry)
|
|
159
168
|
const outputFile = resolve(root, output)
|
|
160
|
-
const url =
|
|
161
|
-
|
|
169
|
+
const url = outputUrls.get(outputFile)
|
|
170
|
+
const closure = new Set()
|
|
171
|
+
const visit = path => {
|
|
172
|
+
if (closure.has(path)) return
|
|
173
|
+
closure.add(path)
|
|
174
|
+
for (const imported of outputImports.get(path) ?? []) visit(imported)
|
|
175
|
+
}
|
|
176
|
+
visit(url)
|
|
177
|
+
for (const reference of rootReferences) {
|
|
178
|
+
emitted.set(reference.placeholder, url)
|
|
179
|
+
outputs.set(reference.placeholder, { entry: url, chunks: [...closure].filter(path => path !== url).sort() })
|
|
180
|
+
}
|
|
162
181
|
}
|
|
163
182
|
for (const reference of references) if (!emitted.has(reference.placeholder)) throw new Error(`Worker entry was not emitted: ${reference.root}`)
|
|
164
|
-
return emitted
|
|
183
|
+
return { assets: emitted, outputs }
|
|
165
184
|
}
|
|
166
185
|
|
|
167
186
|
return { candidate, emit, rejectConstructions, rejectOrdinaryImports, rewriteEffect }
|
package/framework/core.d.ts
CHANGED
package/framework/core.mjs
CHANGED
|
@@ -501,10 +501,10 @@ export async function renderPage(component, metadata = {}, props = {}, layout) {
|
|
|
501
501
|
? `<script type="module"${capability} src="${escapeAttribute(metadata.paramAsset)}"></script>`
|
|
502
502
|
: ""
|
|
503
503
|
const bindingRuntime = renderContext.hasBindings
|
|
504
|
-
? `<script type="module"${capability} src="${assetPath(metadata.base, "assets/kudzu-binding.js")}"></script>`
|
|
504
|
+
? `<script type="module"${capability} src="${escapeAttribute(metadata.bindingAsset ?? assetPath(metadata.base, "assets/kudzu-binding.js"))}"></script>`
|
|
505
505
|
: ""
|
|
506
506
|
const listRuntime = renderContext.hasLists
|
|
507
|
-
? `<script type="module"${capability} src="${assetPath(metadata.base, "assets/kudzu-list.js")}"></script>`
|
|
507
|
+
? `<script type="module"${capability} src="${escapeAttribute(metadata.listAsset ?? assetPath(metadata.base, "assets/kudzu-list.js"))}"></script>`
|
|
508
508
|
: ""
|
|
509
509
|
const effectRuntime = renderContext.hasEffects
|
|
510
510
|
? `<script type="module"${capability} src="${escapeAttribute(metadata.effectAsset)}"></script>`
|
package/framework/dev-server.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { extname, join, resolve, sep } from "node:path"
|
|
|
5
5
|
import { browserPath, withBase } from "./compiler/path-helpers.mjs"
|
|
6
6
|
import { stateSchema } from "./dev-state.js"
|
|
7
7
|
|
|
8
|
-
const devClient = (session, revision, schema) => `<script>(()=>{const show=event=>{let box=document.getElementById("__kudzu_error");if(!box){box=document.createElement("div");box.id="__kudzu_error";box.setAttribute("role","alert");box.setAttribute("aria-live","assertive");box.style.cssText="position:fixed;inset:0;z-index:2147483647;overflow:auto;padding:2rem;background:#200;color:#fff;font:16px/1.5 ui-monospace,monospace";const title=document.createElement("strong"),text=document.createElement("pre");title.textContent="Kudzu build error";text.style.whiteSpace="pre-wrap";box.append(title,text);document.body.append(box)}box.querySelector("pre").textContent=event.data};const schema=${inlineJson(schema)},route=location.pathname+location.search+location.hash,urls=[...document.querySelectorAll('script[type="module"][src]')].map(node=>node.src).filter(url=>/\/assets\/kudzu(?:-
|
|
8
|
+
const devClient = (session, revision, schema) => `<script>(()=>{const show=event=>{let box=document.getElementById("__kudzu_error");if(!box){box=document.createElement("div");box.id="__kudzu_error";box.setAttribute("role","alert");box.setAttribute("aria-live","assertive");box.style.cssText="position:fixed;inset:0;z-index:2147483647;overflow:auto;padding:2rem;background:#200;color:#fff;font:16px/1.5 ui-monospace,monospace";const title=document.createElement("strong"),text=document.createElement("pre");title.textContent="Kudzu build error";text.style.whiteSpace="pre-wrap";box.append(title,text);document.body.append(box)}box.querySelector("pre").textContent=event.data};const schema=${inlineJson(schema)},route=location.pathname+location.search+location.hash,urls=[...document.querySelectorAll('script[type="module"][src]')].map(node=>node.src).filter(url=>/\/assets\/runtime\/[a-f0-9]+\/kudzu(?:-deps|-binding|-list|-native)?\.js$/.test(new URL(url).pathname));const devImport=import("/__kudzu_dev.js"),runtimeImports=Promise.allSettled(urls.map(url=>import(url)));const ready=(async()=>{const dev=await devImport,modules=await runtimeImports,runtime=modules.find(result=>result.status==="fulfilled"&&result.value.browserState instanceof Map&&typeof result.value.commitDom==="function")?.value;try{dev.restoreState(sessionStorage,route,runtime?.browserState,schema,runtime?.commitDom)}catch{}return{dev,runtime}})().catch(()=>({}));const events=new EventSource("/__kudzu_reload?session=${session}&revision=${revision}");let reloading=false;events.addEventListener("reload",async()=>{if(reloading)return;reloading=true;try{const{dev,runtime}=await ready;dev?.snapshotState(sessionStorage,route,runtime?.browserState,schema)}catch{}location.reload()});events.addEventListener("build-error",show)})()</script>`
|
|
9
9
|
|
|
10
10
|
export function parseDevPort(value) {
|
|
11
11
|
if (value === undefined || value.trim() === "") return 3000
|
|
@@ -85,6 +85,7 @@ export async function startDevServer({ build, port, host, base, sourceDirectory,
|
|
|
85
85
|
let rebuilding = false
|
|
86
86
|
let pending = false
|
|
87
87
|
let changedFile
|
|
88
|
+
const changedFiles = new Set()
|
|
88
89
|
const rebuild = async () => {
|
|
89
90
|
if (rebuilding) {
|
|
90
91
|
pending = true
|
|
@@ -93,13 +94,16 @@ export async function startDevServer({ build, port, host, base, sourceDirectory,
|
|
|
93
94
|
rebuilding = true
|
|
94
95
|
do {
|
|
95
96
|
pending = false
|
|
97
|
+
const changes = [...changedFiles]
|
|
98
|
+
changedFiles.clear()
|
|
96
99
|
try {
|
|
97
|
-
await build({ quiet: true, minify: false })
|
|
100
|
+
await build({ changedFiles: changes, quiet: true, minify: false })
|
|
98
101
|
buildError = undefined
|
|
99
102
|
revision++
|
|
100
103
|
console.log(`Rebuilt after ${changedFile ?? "source change"}`)
|
|
101
104
|
for (const client of clients) sendEvent(client, "reload")
|
|
102
105
|
} catch (error) {
|
|
106
|
+
for (const file of changes) changedFiles.add(file)
|
|
103
107
|
buildError = errorText(error)
|
|
104
108
|
console.error(error)
|
|
105
109
|
for (const client of clients) sendEvent(client, "build-error", buildError)
|
|
@@ -110,6 +114,7 @@ export async function startDevServer({ build, port, host, base, sourceDirectory,
|
|
|
110
114
|
const watcher = watch(sourceDirectory, { recursive: true })
|
|
111
115
|
for await (const event of watcher) {
|
|
112
116
|
changedFile = event.filename
|
|
117
|
+
changedFiles.add(event.filename)
|
|
113
118
|
clearTimeout(timer)
|
|
114
119
|
timer = setTimeout(rebuild, 80)
|
|
115
120
|
}
|