@kudzujs/core 0.8.21 → 0.8.23
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 +20 -0
- package/PERFORMANCE.md +48 -0
- package/README.md +1 -1
- package/RELEASES.md +71 -0
- package/docs/next-architecture/README.md +3 -3
- package/docs/next-architecture/compiler-current-architecture.md +25 -25
- package/docs/next-architecture/goal-a-compiler-foundation.md +13 -16
- package/docs/next-architecture/versioning.md +4 -3
- package/framework/README.md +5 -2
- package/framework/build.mjs +234 -3489
- package/framework/compiler/list-runtime-codegen.mjs +95 -0
- package/framework/compiler/param-codegen.mjs +72 -0
- package/framework/compiler/path-helpers.mjs +18 -0
- package/framework/compiler/route-capability-planner.mjs +35 -0
- package/framework/compiler/runtime-codegen.mjs +146 -0
- package/framework/compiler/source-compiler.mjs +2969 -0
- package/framework/compiler/source-graph.mjs +29 -0
- package/framework/compiler/worker-compiler.mjs +9 -2
- package/framework/core.d.ts +41 -38
- package/framework/core.mjs +2 -1
- package/framework/dev-server.mjs +1 -8
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { dirname, extname, join, relative, resolve } from "node:path"
|
|
2
|
+
import ts from "typescript"
|
|
3
|
+
|
|
4
|
+
const root = process.cwd()
|
|
5
|
+
|
|
6
|
+
export function resolveSourceImport(importer, specifier, sourceFiles) {
|
|
7
|
+
const base = resolve(dirname(importer), specifier)
|
|
8
|
+
const extension = extname(base)
|
|
9
|
+
const stem = /\.(?:js|jsx|ts|tsx)$/.test(extension) ? base.slice(0, -extension.length) : base
|
|
10
|
+
const candidates = extension === ".ts" || extension === ".tsx"
|
|
11
|
+
? [base]
|
|
12
|
+
: [`${stem}.ts`, `${stem}.tsx`, join(stem, "index.ts"), join(stem, "index.tsx")]
|
|
13
|
+
const matches = candidates.filter(candidate => sourceFiles.has(candidate))
|
|
14
|
+
if (matches.length !== 1) throw new Error(`${relative(root, importer)} Relative import ${JSON.stringify(specifier)} must resolve to one TypeScript file in src/`)
|
|
15
|
+
return matches[0]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function runtimeModuleReference(node) {
|
|
19
|
+
if (ts.isExportDeclaration(node)) return !node.isTypeOnly && (!node.exportClause || !ts.isNamedExports(node.exportClause) || node.exportClause.elements.some(entry => !entry.isTypeOnly))
|
|
20
|
+
const clause = node.importClause
|
|
21
|
+
if (!clause) return true
|
|
22
|
+
if (clause.isTypeOnly) return false
|
|
23
|
+
if (clause.name || clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) return true
|
|
24
|
+
return clause.namedBindings?.elements.some(entry => !entry.isTypeOnly) ?? false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function parseSourceFile(file, source) {
|
|
28
|
+
return ts.createSourceFile(file, source, ts.ScriptTarget.ES2022, true, file.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS)
|
|
29
|
+
}
|
|
@@ -4,11 +4,12 @@ import { dirname, relative, resolve, sep } from "node:path"
|
|
|
4
4
|
import { build as bundle } from "esbuild"
|
|
5
5
|
import ts from "typescript"
|
|
6
6
|
import { containsJsx, isUnshadowedGlobal, nearestFunction, sourceNodeError } from "./ast-helpers.mjs"
|
|
7
|
+
import { assetPath } from "./path-helpers.mjs"
|
|
8
|
+
import { parseSourceFile, resolveSourceImport, runtimeModuleReference } from "./source-graph.mjs"
|
|
7
9
|
|
|
8
10
|
export function createWorkerCompiler({
|
|
9
11
|
root,
|
|
10
12
|
sourceDirectory,
|
|
11
|
-
outputDirectory,
|
|
12
13
|
assetPath,
|
|
13
14
|
parseSourceFile,
|
|
14
15
|
resolveSourceImport,
|
|
@@ -158,7 +159,7 @@ export function createWorkerCompiler({
|
|
|
158
159
|
const entry = resolve(root, metadata.entryPoint)
|
|
159
160
|
const rootReferences = references.filter(reference => resolve(sourceDirectory, reference.root) === entry)
|
|
160
161
|
const outputFile = resolve(root, output)
|
|
161
|
-
const url = assetPath(base, relative(
|
|
162
|
+
const url = assetPath(base, relative(resolve(assetsDirectory, ".."), outputFile).replaceAll(sep, "/"))
|
|
162
163
|
for (const reference of rootReferences) emitted.set(reference.placeholder, url)
|
|
163
164
|
}
|
|
164
165
|
for (const reference of references) if (!emitted.has(reference.placeholder)) throw new Error(`Worker entry was not emitted: ${reference.root}`)
|
|
@@ -167,3 +168,9 @@ export function createWorkerCompiler({
|
|
|
167
168
|
|
|
168
169
|
return { candidate, emit, rejectConstructions, rejectOrdinaryImports, rewriteEffect }
|
|
169
170
|
}
|
|
171
|
+
|
|
172
|
+
export function emitWorkers(references, sourceFiles, assetsDirectory, base, minify) {
|
|
173
|
+
const root = process.cwd()
|
|
174
|
+
const sourceDirectory = resolve(root, "src")
|
|
175
|
+
return createWorkerCompiler({ root, sourceDirectory, assetPath, parseSourceFile, resolveSourceImport, runtimeModuleReference }).emit(references, sourceFiles, assetsDirectory, base, minify)
|
|
176
|
+
}
|
package/framework/core.d.ts
CHANGED
|
@@ -131,6 +131,46 @@ export type ListDescriptor = {
|
|
|
131
131
|
valueSeed?: Record<string, string | number | boolean | null>
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
export interface RouteIR {
|
|
135
|
+
version: 1
|
|
136
|
+
states: Array<{ slot: number; id: string; name: string; initialValue: unknown; lifetime?: "layout" | "route"; internal?: true }>
|
|
137
|
+
params: Array<{ name: string; id: string }>
|
|
138
|
+
searchParams: Array<{ name: string; id: string }>
|
|
139
|
+
searchParamsWritable: boolean
|
|
140
|
+
events: Array<{
|
|
141
|
+
event: string
|
|
142
|
+
commands?: Array<[string, string, unknown]>
|
|
143
|
+
native?: { module: string; handler: string; states: Record<string, string>; scope: Record<string, unknown> }
|
|
144
|
+
}>
|
|
145
|
+
effects: Array<{
|
|
146
|
+
module: string
|
|
147
|
+
handler: string
|
|
148
|
+
states: Record<string, string>
|
|
149
|
+
scope: Record<string, unknown>
|
|
150
|
+
lifetime?: "layout" | "route"
|
|
151
|
+
dependencies?: string[]
|
|
152
|
+
dependencyExpressions?: unknown[]
|
|
153
|
+
dependencyStates?: Record<string, string>
|
|
154
|
+
itemDependencies?: string[]
|
|
155
|
+
listState?: string
|
|
156
|
+
cleanup?: true
|
|
157
|
+
owner?: string
|
|
158
|
+
list?: true
|
|
159
|
+
}>
|
|
160
|
+
bindings: Array<{
|
|
161
|
+
target: string
|
|
162
|
+
state?: string
|
|
163
|
+
module?: string
|
|
164
|
+
handler?: string
|
|
165
|
+
states?: Record<string, string>
|
|
166
|
+
scope?: Record<string, unknown>
|
|
167
|
+
scopeStates?: Record<string, string>
|
|
168
|
+
scopeBindings?: Record<string, unknown>
|
|
169
|
+
}>
|
|
170
|
+
conditions: Array<Record<string, unknown>>
|
|
171
|
+
lists: ListDescriptor[]
|
|
172
|
+
}
|
|
173
|
+
|
|
134
174
|
export function renderPage<Props = Record<string, never>>(
|
|
135
175
|
component: (props: Props) => unknown | Promise<unknown>,
|
|
136
176
|
metadata?: PageMetadata,
|
|
@@ -146,42 +186,5 @@ export function renderPage<Props = Record<string, never>>(
|
|
|
146
186
|
hasListStyles: boolean
|
|
147
187
|
hasStateSeed: boolean
|
|
148
188
|
handlerModules: string[]
|
|
149
|
-
plan:
|
|
150
|
-
states: Array<{ id: string; name: string; initialValue: unknown; lifetime?: "layout" | "route"; internal?: true }>
|
|
151
|
-
params: Array<{ name: string; id: string }>
|
|
152
|
-
searchParams: Array<{ name: string; id: string }>
|
|
153
|
-
searchParamsWritable: boolean
|
|
154
|
-
events: Array<{
|
|
155
|
-
event: string
|
|
156
|
-
commands?: Array<[string, string, unknown]>
|
|
157
|
-
native?: { module: string; handler: string; states: Record<string, string>; scope: Record<string, unknown> }
|
|
158
|
-
}>
|
|
159
|
-
effects: Array<{
|
|
160
|
-
module: string
|
|
161
|
-
handler: string
|
|
162
|
-
states: Record<string, string>
|
|
163
|
-
scope: Record<string, unknown>
|
|
164
|
-
lifetime?: "layout" | "route"
|
|
165
|
-
dependencies?: string[]
|
|
166
|
-
dependencyExpressions?: unknown[]
|
|
167
|
-
dependencyStates?: Record<string, string>
|
|
168
|
-
itemDependencies?: string[]
|
|
169
|
-
listState?: string
|
|
170
|
-
cleanup?: true
|
|
171
|
-
owner?: string
|
|
172
|
-
list?: true
|
|
173
|
-
}>
|
|
174
|
-
bindings: Array<{
|
|
175
|
-
target: string
|
|
176
|
-
state?: string
|
|
177
|
-
module?: string
|
|
178
|
-
handler?: string
|
|
179
|
-
states?: Record<string, string>
|
|
180
|
-
scope?: Record<string, unknown>
|
|
181
|
-
scopeStates?: Record<string, string>
|
|
182
|
-
scopeBindings?: Record<string, unknown>
|
|
183
|
-
}>
|
|
184
|
-
conditions: Array<Record<string, unknown>>
|
|
185
|
-
lists: ListDescriptor[]
|
|
186
|
-
}
|
|
189
|
+
plan: RouteIR
|
|
187
190
|
}>
|
package/framework/core.mjs
CHANGED
|
@@ -527,7 +527,8 @@ export async function renderPage(component, metadata = {}, props = {}, layout) {
|
|
|
527
527
|
hasStateSeed: initialState.length > 0,
|
|
528
528
|
handlerModules: [...renderContext.handlerModules],
|
|
529
529
|
plan: {
|
|
530
|
-
|
|
530
|
+
version: 1,
|
|
531
|
+
states: Object.entries(renderContext.states).map(([id, state], slot) => ({ slot, id, ...state })),
|
|
531
532
|
params: renderContext.paramEntries,
|
|
532
533
|
searchParams: renderContext.searchParamEntries,
|
|
533
534
|
searchParamsWritable: renderContext.searchParamsWritable,
|
package/framework/dev-server.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto"
|
|
|
2
2
|
import { readFile, stat, watch } from "node:fs/promises"
|
|
3
3
|
import { createServer } from "node:http"
|
|
4
4
|
import { extname, join, resolve, sep } from "node:path"
|
|
5
|
+
import { browserPath, withBase } from "./compiler/path-helpers.mjs"
|
|
5
6
|
import { stateSchema } from "./dev-state.js"
|
|
6
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(?:-(?: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>`
|
|
@@ -216,14 +217,6 @@ function escapeHtml(value) {
|
|
|
216
217
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
|
|
217
218
|
}
|
|
218
219
|
|
|
219
|
-
function browserPath(path) {
|
|
220
|
-
return path ? new URL(path, "http://kudzu.local").pathname : ""
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function withBase(base, path) {
|
|
224
|
-
return base ? `${base}${path}` : path
|
|
225
|
-
}
|
|
226
|
-
|
|
227
220
|
async function exists(path) {
|
|
228
221
|
try {
|
|
229
222
|
await stat(path)
|