@kudzujs/core 0.6.19 → 0.6.20
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 +1 -1
- package/framework/build.mjs +51 -9
- package/framework/core.d.ts +1 -0
- package/framework/core.mjs +1 -1
- package/framework/native-runtime.js +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ export default function HomePage() {
|
|
|
79
79
|
npm run dev
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
Pages live in `src/pages`; `index.tsx` maps to `/`. `npm run dev` serves locally on `127.0.0.1`, reloads the browser after successful rebuilds, and shows build failures in an error overlay. Across that full-page reload, compatible Kudzu logical state is briefly preserved by route-unique state variable name for the current pathname, query, and hash, including controlled properties, conditions, and keyed-list arrays. Renamed, removed, and duplicate-named state is skipped. Uncontrolled DOM state, focus, selection, and imperative DOM mutations are not preserved.
|
|
82
|
+
Pages live in `src/pages`; `index.tsx` maps to `/`. `npm run dev` serves locally on `127.0.0.1`, reloads the browser after successful rebuilds, and shows build failures in an error overlay. Across that full-page reload, compatible Kudzu logical state is briefly preserved by route-unique state variable name for the current pathname, query, and hash, including controlled properties, conditions, and keyed-list arrays. Renamed, removed, and duplicate-named state is skipped. Uncontrolled DOM state, focus, selection, and imperative DOM mutations are not preserved. The server starts at `PORT` or `3000` and increments until it finds an available port. Set `HOST=0.0.0.0` when a local reverse proxy or container must reach the server. The development client and state snapshot are dev-only; production output in `dist/` is unaffected.
|
|
83
83
|
|
|
84
84
|
Dynamic static pages use bracket parameters and `getStaticPaths()`:
|
|
85
85
|
|
package/framework/build.mjs
CHANGED
|
@@ -65,6 +65,7 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
65
65
|
const plans = []
|
|
66
66
|
const pageEntries = []
|
|
67
67
|
const effectEntries = []
|
|
68
|
+
const nativeEntries = []
|
|
68
69
|
const paramEntries = []
|
|
69
70
|
const rewrites = []
|
|
70
71
|
const emittedRoutes = new Set()
|
|
@@ -103,6 +104,7 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
103
104
|
const navigationGroup = navigationByRoute.get(applicationRoute)
|
|
104
105
|
const navigable = Boolean(navigationGroup)
|
|
105
106
|
const effectPath = `effects/${route ? `${route}/index` : "index"}.js`
|
|
107
|
+
const nativePath = `native/${route ? `${route}/index` : "index"}.js`
|
|
106
108
|
const paramPath = `params/${route}/index.js`
|
|
107
109
|
if (emittedRoutes.has(routePath)) throw new Error(`Duplicate route: ${routePath}`)
|
|
108
110
|
emittedRoutes.add(routePath)
|
|
@@ -126,6 +128,7 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
126
128
|
base,
|
|
127
129
|
runtimeAsset: runtimePlaceholder,
|
|
128
130
|
effectAsset: assetPath(base, `assets/${effectPath}`),
|
|
131
|
+
nativeAsset: assetPath(base, `assets/${nativePath}`),
|
|
129
132
|
paramAsset: assetPath(base, `assets/${paramPath}`),
|
|
130
133
|
runtimeParams: runtimeSchema?.params,
|
|
131
134
|
...(navigable ? { navigationAsset: navigationGroup.assetPath, applicationId: navigationGroup.applicationId, layoutId: navigationGroup.layoutId, routeId: applicationRoute } : {})
|
|
@@ -141,6 +144,10 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
141
144
|
plans.push({ route: routePath, ...result.plan })
|
|
142
145
|
if (result.hasParams) paramEntries.push({ path: paramPath, schema: runtimeSchema, params: result.plan.params, usesDependencyRuntime, navigable })
|
|
143
146
|
if (result.hasEffects) effectEntries.push({ path: effectPath, effects: runtimeEffects(result.plan.effects, navigable), paramPath: result.hasParams ? paramPath : undefined, usesDependencyRuntime, navigable })
|
|
147
|
+
if (result.plan.events.some(event => event.native)) nativeEntries.push({
|
|
148
|
+
path: nativePath,
|
|
149
|
+
modules: [...new Set(result.plan.events.filter(event => event.native).map(event => event.native.module))]
|
|
150
|
+
})
|
|
144
151
|
if (result.hasBehaviors) {
|
|
145
152
|
behaviorCount++
|
|
146
153
|
if (!usesDependencyRuntime) regularBehaviorCount++
|
|
@@ -196,8 +203,7 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
196
203
|
const hasNestedStateCaptures = hasNestedCaptureState(plans)
|
|
197
204
|
const hasSetterCaptures = hasCaptureType(plans, "setter")
|
|
198
205
|
const hasEffectCaptures = plans.some(plan => plan.effects.some(effect => Object.keys(effect.scope).length))
|
|
199
|
-
const
|
|
200
|
-
const hasNativeHandlers = nativeModules.length > 0
|
|
206
|
+
const hasNativeHandlers = nativeEntries.length > 0
|
|
201
207
|
const hasEffects = effectEntries.length > 0
|
|
202
208
|
const hasNavigableEffects = effectEntries.some(entry => entry.navigable)
|
|
203
209
|
const hasNavigableOwners = effectEntries.some(entry => entry.navigable && entry.effects.some(effect => effect.owner))
|
|
@@ -279,9 +285,10 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
279
285
|
const nativeRuntime = (await readFile(new URL("./native-runtime.js", import.meta.url), "utf8"))
|
|
280
286
|
.replace('"./shared-runtime.js"', '"./kudzu.js"')
|
|
281
287
|
.replace('"./serialization.js"', '"./kudzu-serialization.js"')
|
|
282
|
-
await writeJavaScript(join(assetsDirectory, "kudzu-native.js"),
|
|
288
|
+
await writeJavaScript(join(assetsDirectory, "kudzu-native.js"), specializeEvents(nativeRuntime, nativeEvents), minify, {
|
|
283
289
|
"globalThis.__KUDZU_CAPTURE_SETTER__": String(hasSetterCaptures)
|
|
284
290
|
})
|
|
291
|
+
for (const entry of nativeEntries) await printNativeEntry(entry, assetsDirectory, base, minify)
|
|
285
292
|
}
|
|
286
293
|
if (navigationGroups.length) {
|
|
287
294
|
const navigationSource = await readFile(new URL("./navigation-runtime.js", import.meta.url), "utf8")
|
|
@@ -411,10 +418,13 @@ function specializeNavigationPatterns(source, enabled) {
|
|
|
411
418
|
function fallback`)
|
|
412
419
|
}
|
|
413
420
|
|
|
414
|
-
function
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
421
|
+
async function printNativeEntry(entry, assetsDirectory, base, minify) {
|
|
422
|
+
const output = join(assetsDirectory, entry.path)
|
|
423
|
+
await mkdir(dirname(output), { recursive: true })
|
|
424
|
+
const imports = entry.modules.map((module, index) => `import * as __kNativeModule${index} from ${JSON.stringify(module)}`).join("\n")
|
|
425
|
+
const registrations = entry.modules.map((module, index) => `[${JSON.stringify(module)}, __kNativeModule${index}]`).join(",")
|
|
426
|
+
const runtime = assetPath(base, "assets/kudzu-native.js")
|
|
427
|
+
await writeJavaScript(output, `import { registerNativeModules } from ${JSON.stringify(runtime)}\n${imports}\nregisterNativeModules([${registrations}])`, minify)
|
|
418
428
|
}
|
|
419
429
|
|
|
420
430
|
function printEffectEntry(effects, output, handlerModules, assetsDirectory, base, paramPath, runtimeName) {
|
|
@@ -1360,8 +1370,13 @@ export function parseDevPort(value) {
|
|
|
1360
1370
|
return port
|
|
1361
1371
|
}
|
|
1362
1372
|
|
|
1363
|
-
export
|
|
1373
|
+
export function parseDevHost(value) {
|
|
1374
|
+
return value?.trim() || "127.0.0.1"
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
export async function dev({ port = parseDevPort(process.env.PORT), host = parseDevHost(process.env.HOST) } = {}) {
|
|
1364
1378
|
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error(`Invalid dev server port: ${port}`)
|
|
1379
|
+
if (typeof host !== "string" || !host.trim()) throw new Error(`Invalid dev server host: ${host}`)
|
|
1365
1380
|
const base = normalizeBase((await loadConfig()).base)
|
|
1366
1381
|
|
|
1367
1382
|
let buildError
|
|
@@ -1431,7 +1446,8 @@ export async function dev({ port = parseDevPort(process.env.PORT) } = {}) {
|
|
|
1431
1446
|
}
|
|
1432
1447
|
})
|
|
1433
1448
|
|
|
1434
|
-
|
|
1449
|
+
const listeningPort = await listenDevServer(server, port, host)
|
|
1450
|
+
console.log(`Kudzu dev server: http://${host}:${listeningPort}`)
|
|
1435
1451
|
|
|
1436
1452
|
let timer
|
|
1437
1453
|
let rebuilding = false
|
|
@@ -1467,6 +1483,32 @@ export async function dev({ port = parseDevPort(process.env.PORT) } = {}) {
|
|
|
1467
1483
|
}
|
|
1468
1484
|
}
|
|
1469
1485
|
|
|
1486
|
+
async function listenDevServer(server, port, host) {
|
|
1487
|
+
let candidate = port
|
|
1488
|
+
while (true) {
|
|
1489
|
+
try {
|
|
1490
|
+
await new Promise((resolve, reject) => {
|
|
1491
|
+
const onError = error => {
|
|
1492
|
+
server.off("listening", onListening)
|
|
1493
|
+
reject(error)
|
|
1494
|
+
}
|
|
1495
|
+
const onListening = () => {
|
|
1496
|
+
server.off("error", onError)
|
|
1497
|
+
resolve()
|
|
1498
|
+
}
|
|
1499
|
+
server.once("error", onError)
|
|
1500
|
+
server.once("listening", onListening)
|
|
1501
|
+
server.listen(candidate, host)
|
|
1502
|
+
})
|
|
1503
|
+
return server.address().port
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
if (error.code !== "EADDRINUSE" || candidate === 0 || candidate === 65535) throw error
|
|
1506
|
+
console.log(`Port ${candidate} is in use, trying ${candidate + 1}`)
|
|
1507
|
+
candidate++
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1470
1512
|
function injectDevClient(html, session, revision, schema) {
|
|
1471
1513
|
return `${html}${devClient(session, revision, schema).replace("binding|list|native", "binding|deps|list|native")}`
|
|
1472
1514
|
}
|
package/framework/core.d.ts
CHANGED
package/framework/core.mjs
CHANGED
|
@@ -381,7 +381,7 @@ export async function renderPage(component, metadata = {}, props = {}, layout) {
|
|
|
381
381
|
? `<script type="module"${capability} src="${escapeAttribute(metadata.runtimeAsset ?? assetPath(metadata.base, "assets/kudzu.js"))}"></script>`
|
|
382
382
|
: ""
|
|
383
383
|
const nativeRuntime = renderContext.hasNativeBehaviors
|
|
384
|
-
? `<script type="module"${capability} src="${assetPath(metadata.base, "assets/kudzu-native.js")}"></script>`
|
|
384
|
+
? `<script type="module"${capability} src="${escapeAttribute(metadata.nativeAsset ?? assetPath(metadata.base, "assets/kudzu-native.js"))}"></script>`
|
|
385
385
|
: ""
|
|
386
386
|
const paramRuntime = renderContext.hasParams
|
|
387
387
|
? `<script type="module"${capability} src="${escapeAttribute(metadata.paramAsset)}"></script>`
|
|
@@ -2,6 +2,11 @@ import { browserState, commitDom, registerMountHook, registerUnmountHook } from
|
|
|
2
2
|
import { deserialize } from "./serialization.js"
|
|
3
3
|
|
|
4
4
|
const registrations = new WeakMap()
|
|
5
|
+
const modules = new Map()
|
|
6
|
+
|
|
7
|
+
export function registerNativeModules(entries) {
|
|
8
|
+
for (const [url, module] of entries) modules.set(url, module)
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
export function createNativeContext(state, stateIds, commit, serializedScope = {}) {
|
|
7
12
|
const changed = new Set()
|
|
@@ -52,7 +57,6 @@ export function createNativeContext(state, stateIds, commit, serializedScope = {
|
|
|
52
57
|
|
|
53
58
|
if (typeof document !== "undefined") {
|
|
54
59
|
const eventNames = ["click", "input", "change", "submit", "keydown", "keyup"]
|
|
55
|
-
const modules = new Map([])
|
|
56
60
|
const mount = root => mountNative(root, eventNames, modules)
|
|
57
61
|
registerMountHook(mount)
|
|
58
62
|
registerUnmountHook(unmountNative)
|