@kudzujs/core 0.8.52 → 0.8.55
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 +3 -1
- package/PERFORMANCE.md +13 -1
- package/README.md +1 -1
- package/RELEASES.md +113 -0
- package/docs/next-architecture/README.md +1 -1
- package/docs/next-architecture/compiler-current-architecture.md +8 -8
- package/docs/next-architecture/large-application-ai-native-roadmap.md +3 -3
- package/docs/next-architecture/versioning.md +4 -1
- package/framework/README.md +5 -1
- package/framework/build.mjs +136 -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-codegen.mjs +1 -1
- package/framework/compiler/runtime-family-planner.mjs +48 -0
- package/framework/compiler/source-compiler.mjs +11 -10
- package/framework/compiler/worker-compiler.mjs +23 -4
- package/framework/core.d.ts +2 -0
- package/framework/core.mjs +6 -4
- package/framework/dev-server.mjs +1 -1
- package/framework/navigation-runtime.js +86 -2
- package/package.json +1 -1
package/framework/build.mjs
CHANGED
|
@@ -8,9 +8,11 @@ import { generateListRuntime } from "./compiler/list-runtime-codegen.mjs"
|
|
|
8
8
|
import { assetPath, browserPath, relativeModulePath, withBase } from "./compiler/path-helpers.mjs"
|
|
9
9
|
import { createProjectSession } from "./compiler/project-session.mjs"
|
|
10
10
|
import { createRouteBuildRecord, planRouteArtifacts } from "./compiler/route-build-record.mjs"
|
|
11
|
+
import { createRouteArtifactReport } from "./compiler/route-artifact-report.mjs"
|
|
12
|
+
import { planRuntimeFamilies } from "./compiler/runtime-family-planner.mjs"
|
|
11
13
|
import { createSourceCompiler } from "./compiler/source-compiler.mjs"
|
|
12
14
|
import { createParamCodegen } from "./compiler/param-codegen.mjs"
|
|
13
|
-
import {
|
|
15
|
+
import { usesRouteDependencyRuntime } from "./compiler/route-capability-planner.mjs"
|
|
14
16
|
import { generateBindingRuntime, generateCoreRuntime, generateEffectRuntime, generateNativeRuntime, generateNavigationRuntime, specializeRuntime } from "./compiler/runtime-codegen.mjs"
|
|
15
17
|
import { renderPage } from "./core.mjs"
|
|
16
18
|
import { parseDevHost, parseDevPort, startDevServer } from "./dev-server.mjs"
|
|
@@ -62,9 +64,10 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
62
64
|
const config = await loadConfig(root)
|
|
63
65
|
const base = normalizeBase(config.base)
|
|
64
66
|
const configuredStyles = normalizeStyles(config.styles, base, project)
|
|
67
|
+
const globalStyleUrls = [...new Set(configuredStyles.urls)]
|
|
68
|
+
const globalStyleUrlSet = new Set(globalStyleUrls)
|
|
65
69
|
const publicDirectory = normalizePublicDirectory(config.publicDir, project)
|
|
66
70
|
const navigationGroups = normalizeNavigation(config.navigation)
|
|
67
|
-
const navigationRoutes = navigationGroups.flatMap(group => group.routes)
|
|
68
71
|
const navigationByRoute = new Map(navigationGroups.flatMap(group => group.routes.map(route => [route, group])))
|
|
69
72
|
for (const group of navigationGroups) {
|
|
70
73
|
group.assetPath = assetPath(base, `assets/${group.assetName}`)
|
|
@@ -72,6 +75,7 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
72
75
|
group.layoutId = `l-${group.id}`
|
|
73
76
|
group.records = []
|
|
74
77
|
group.routeRecords = []
|
|
78
|
+
group.buildRecords = []
|
|
75
79
|
group.hasEffects = false
|
|
76
80
|
group.hasParams = false
|
|
77
81
|
}
|
|
@@ -82,7 +86,6 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
82
86
|
const projectFiles = await walk(sourceDirectory)
|
|
83
87
|
const allSourceFiles = projectFiles.filter(file => /\.(?:ts|tsx)$/.test(file) && !file.endsWith(".d.ts")).sort()
|
|
84
88
|
const configuredStyleSources = new Set(configuredStyles.sources.map(style => style.source))
|
|
85
|
-
const discoveredCssFiles = projectFiles.filter(file => file.toLowerCase().endsWith(".css") && !configuredStyleSources.has(file)).sort()
|
|
86
89
|
if (!allSourceFiles.length) throw new Error("No TypeScript files found in src/")
|
|
87
90
|
const allSourceFileSet = new Set(allSourceFiles)
|
|
88
91
|
const sourceIndex = project.sourceIndex
|
|
@@ -93,7 +96,8 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
93
96
|
const sourceFileSet = project.sourceFiles
|
|
94
97
|
for (const file of sourceFiles) sourceFileSet.add(file)
|
|
95
98
|
const staticFiles = await safeStaticFiles(projectFiles)
|
|
96
|
-
const
|
|
99
|
+
const stylesByPage = new Map(pageFiles.map(file => [file, orderSourceStyles([file], sourceFiles, sourceIndex, staticFiles).filter(style => !configuredStyleSources.has(style))]))
|
|
100
|
+
const cssFiles = [...new Set([...stylesByPage.values()].flat())]
|
|
97
101
|
const importedAssets = new Set()
|
|
98
102
|
const { cssModules, cssOutputs } = await prepareSourceStyles(cssFiles, staticFiles, importedAssets, base, project)
|
|
99
103
|
|
|
@@ -114,7 +118,8 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
114
118
|
return effect.workers.map(worker => ({ ...worker, module: assetPath(base, `assets/${result.handlerModule.path}`), handler: handler.exportName }))
|
|
115
119
|
}))
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
let routeRecords = []
|
|
122
|
+
const routeDrafts = []
|
|
118
123
|
const routeEntryTransforms = new Map()
|
|
119
124
|
const routeEntrySources = new Map()
|
|
120
125
|
const routeEntryPaths = new Map()
|
|
@@ -122,13 +127,14 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
122
127
|
const emittedRoutes = new Set()
|
|
123
128
|
const emittedApplicationRoutes = new Set()
|
|
124
129
|
const emittedNavigationRecords = []
|
|
125
|
-
const
|
|
126
|
-
...cssFiles.map(file => assetPath(base, `assets/${relative(sourceDirectory, file).replaceAll(sep, "/")}`)),
|
|
127
|
-
...configuredStyles.urls
|
|
128
|
-
])]
|
|
130
|
+
const navigationAssets = new Map()
|
|
129
131
|
const runtimePlaceholder = `/__kudzu_runtime_${randomUUID()}.js`
|
|
132
|
+
const bindingPlaceholder = `/__kudzu_binding_${randomUUID()}.js`
|
|
133
|
+
const listPlaceholder = `/__kudzu_list_${randomUUID()}.js`
|
|
130
134
|
|
|
131
135
|
for (const pageFile of pageFiles) {
|
|
136
|
+
const sourceStyleUrls = stylesByPage.get(pageFile).map(file => assetPath(base, `assets/${relative(sourceDirectory, file).replaceAll(sep, "/")}`)).filter(url => !globalStyleUrlSet.has(url))
|
|
137
|
+
const styleUrls = [...sourceStyleUrls, ...globalStyleUrls]
|
|
132
138
|
const compiledFile = compiledPath(pageFile)
|
|
133
139
|
const module = await import(`${pathToFileURL(compiledFile).href}?v=${Date.now()}`)
|
|
134
140
|
if (typeof module.default !== "function") throw new Error(`${relative(root, pageFile)} must export a default component`)
|
|
@@ -156,6 +162,7 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
156
162
|
const pageMetadata = await resolveDocumentMetadata(module.metadata, metadataContext, `${relative(root, pageFile)} metadata`)
|
|
157
163
|
const navigationGroup = navigationByRoute.get(applicationRoute)
|
|
158
164
|
const navigable = Boolean(navigationGroup)
|
|
165
|
+
if (navigationGroup) navigationAssets.set(routePath, navigationGroup.assetPath)
|
|
159
166
|
const effectPath = `effects/${route ? `${route}/index` : "index"}.js`
|
|
160
167
|
const nativePath = `native/${route ? `${route}/index` : "index"}.js`
|
|
161
168
|
const paramPath = `params/${route ? `${route}/index` : "index"}.js`
|
|
@@ -179,8 +186,11 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
179
186
|
...configuredMetadata,
|
|
180
187
|
...pageMetadata,
|
|
181
188
|
styles: styleUrls.length ? styleUrls : false,
|
|
189
|
+
managedStyles: navigable ? sourceStyleUrls : [],
|
|
182
190
|
base,
|
|
183
191
|
runtimeAsset: runtimePlaceholder,
|
|
192
|
+
bindingAsset: bindingPlaceholder,
|
|
193
|
+
listAsset: listPlaceholder,
|
|
184
194
|
effectAsset: assetPath(base, `assets/${effectPath}`),
|
|
185
195
|
nativeAsset: assetPath(base, `assets/${nativePath}`),
|
|
186
196
|
paramAsset: assetPath(base, `assets/${paramPath}`),
|
|
@@ -192,30 +202,16 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
192
202
|
navigationGroup.hasParams ||= result.hasParams
|
|
193
203
|
}
|
|
194
204
|
const usesDependencyRuntime = usesRouteDependencyRuntime({ plan: result.plan, navigable, hasBindings: result.hasBindings, hasLists: result.hasLists })
|
|
195
|
-
const routeRuntimeName = usesDependencyRuntime ? "kudzu-deps.js" : "kudzu.js"
|
|
196
205
|
const plan = { route: routePath, ...result.plan }
|
|
197
|
-
const entries = {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
entries.param = entry.path
|
|
202
|
-
html = html.replaceAll(assetPath(base, `assets/${paramPath}`), assetPath(base, `assets/${entry.path}`))
|
|
206
|
+
const entries = {
|
|
207
|
+
...(result.hasParams ? { param: paramPath } : {}),
|
|
208
|
+
...(result.hasEffects ? { effect: effectPath } : {}),
|
|
209
|
+
...(plan.events.some(event => event.native) ? { native: nativePath } : {})
|
|
203
210
|
}
|
|
204
|
-
|
|
205
|
-
const entry = retainRouteEntry(effectPath, output => printEffectEntry(runtimeEffects(plan.effects, navigable), output, handlerModules, join(outputDirectory, "assets"), base, entries.param, routeRuntimeName, navigable), routeEntrySources, routeEntryPaths, outputDirectory)
|
|
206
|
-
entries.effect = entry.path
|
|
207
|
-
html = html.replaceAll(assetPath(base, `assets/${effectPath}`), assetPath(base, `assets/${entry.path}`))
|
|
208
|
-
}
|
|
209
|
-
if (plan.events.some(event => event.native)) {
|
|
210
|
-
const modules = [...new Set(plan.events.filter(event => event.native).map(event => event.native.module))]
|
|
211
|
-
const entry = retainRouteEntry(nativePath, () => printNativeEntrySource(modules, base), routeEntrySources, routeEntryPaths, outputDirectory)
|
|
212
|
-
entries.native = entry.path
|
|
213
|
-
html = html.replaceAll(assetPath(base, `assets/${nativePath}`), assetPath(base, `assets/${entry.path}`))
|
|
214
|
-
}
|
|
215
|
-
routeRecords.push(createRouteBuildRecord({
|
|
211
|
+
const record = createRouteBuildRecord({
|
|
216
212
|
route: routePath,
|
|
217
213
|
output: route,
|
|
218
|
-
html,
|
|
214
|
+
html: inlineQueryFormCarry(result.html, plan),
|
|
219
215
|
plan,
|
|
220
216
|
handlerReferences: result.handlerReferences,
|
|
221
217
|
styles: styleUrls,
|
|
@@ -232,7 +228,10 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
232
228
|
},
|
|
233
229
|
entries,
|
|
234
230
|
runtimeSchema
|
|
235
|
-
})
|
|
231
|
+
})
|
|
232
|
+
routeRecords.push(record)
|
|
233
|
+
if (navigationGroup) navigationGroup.buildRecords.push(record)
|
|
234
|
+
routeDrafts.push({ record, result, runtimeSchema, navigationGroup, effectPath, nativePath, paramPath })
|
|
236
235
|
}
|
|
237
236
|
}
|
|
238
237
|
|
|
@@ -244,12 +243,65 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
244
243
|
group.records.sort((left, right) => (right.segments?.filter(segment => segment !== null).length ?? 0) - (left.segments?.filter(segment => segment !== null).length ?? 0) || left.id.localeCompare(right.id))
|
|
245
244
|
}
|
|
246
245
|
|
|
246
|
+
const runtimePlan = planRuntimeFamilies(routeRecords, navigationGroups)
|
|
247
|
+
for (const group of navigationGroups) {
|
|
248
|
+
group.runtimeFamily = runtimePlan.familyByRecord.get(group.buildRecords[0])
|
|
249
|
+
group.assetPath = assetPath(base, `assets/runtime/${group.runtimeFamily.id}/${group.assetName}`)
|
|
250
|
+
}
|
|
251
|
+
const runtimeFamilyByRecord = new Map()
|
|
252
|
+
routeRecords = routeDrafts.map(draft => {
|
|
253
|
+
const { record, result, runtimeSchema, navigationGroup, effectPath, nativePath, paramPath } = draft
|
|
254
|
+
const family = runtimePlan.familyByRecord.get(record)
|
|
255
|
+
if (record.capabilities.hasBehaviors && !family) throw new Error(`Interactive route has no runtime family: ${record.route}`)
|
|
256
|
+
const runtimeDirectory = family ? join(outputDirectory, "assets", "runtime", family.id) : undefined
|
|
257
|
+
const routeRuntimeName = record.capabilities.usesDependencyRuntime ? "kudzu-deps.js" : "kudzu.js"
|
|
258
|
+
const entries = {}
|
|
259
|
+
let html = record.html
|
|
260
|
+
if (record.capabilities.hasParams) {
|
|
261
|
+
const entry = retainRouteEntry(paramPath, output => printParamEntry(runtimeSchema, record.plan.params, record.plan.searchParams, record.plan.searchParamsWritable, output, runtimeDirectory, base, routeRuntimeName, record.capabilities.navigable), routeEntrySources, routeEntryPaths, outputDirectory)
|
|
262
|
+
entries.param = entry.path
|
|
263
|
+
html = html.replaceAll(assetPath(base, `assets/${paramPath}`), assetPath(base, `assets/${entry.path}`))
|
|
264
|
+
}
|
|
265
|
+
if (record.capabilities.hasEffects) {
|
|
266
|
+
const entry = retainRouteEntry(effectPath, output => printEffectEntry(runtimeEffects(record.plan.effects, record.capabilities.navigable), output, handlerModules, join(outputDirectory, "assets"), base, entries.param, routeRuntimeName, record.capabilities.navigable, runtimeDirectory), routeEntrySources, routeEntryPaths, outputDirectory)
|
|
267
|
+
entries.effect = entry.path
|
|
268
|
+
html = html.replaceAll(assetPath(base, `assets/${effectPath}`), assetPath(base, `assets/${entry.path}`))
|
|
269
|
+
}
|
|
270
|
+
if (record.plan.events.some(event => event.native)) {
|
|
271
|
+
const modules = [...new Set(record.plan.events.filter(event => event.native).map(event => event.native.module))]
|
|
272
|
+
const nativeRuntime = assetPath(base, `assets/runtime/${family.id}/kudzu-native.js`)
|
|
273
|
+
const entry = retainRouteEntry(nativePath, () => printNativeEntrySource(modules, nativeRuntime), routeEntrySources, routeEntryPaths, outputDirectory)
|
|
274
|
+
entries.native = entry.path
|
|
275
|
+
html = html.replaceAll(assetPath(base, `assets/${nativePath}`), assetPath(base, `assets/${entry.path}`))
|
|
276
|
+
}
|
|
277
|
+
if (family) {
|
|
278
|
+
html = html.replaceAll(runtimePlaceholder, escapeAttribute(assetPath(base, `assets/runtime/${family.id}/${routeRuntimeName}`)))
|
|
279
|
+
html = html.replaceAll(bindingPlaceholder, escapeAttribute(assetPath(base, `assets/runtime/${family.id}/kudzu-binding.js`)))
|
|
280
|
+
html = html.replaceAll(listPlaceholder, escapeAttribute(assetPath(base, `assets/runtime/${family.id}/kudzu-list.js`)))
|
|
281
|
+
}
|
|
282
|
+
if (navigationGroup) html = html.replaceAll(escapeAttribute(navigationAssets.get(record.route)), escapeAttribute(navigationGroup.assetPath))
|
|
283
|
+
const finalRecord = createRouteBuildRecord({
|
|
284
|
+
route: record.route,
|
|
285
|
+
output: record.output,
|
|
286
|
+
html,
|
|
287
|
+
plan: record.plan,
|
|
288
|
+
handlerReferences: result.handlerReferences,
|
|
289
|
+
styles: record.artifacts.styles,
|
|
290
|
+
capabilities: record.capabilities,
|
|
291
|
+
entries,
|
|
292
|
+
runtimeSchema
|
|
293
|
+
})
|
|
294
|
+
if (family) runtimeFamilyByRecord.set(finalRecord, family)
|
|
295
|
+
if (navigationGroup) navigationAssets.set(finalRecord.route, navigationGroup.assetPath)
|
|
296
|
+
return finalRecord
|
|
297
|
+
})
|
|
298
|
+
|
|
247
299
|
const assetsDirectory = join(outputDirectory, "assets")
|
|
248
300
|
await mkdir(assetsDirectory, { recursive: true })
|
|
249
301
|
const { handlerModules: emittedHandlerModules, workerReferences: renderedWorkerReferences, styles: renderedStyles } = planRouteArtifacts(routeRecords, handlerModules, workerReferences, module => assetPath(base, `assets/${module.path}`))
|
|
250
302
|
const renderedStyleUrls = new Set(renderedStyles)
|
|
251
303
|
if (renderedWorkerReferences.length && await exists(join(publicDirectory, "assets", "workers"))) throw new Error("public/assets/workers collides with Kudzu's generated Worker asset namespace")
|
|
252
|
-
const workerAssets = await project.workerCompiler.emit(renderedWorkerReferences, sourceFileSet, assetsDirectory, base, minify)
|
|
304
|
+
const { assets: workerAssets, outputs: workerOutputs } = await project.workerCompiler.emit(renderedWorkerReferences, sourceFileSet, assetsDirectory, base, minify)
|
|
253
305
|
for (const module of emittedHandlerModules) {
|
|
254
306
|
for (const reference of workerReferences) {
|
|
255
307
|
if (reference.module !== assetPath(base, `assets/${module.path}`)) continue
|
|
@@ -259,63 +311,55 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
259
311
|
if (module.code.includes("/__kudzu_worker_")) throw new Error(`Worker URL placeholder survived in ${module.path}`)
|
|
260
312
|
}
|
|
261
313
|
const plans = routeRecords.map(record => record.plan)
|
|
262
|
-
const
|
|
263
|
-
const {
|
|
264
|
-
routes: { behaviors: behaviorCount, regularBehaviors: regularBehaviorCount, dependencyStateSeeds: dependencyStateSeedCount },
|
|
265
|
-
events: { command: commandEvents, hasNativeHandlers },
|
|
266
|
-
bindings: { count: bindingCount },
|
|
267
|
-
lists,
|
|
268
|
-
effects: { any: hasEffects, derivedDependencies: hasDerivedEffectDependencies, captures: hasEffectCaptures },
|
|
269
|
-
captures: { nestedState: hasNestedStateCaptures, setter: hasSetterCaptures },
|
|
270
|
-
runtime: { shared: hasSharedRuntime, dependency: hasDependencyRuntime }
|
|
271
|
-
} = capabilityIR
|
|
272
|
-
const runtimeName = usesDependencyRuntime => usesDependencyRuntime ? "kudzu-deps.js" : "kudzu.js"
|
|
314
|
+
const behaviorCount = routeRecords.filter(record => record.capabilities.hasBehaviors).length
|
|
273
315
|
for (let offset = 0; offset < routeRecords.length; offset += 64) {
|
|
274
316
|
await Promise.all(routeRecords.slice(offset, offset + 64).map(async record => {
|
|
275
317
|
const routeDirectory = join(outputDirectory, record.output)
|
|
276
318
|
await mkdir(routeDirectory, { recursive: true })
|
|
277
|
-
|
|
278
|
-
await writeFile(join(routeDirectory, "index.html"), html)
|
|
319
|
+
if ([runtimePlaceholder, bindingPlaceholder, listPlaceholder].some(placeholder => record.html.includes(placeholder))) throw new Error(`Runtime family placeholder survived in ${record.route}`)
|
|
320
|
+
await writeFile(join(routeDirectory, "index.html"), preloadModules(record.html))
|
|
279
321
|
}))
|
|
280
322
|
}
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
await
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
323
|
+
for (const family of runtimePlan.families) {
|
|
324
|
+
const capabilityIR = family.capability
|
|
325
|
+
const { bindings, captures, effects, events, lists, routes, runtime } = capabilityIR
|
|
326
|
+
const familyDirectory = join(assetsDirectory, "runtime", family.id)
|
|
327
|
+
await mkdir(familyDirectory, { recursive: true })
|
|
328
|
+
if (runtime.dependency) {
|
|
329
|
+
const source = await readFile(new URL("./dependency-runtime.js", import.meta.url), "utf8")
|
|
330
|
+
await writeJavaScript(join(familyDirectory, "kudzu-deps.js"), specializeRuntime(source, events.command, routes.dependencyStateSeeds > 0), minify)
|
|
331
|
+
} else {
|
|
332
|
+
const source = await readFile(new URL(runtime.shared ? "./shared-runtime.js" : "./runtime.js", import.meta.url), "utf8")
|
|
333
|
+
await writeJavaScript(join(familyDirectory, "kudzu.js"), generateCoreRuntime(source, capabilityIR), minify)
|
|
334
|
+
}
|
|
335
|
+
if (bindings.count || events.hasNativeHandlers || effects.captures) await writeJavaScript(join(familyDirectory, "kudzu-serialization.js"), await readFile(new URL("./serialization.js", import.meta.url), "utf8"), minify, {
|
|
336
|
+
"globalThis.__KUDZU_CAPTURE_STATE__": String(captures.nestedState),
|
|
337
|
+
"globalThis.__KUDZU_CAPTURE_SETTER__": String(captures.setter)
|
|
338
|
+
})
|
|
339
|
+
if (effects.any) {
|
|
340
|
+
const generated = generateEffectRuntime(await readFile(new URL("./effect-runtime.js", import.meta.url), "utf8"), capabilityIR)
|
|
341
|
+
await writeBundledJavaScript(join(familyDirectory, "kudzu-effect.js"), generated.source, minify, generated.define)
|
|
342
|
+
}
|
|
343
|
+
if (bindings.count || lists.styleCount) await writeJavaScript(join(familyDirectory, "kudzu-style.js"), await readFile(new URL("./style.js", import.meta.url), "utf8"), minify)
|
|
344
|
+
if (bindings.count) {
|
|
345
|
+
const generated = generateBindingRuntime(await readFile(new URL("./binding-runtime.js", import.meta.url), "utf8"), capabilityIR, family.navigation)
|
|
346
|
+
await writeBundledJavaScript(join(familyDirectory, "kudzu-binding.js"), generated.source, minify, generated.define)
|
|
347
|
+
}
|
|
348
|
+
if (effects.derivedDependencies || lists.selectors) await writeJavaScript(join(familyDirectory, "kudzu-collection-selector.js"), await readFile(new URL("./collection-selector.js", import.meta.url), "utf8"), minify)
|
|
349
|
+
if (lists.count) {
|
|
350
|
+
const generated = generateListRuntime(await readFile(new URL("./list-runtime.js", import.meta.url), "utf8"), capabilityIR)
|
|
351
|
+
await writeBundledJavaScript(join(familyDirectory, "kudzu-list.js"), generated.source, minify, generated.define)
|
|
352
|
+
}
|
|
353
|
+
if (events.hasNativeHandlers) {
|
|
354
|
+
const generated = generateNativeRuntime(await readFile(new URL("./native-runtime.js", import.meta.url), "utf8"), capabilityIR)
|
|
355
|
+
await writeJavaScript(join(familyDirectory, "kudzu-native.js"), generated.source, minify, generated.define)
|
|
356
|
+
}
|
|
314
357
|
}
|
|
358
|
+
for (const [path, source] of routeEntrySources) if (path.startsWith("native/")) await writeRetainedRouteEntry(path, source, assetsDirectory, minify, routeEntryTransforms)
|
|
315
359
|
if (navigationGroups.length) {
|
|
316
360
|
const navigationSource = await readFile(new URL("./navigation-runtime.js", import.meta.url), "utf8")
|
|
317
361
|
for (const group of navigationGroups) {
|
|
318
|
-
await writeJavaScript(join(assetsDirectory, group.assetName), generateNavigationRuntime(navigationSource, group), minify)
|
|
362
|
+
await writeJavaScript(join(assetsDirectory, "runtime", group.runtimeFamily.id, group.assetName), generateNavigationRuntime(navigationSource, group), minify)
|
|
319
363
|
}
|
|
320
364
|
}
|
|
321
365
|
for (const handlerModule of emittedHandlerModules) {
|
|
@@ -332,8 +376,9 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
332
376
|
await mkdir(dirname(output), { recursive: true })
|
|
333
377
|
await writeJavaScript(output, module.code, minify)
|
|
334
378
|
}
|
|
379
|
+
let handlerMetafile
|
|
335
380
|
if (clientModules.length || emittedHandlerModules.some(module => module.hasPackageImports)) {
|
|
336
|
-
await bundle({
|
|
381
|
+
const result = await bundle({
|
|
337
382
|
entryPoints: emittedHandlerModules.map(module => join(assetsDirectory, module.path)),
|
|
338
383
|
outbase: join(assetsDirectory, "handlers"),
|
|
339
384
|
outdir: join(assetsDirectory, "handlers"),
|
|
@@ -346,22 +391,29 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
346
391
|
target: "es2022",
|
|
347
392
|
minify,
|
|
348
393
|
legalComments: "none",
|
|
394
|
+
metafile: true,
|
|
349
395
|
logLevel: "silent"
|
|
350
396
|
})
|
|
397
|
+
handlerMetafile = result.metafile
|
|
351
398
|
await rm(join(assetsDirectory, "modules"), { recursive: true, force: true })
|
|
352
399
|
}
|
|
353
400
|
const sortedRewrites = rewrites.sort((left, right) => runtimeSpecificity(right) - runtimeSpecificity(left) || left.pattern.localeCompare(right.pattern))
|
|
354
401
|
await writeFile(join(workDirectory, "kudzu-plan.json"), JSON.stringify({ routes: plans, rewrites: sortedRewrites }, null, 2))
|
|
402
|
+
const artifacts = createRouteArtifactReport(routeRecords, { base, handlerMetafile, outputDirectory, navigationAssets, runtimeFamilies: runtimePlan.families, runtimeFamilyByRecord, workerReferences: renderedWorkerReferences, workerOutputs })
|
|
403
|
+
await writeFile(join(workDirectory, "kudzu-artifacts.json"), JSON.stringify(artifacts, null, 2))
|
|
404
|
+
const emittedCssFiles = new Set()
|
|
355
405
|
for (const file of cssFiles.filter(file => renderedStyleUrls.has(assetPath(base, `assets/${relative(sourceDirectory, file).replaceAll(sep, "/")}`)))) {
|
|
356
406
|
const output = join(assetsDirectory, relative(sourceDirectory, file))
|
|
357
407
|
await mkdir(dirname(output), { recursive: true })
|
|
358
408
|
await writeFile(output, cssOutputs.get(file))
|
|
409
|
+
emittedCssFiles.add(file)
|
|
359
410
|
}
|
|
360
411
|
for (const file of [...importedAssets].sort()) {
|
|
361
|
-
if (cssOutputs.has(file)) continue
|
|
362
412
|
const output = join(assetsDirectory, relative(sourceDirectory, file))
|
|
363
413
|
await mkdir(dirname(output), { recursive: true })
|
|
364
|
-
|
|
414
|
+
if (cssOutputs.has(file)) {
|
|
415
|
+
if (!emittedCssFiles.has(file)) await writeFile(output, cssOutputs.get(file))
|
|
416
|
+
} else await writeFile(output, await readFile(file))
|
|
365
417
|
}
|
|
366
418
|
for (const style of configuredStyles.sources.filter(style => renderedStyleUrls.has(withBase(base, style.output)))) {
|
|
367
419
|
let css = await readFile(style.source, "utf8")
|
|
@@ -380,7 +432,7 @@ async function buildInto(project, outputDirectory, { minify }) {
|
|
|
380
432
|
}
|
|
381
433
|
if (config.afterBuild !== undefined) {
|
|
382
434
|
if (typeof config.afterBuild !== "function") throw new Error("kudzu.config afterBuild must be a function")
|
|
383
|
-
await config.afterBuild({ root, outDir: outputDirectory, sourceDir: sourceDirectory, base, routes: plans.map(plan => plan.route), plans, rewrites: sortedRewrites })
|
|
435
|
+
await config.afterBuild({ root, outDir: outputDirectory, sourceDir: sourceDirectory, base, routes: plans.map(plan => plan.route), plans, rewrites: sortedRewrites, artifacts })
|
|
384
436
|
}
|
|
385
437
|
|
|
386
438
|
return { result: { sourceResults }, pageCount: plans.length, behaviorCount }
|
|
@@ -485,10 +537,9 @@ function inlineQueryFormCarry(html, plan) {
|
|
|
485
537
|
return html.replace("</body>", `${script}</body>`)
|
|
486
538
|
}
|
|
487
539
|
|
|
488
|
-
function printNativeEntrySource(modules,
|
|
540
|
+
function printNativeEntrySource(modules, runtime) {
|
|
489
541
|
const imports = modules.map((module, index) => `import * as __kNativeModule${index} from ${JSON.stringify(module)}`).join("\n")
|
|
490
542
|
const registrations = modules.map((module, index) => `[${JSON.stringify(module)}, __kNativeModule${index}]`).join(",")
|
|
491
|
-
const runtime = assetPath(base, "assets/kudzu-native.js")
|
|
492
543
|
return `import { registerNativeModules } from ${JSON.stringify(runtime)}\n${imports}\nregisterNativeModules([${registrations}])`
|
|
493
544
|
}
|
|
494
545
|
|
|
@@ -541,7 +592,7 @@ async function writeBundledJavaScript(file, source, minify, define) {
|
|
|
541
592
|
stdin: { contents: source, resolveDir: dirname(file), sourcefile: file },
|
|
542
593
|
bundle: true,
|
|
543
594
|
write: false,
|
|
544
|
-
external: ["./kudzu.js", "./kudzu-binding.js", "./kudzu-serialization.js", "./kudzu-style.js"],
|
|
595
|
+
external: ["./kudzu.js", "./kudzu-binding.js", "./kudzu-collection-selector.js", "./kudzu-serialization.js", "./kudzu-style.js"],
|
|
545
596
|
define,
|
|
546
597
|
format: "esm",
|
|
547
598
|
target: "es2022",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path"
|
|
2
2
|
|
|
3
3
|
export function createEffectCodegen({ assetPath, inlineJson, relativeModulePath }) {
|
|
4
|
-
function printEffectEntry(effects, output, handlerModules, assetsDirectory, base, paramPath, runtimeName) {
|
|
4
|
+
function printEffectEntry(effects, output, handlerModules, assetsDirectory, runtimeDirectory, base, paramPath, runtimeName) {
|
|
5
5
|
const hasCleanup = effects.some(effect => effect.cleanup)
|
|
6
6
|
const hasDependencies = effects.some(effect => effect.dependencies?.length || effect.itemDependencies?.length)
|
|
7
7
|
const hasOwners = effects.some(effect => effect.owner)
|
|
@@ -14,10 +14,10 @@ function printEffectEntry(effects, output, handlerModules, assetsDirectory, base
|
|
|
14
14
|
})
|
|
15
15
|
const imports = [
|
|
16
16
|
hasCleanup || hasDependencies || hasOwners
|
|
17
|
-
? `import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(
|
|
18
|
-
: `import { browserState, commitDom } from ${JSON.stringify(relativeModulePath(output, join(
|
|
19
|
-
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(
|
|
20
|
-
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(
|
|
17
|
+
? `import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, runtimeName)))}\nconst { browserState, commitDom } = __kRuntime`
|
|
18
|
+
: `import { browserState, commitDom } from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, runtimeName)))}`,
|
|
19
|
+
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, "kudzu-effect.js")))}`,
|
|
20
|
+
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, "kudzu-collection-selector.js")))}`] : []),
|
|
21
21
|
...(paramPath ? [`import ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, paramPath)))}`] : []),
|
|
22
22
|
...modules.map((module, index) => `import * as __kEffectModule${index} from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, module.path)))}`)
|
|
23
23
|
]
|
|
@@ -178,7 +178,7 @@ addEventListener("pagehide", event => {
|
|
|
178
178
|
})`
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
function printNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base) {
|
|
181
|
+
function printNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, runtimeDirectory, base) {
|
|
182
182
|
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
183
183
|
const moduleUrls = [...new Set(effects.map(effect => effect.module))]
|
|
184
184
|
const modules = moduleUrls.map(url => {
|
|
@@ -187,9 +187,9 @@ function printNavigableEffectEntry(effects, output, handlerModules, assetsDirect
|
|
|
187
187
|
return module
|
|
188
188
|
})
|
|
189
189
|
const imports = [
|
|
190
|
-
`import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(
|
|
191
|
-
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(
|
|
192
|
-
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(
|
|
190
|
+
`import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, "kudzu.js")))}`,
|
|
191
|
+
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, "kudzu-effect.js")))}`,
|
|
192
|
+
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, "kudzu-collection-selector.js")))}`] : []),
|
|
193
193
|
...modules.map((module, index) => `import * as __kEffectModule${index} from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, module.path)))}`)
|
|
194
194
|
]
|
|
195
195
|
const entries = moduleUrls.map((url, index) => `[${JSON.stringify(url)}, __kEffectModule${index}]`).join(",")
|
|
@@ -300,7 +300,7 @@ ${hasDependencyExpressions ? printDerivedDependencyRead("__kRuntime.browserState
|
|
|
300
300
|
}`
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
function printOwnedNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base) {
|
|
303
|
+
function printOwnedNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, runtimeDirectory, base) {
|
|
304
304
|
const hasItemDependencies = effects.some(effect => effect.itemDependencies?.length)
|
|
305
305
|
const hasDependencyExpressions = effects.some(effect => effect.dependencyExpressions?.length)
|
|
306
306
|
const moduleUrls = [...new Set(effects.map(effect => effect.module))]
|
|
@@ -310,9 +310,9 @@ function printOwnedNavigableEffectEntry(effects, output, handlerModules, assetsD
|
|
|
310
310
|
return module
|
|
311
311
|
})
|
|
312
312
|
const imports = [
|
|
313
|
-
`import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(
|
|
314
|
-
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(
|
|
315
|
-
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(
|
|
313
|
+
`import * as __kRuntime from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, "kudzu.js")))}`,
|
|
314
|
+
`import { createEffectContext } from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, "kudzu-effect.js")))}`,
|
|
315
|
+
...(hasDependencyExpressions ? [`import { evaluateCollectionExpression as __kEvaluateDependency } from ${JSON.stringify(relativeModulePath(output, join(runtimeDirectory, "kudzu-collection-selector.js")))}`] : []),
|
|
316
316
|
...modules.map((module, index) => `import * as __kEffectModule${index} from ${JSON.stringify(relativeModulePath(output, join(assetsDirectory, module.path)))}`)
|
|
317
317
|
]
|
|
318
318
|
const entries = moduleUrls.map((url, index) => `[${JSON.stringify(url)}, __kEffectModule${index}]`).join(",")
|
|
@@ -876,9 +876,9 @@ async function invokeCleanup() {
|
|
|
876
876
|
}${disposal}`
|
|
877
877
|
}
|
|
878
878
|
|
|
879
|
-
return (effects, output, handlerModules, assetsDirectory, base, paramPath, runtimeName, navigable) => navigable
|
|
879
|
+
return (effects, output, handlerModules, assetsDirectory, base, paramPath, runtimeName, navigable, runtimeDirectory = assetsDirectory) => navigable
|
|
880
880
|
? effects.some(effect => effect.owner)
|
|
881
|
-
? printOwnedNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base)
|
|
882
|
-
: printNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, base)
|
|
883
|
-
: printEffectEntry(effects, output, handlerModules, assetsDirectory, base, paramPath, runtimeName)
|
|
881
|
+
? printOwnedNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, runtimeDirectory, base)
|
|
882
|
+
: printNavigableEffectEntry(effects, output, handlerModules, assetsDirectory, runtimeDirectory, base)
|
|
883
|
+
: printEffectEntry(effects, output, handlerModules, assetsDirectory, runtimeDirectory, base, paramPath, runtimeName)
|
|
884
884
|
}
|
|
@@ -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
|
+
}
|