nukejs 0.0.6 → 0.0.7

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.
Files changed (40) hide show
  1. package/README.md +87 -5
  2. package/dist/{as-is/Link.js → Link.js} +3 -1
  3. package/dist/Link.js.map +7 -0
  4. package/dist/build-common.d.ts +6 -0
  5. package/dist/build-common.js +20 -6
  6. package/dist/build-common.js.map +2 -2
  7. package/dist/build-node.js.map +1 -1
  8. package/dist/build-vercel.js.map +1 -1
  9. package/dist/builder.d.ts +4 -10
  10. package/dist/builder.js +7 -38
  11. package/dist/builder.js.map +2 -2
  12. package/dist/bundle.js +60 -4
  13. package/dist/bundle.js.map +2 -2
  14. package/dist/component-analyzer.d.ts +6 -0
  15. package/dist/component-analyzer.js +12 -1
  16. package/dist/component-analyzer.js.map +2 -2
  17. package/dist/hmr-bundle.js +17 -4
  18. package/dist/hmr-bundle.js.map +2 -2
  19. package/dist/html-store.d.ts +7 -0
  20. package/dist/html-store.js.map +2 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +2 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/renderer.js +2 -7
  25. package/dist/renderer.js.map +2 -2
  26. package/dist/router.d.ts +20 -19
  27. package/dist/router.js +14 -6
  28. package/dist/router.js.map +2 -2
  29. package/dist/ssr.js +21 -4
  30. package/dist/ssr.js.map +2 -2
  31. package/dist/use-html.js +5 -1
  32. package/dist/use-html.js.map +2 -2
  33. package/dist/{as-is/useRouter.js → use-router.js} +1 -1
  34. package/dist/{as-is/useRouter.js.map → use-router.js.map} +2 -2
  35. package/package.json +1 -1
  36. package/dist/as-is/Link.js.map +0 -7
  37. package/dist/as-is/Link.tsx +0 -20
  38. package/dist/as-is/useRouter.ts +0 -33
  39. /package/dist/{as-is/Link.d.ts → Link.d.ts} +0 -0
  40. /package/dist/{as-is/useRouter.d.ts → use-router.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/build-vercel.ts"],
4
- "sourcesContent": ["/**\n * build-vercel.ts \u2014 Vercel Production Build\n *\n * Produces a .vercel/output/ directory conforming to the Vercel Build Output\n * API v3. Two serverless functions are emitted:\n *\n * api.func/ \u2190 single dispatcher bundling all API route handlers\n * pages.func/ \u2190 single dispatcher bundling all SSR page handlers\n *\n * Static assets (React runtime, client components, public files) go to\n * .vercel/output/static/ and are served by Vercel's CDN directly.\n *\n * Notes on bundling strategy:\n * - npm packages are FULLY BUNDLED (no node_modules at Vercel runtime).\n * - Node built-ins are kept external (available in the nodejs20.x runtime).\n * - A createRequire banner lets CJS packages (mongoose, etc.) resolve Node\n * built-ins correctly inside the ESM output bundle.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { randomBytes } from 'node:crypto';\nimport { build } from 'esbuild';\n\nimport { loadConfig } from './config';\nimport {\n walkFiles,\n analyzeFile,\n collectServerPages,\n collectGlobalClientRegistry,\n bundleClientComponents,\n findPageLayouts,\n buildPerPageRegistry,\n makePageAdapterSource,\n buildCombinedBundle,\n copyPublicFiles,\n} from './build-common';\n\n// \u2500\u2500\u2500 Output directories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst OUTPUT_DIR = path.resolve('.vercel/output');\nconst FUNCTIONS_DIR = path.join(OUTPUT_DIR, 'functions');\nconst STATIC_DIR = path.join(OUTPUT_DIR, 'static');\n\nfor (const dir of [FUNCTIONS_DIR, STATIC_DIR])\n fs.mkdirSync(dir, { recursive: true });\n\n// \u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst config = await loadConfig();\nconst SERVER_DIR = path.resolve(config.serverDir);\nconst PAGES_DIR = path.resolve('./app/pages');\nconst PUBLIC_DIR = path.resolve('./app/public');\n\n// \u2500\u2500\u2500 Shared esbuild config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Node built-ins that should never be bundled.\n * npm packages are intentionally absent \u2014 they must be bundled because\n * Vercel serverless functions have no node_modules at runtime.\n */\nconst NODE_BUILTINS = [\n 'node:*',\n 'http', 'https', 'fs', 'path', 'url', 'crypto', 'stream', 'buffer',\n 'events', 'util', 'os', 'net', 'tls', 'child_process', 'worker_threads',\n 'cluster', 'dgram', 'dns', 'readline', 'zlib', 'assert', 'module',\n 'perf_hooks', 'string_decoder', 'timers', 'async_hooks', 'v8', 'vm',\n];\n\n/**\n * Banner injected at the top of every Vercel function bundle.\n *\n * Why it's needed: esbuild bundles CJS packages (mongoose, etc.) into ESM\n * output and replaces their require() calls with a __require2 shim. That\n * shim cannot resolve Node built-ins on its own inside an ESM module scope.\n * Injecting a real require (backed by createRequire) fixes the shim so that\n * dynamic require('crypto'), require('stream'), etc. work correctly.\n */\nconst CJS_COMPAT_BANNER = {\n js: `import { createRequire } from 'module';\\nconst require = createRequire(import.meta.url);`,\n};\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype VercelRoute = { src: string; dest: string };\n\n/** Writes a bundled dispatcher into a Vercel .func directory. */\nfunction emitVercelFunction(name: string, bundleText: string): void {\n const funcDir = path.join(FUNCTIONS_DIR, `${name}.func`);\n fs.mkdirSync(funcDir, { recursive: true });\n fs.writeFileSync(path.join(funcDir, 'index.mjs'), bundleText);\n fs.writeFileSync(\n path.join(funcDir, '.vc-config.json'),\n JSON.stringify({ runtime: 'nodejs20.x', handler: 'index.mjs', launcherType: 'Nodejs' }, null, 2),\n );\n}\n\n// \u2500\u2500\u2500 API dispatcher source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Generates a single TypeScript dispatcher that imports every API route module,\n * matches the incoming URL against each route's regex, injects captured params,\n * and calls the right HTTP-method export (GET, POST, \u2026) or default export.\n */\nfunction makeApiDispatcherSource(\n routes: Array<{ absPath: string; srcRegex: string; paramNames: string[] }>,\n): string {\n const imports = routes\n .map((r, i) => `import * as __api_${i}__ from ${JSON.stringify(r.absPath)};`)\n .join('\\n');\n\n const routeEntries = routes\n .map((r, i) =>\n ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, mod: __api_${i}__ },`,\n )\n .join('\\n');\n\n return `\\\nimport type { IncomingMessage, ServerResponse } from 'http';\n${imports}\n\nfunction enhance(res: ServerResponse) {\n (res as any).json = function(data: any, status = 200) {\n this.statusCode = status;\n this.setHeader('Content-Type', 'application/json');\n this.end(JSON.stringify(data));\n };\n (res as any).status = function(code: number) { this.statusCode = code; return this; };\n return res;\n}\n\nasync function parseBody(req: IncomingMessage): Promise<any> {\n return new Promise((resolve, reject) => {\n let body = '';\n req.on('data', (chunk: any) => { body += chunk.toString(); });\n req.on('end', () => {\n try {\n resolve(\n body && req.headers['content-type']?.includes('application/json')\n ? JSON.parse(body)\n : body,\n );\n } catch (e) { reject(e); }\n });\n req.on('error', reject);\n });\n}\n\nconst ROUTES = [\n${routeEntries}\n];\n\nexport default async function handler(req: IncomingMessage, res: ServerResponse) {\n const url = new URL(req.url || '/', 'http://localhost');\n const pathname = url.pathname;\n\n for (const route of ROUTES) {\n const m = pathname.match(new RegExp(route.regex));\n if (!m) continue;\n\n const method = (req.method || 'GET').toUpperCase();\n const apiRes = enhance(res);\n const apiReq = req as any;\n\n apiReq.body = await parseBody(req);\n apiReq.query = Object.fromEntries(url.searchParams);\n apiReq.params = {};\n route.params.forEach((name: string, i: number) => { apiReq.params[name] = m[i + 1]; });\n\n const fn = (route.mod as any)[method] ?? (route.mod as any)['default'];\n if (typeof fn !== 'function') {\n (apiRes as any).json({ error: \\`Method \\${method} not allowed\\` }, 405);\n return;\n }\n await fn(apiReq, apiRes);\n return;\n }\n\n res.statusCode = 404;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: 'Not Found' }));\n}\n`;\n}\n\n// \u2500\u2500\u2500 Pages dispatcher source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Generates a TypeScript dispatcher that imports each page's pre-generated\n * adapter, matches the incoming URL, encodes captured dynamic params as\n * query-string values (catch-all params use repeated keys), then delegates\n * to the matching handler.\n */\nfunction makePagesDispatcherSource(\n routes: Array<{\n adapterPath: string;\n srcRegex: string;\n paramNames: string[];\n catchAllNames: string[];\n }>,\n): string {\n const imports = routes\n .map((r, i) => `import __page_${i}__ from ${JSON.stringify(r.adapterPath)};`)\n .join('\\n');\n\n const routeEntries = routes\n .map((r, i) =>\n ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, catchAll: ${JSON.stringify(r.catchAllNames)}, handler: __page_${i}__ },`,\n )\n .join('\\n');\n\n return `\\\nimport type { IncomingMessage, ServerResponse } from 'http';\n${imports}\n\nconst ROUTES: Array<{\n regex: string;\n params: string[];\n catchAll: string[];\n handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;\n}> = [\n${routeEntries}\n];\n\nexport default async function handler(req: IncomingMessage, res: ServerResponse) {\n const url = new URL(req.url || '/', 'http://localhost');\n const pathname = url.pathname;\n\n for (const route of ROUTES) {\n const m = pathname.match(new RegExp(route.regex));\n if (!m) continue;\n\n const catchAllSet = new Set(route.catchAll);\n route.params.forEach((name, i) => {\n const raw = m[i + 1] ?? '';\n if (catchAllSet.has(name)) {\n // Encode catch-all as repeated keys so the handler can getAll() \u2192 string[]\n raw.split('/').filter(Boolean).forEach(seg => url.searchParams.append(name, seg));\n } else {\n url.searchParams.set(name, raw);\n }\n });\n req.url = pathname + (url.search || '');\n\n return route.handler(req, res);\n }\n\n res.statusCode = 404;\n res.setHeader('Content-Type', 'text/plain; charset=utf-8');\n res.end('Not Found');\n}\n`;\n}\n\n// \u2500\u2500\u2500 Build API function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst vercelRoutes: VercelRoute[] = [];\n\nconst apiFiles = walkFiles(SERVER_DIR);\nif (apiFiles.length === 0) console.warn(`\u26A0 No server files found in ${SERVER_DIR}`);\n\nconst apiRoutes = apiFiles\n .map(relPath => ({ ...analyzeFile(relPath, 'api'), absPath: path.join(SERVER_DIR, relPath) }))\n .sort((a, b) => b.specificity - a.specificity);\n\nif (apiRoutes.length > 0) {\n const dispatcherPath = path.join(SERVER_DIR, `_api_dispatcher_${randomBytes(4).toString('hex')}.ts`);\n fs.writeFileSync(dispatcherPath, makeApiDispatcherSource(apiRoutes));\n\n try {\n const result = await build({\n entryPoints: [dispatcherPath],\n bundle: true,\n format: 'esm',\n platform: 'node',\n target: 'node20',\n banner: CJS_COMPAT_BANNER,\n external: NODE_BUILTINS,\n write: false,\n });\n emitVercelFunction('api', result.outputFiles[0].text);\n console.log(` built API dispatcher \u2192 api.func (${apiRoutes.length} route(s))`);\n } finally {\n fs.unlinkSync(dispatcherPath);\n }\n\n // API routes are listed first \u2014 they win on any URL collision with pages.\n for (const { srcRegex } of apiRoutes)\n vercelRoutes.push({ src: srcRegex, dest: '/api' });\n}\n\n// \u2500\u2500\u2500 Build Pages function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst serverPages = collectServerPages(PAGES_DIR);\n\nif (serverPages.length > 0) {\n // Pass 1 \u2014 bundle all client components to static files.\n const globalRegistry = collectGlobalClientRegistry(serverPages, PAGES_DIR);\n const prerenderedHtml = await bundleClientComponents(globalRegistry, PAGES_DIR, STATIC_DIR);\n const prerenderedRecord = Object.fromEntries(prerenderedHtml);\n\n // Pass 2 \u2014 write one temp adapter per page next to its source file (so\n // relative imports resolve correctly), then bundle everything in\n // one esbuild pass via the dispatcher.\n const tempAdapterPaths: string[] = [];\n\n for (const page of serverPages) {\n const adapterDir = path.dirname(page.absPath);\n const adapterPath = path.join(adapterDir, `_page_adapter_${randomBytes(4).toString('hex')}.ts`);\n\n const layoutPaths = findPageLayouts(page.absPath, PAGES_DIR);\n const { registry, clientComponentNames } = buildPerPageRegistry(page.absPath, layoutPaths, PAGES_DIR);\n\n const layoutImports = layoutPaths\n .map((lp, i) => {\n const rel = path.relative(adapterDir, lp).replace(/\\\\/g, '/');\n return `import __layout_${i}__ from ${JSON.stringify(rel.startsWith('.') ? rel : './' + rel)};`;\n })\n .join('\\n');\n\n fs.writeFileSync(\n adapterPath,\n makePageAdapterSource({\n pageImport: JSON.stringify('./' + path.basename(page.absPath)),\n layoutImports,\n clientComponentNames,\n allClientIds: [...registry.keys()],\n layoutArrayItems: layoutPaths.map((_, i) => `__layout_${i}__`).join(', '),\n prerenderedHtml: prerenderedRecord,\n catchAllNames: page.catchAllNames,\n }),\n );\n\n tempAdapterPaths.push(adapterPath);\n console.log(` prepared ${path.relative(PAGES_DIR, page.absPath)} \u2192 ${page.funcPath} [page]`);\n }\n\n const dispatcherRoutes = serverPages.map((page, i) => ({\n adapterPath: tempAdapterPaths[i],\n srcRegex: page.srcRegex,\n paramNames: page.paramNames,\n catchAllNames: page.catchAllNames,\n }));\n\n const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${randomBytes(4).toString('hex')}.ts`);\n fs.writeFileSync(dispatcherPath, makePagesDispatcherSource(dispatcherRoutes));\n\n try {\n const result = await build({\n entryPoints: [dispatcherPath],\n bundle: true,\n format: 'esm',\n platform: 'node',\n target: 'node20',\n jsx: 'automatic',\n banner: CJS_COMPAT_BANNER,\n external: NODE_BUILTINS,\n define: { 'process.env.NODE_ENV': '\"production\"' },\n write: false,\n });\n emitVercelFunction('pages', result.outputFiles[0].text);\n console.log(` built Pages dispatcher \u2192 pages.func (${serverPages.length} page(s))`);\n } finally {\n fs.unlinkSync(dispatcherPath);\n for (const p of tempAdapterPaths) if (fs.existsSync(p)) fs.unlinkSync(p);\n }\n\n for (const { srcRegex } of serverPages)\n vercelRoutes.push({ src: srcRegex, dest: '/pages' });\n}\n\n// \u2500\u2500\u2500 Vercel config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfs.writeFileSync(\n path.join(OUTPUT_DIR, 'config.json'),\n JSON.stringify({ version: 3, routes: vercelRoutes }, null, 2),\n);\nfs.writeFileSync(\n path.resolve('vercel.json'),\n JSON.stringify({ runtime: 'nodejs20.x' }, null, 2),\n);\n\n// \u2500\u2500\u2500 Static assets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nawait buildCombinedBundle(STATIC_DIR);\ncopyPublicFiles(PUBLIC_DIR, STATIC_DIR);\n\nconst fnCount = (apiRoutes.length > 0 ? 1 : 0) + (serverPages.length > 0 ? 1 : 0);\nconsole.log(`\\n\u2713 Vercel build complete \u2014 ${fnCount} function(s) \u2192 .vercel/output`);\n"],
4
+ "sourcesContent": ["/**\r\n * build-vercel.ts \u2014 Vercel Production Build\r\n *\r\n * Produces a .vercel/output/ directory conforming to the Vercel Build Output\r\n * API v3. Two serverless functions are emitted:\r\n *\r\n * api.func/ \u2190 single dispatcher bundling all API route handlers\r\n * pages.func/ \u2190 single dispatcher bundling all SSR page handlers\r\n *\r\n * Static assets (React runtime, client components, public files) go to\r\n * .vercel/output/static/ and are served by Vercel's CDN directly.\r\n *\r\n * Notes on bundling strategy:\r\n * - npm packages are FULLY BUNDLED (no node_modules at Vercel runtime).\r\n * - Node built-ins are kept external (available in the nodejs20.x runtime).\r\n * - A createRequire banner lets CJS packages (mongoose, etc.) resolve Node\r\n * built-ins correctly inside the ESM output bundle.\r\n */\r\n\r\nimport fs from 'fs';\r\nimport path from 'path';\r\nimport { randomBytes } from 'node:crypto';\r\nimport { build } from 'esbuild';\r\n\r\nimport { loadConfig } from './config';\r\nimport {\r\n walkFiles,\r\n analyzeFile,\r\n collectServerPages,\r\n collectGlobalClientRegistry,\r\n bundleClientComponents,\r\n findPageLayouts,\r\n buildPerPageRegistry,\r\n makePageAdapterSource,\r\n buildCombinedBundle,\r\n copyPublicFiles,\r\n} from './build-common';\r\n\r\n// \u2500\u2500\u2500 Output directories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst OUTPUT_DIR = path.resolve('.vercel/output');\r\nconst FUNCTIONS_DIR = path.join(OUTPUT_DIR, 'functions');\r\nconst STATIC_DIR = path.join(OUTPUT_DIR, 'static');\r\n\r\nfor (const dir of [FUNCTIONS_DIR, STATIC_DIR])\r\n fs.mkdirSync(dir, { recursive: true });\r\n\r\n// \u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst config = await loadConfig();\r\nconst SERVER_DIR = path.resolve(config.serverDir);\r\nconst PAGES_DIR = path.resolve('./app/pages');\r\nconst PUBLIC_DIR = path.resolve('./app/public');\r\n\r\n// \u2500\u2500\u2500 Shared esbuild config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Node built-ins that should never be bundled.\r\n * npm packages are intentionally absent \u2014 they must be bundled because\r\n * Vercel serverless functions have no node_modules at runtime.\r\n */\r\nconst NODE_BUILTINS = [\r\n 'node:*',\r\n 'http', 'https', 'fs', 'path', 'url', 'crypto', 'stream', 'buffer',\r\n 'events', 'util', 'os', 'net', 'tls', 'child_process', 'worker_threads',\r\n 'cluster', 'dgram', 'dns', 'readline', 'zlib', 'assert', 'module',\r\n 'perf_hooks', 'string_decoder', 'timers', 'async_hooks', 'v8', 'vm',\r\n];\r\n\r\n/**\r\n * Banner injected at the top of every Vercel function bundle.\r\n *\r\n * Why it's needed: esbuild bundles CJS packages (mongoose, etc.) into ESM\r\n * output and replaces their require() calls with a __require2 shim. That\r\n * shim cannot resolve Node built-ins on its own inside an ESM module scope.\r\n * Injecting a real require (backed by createRequire) fixes the shim so that\r\n * dynamic require('crypto'), require('stream'), etc. work correctly.\r\n */\r\nconst CJS_COMPAT_BANNER = {\r\n js: `import { createRequire } from 'module';\\nconst require = createRequire(import.meta.url);`,\r\n};\r\n\r\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\ntype VercelRoute = { src: string; dest: string };\r\n\r\n/** Writes a bundled dispatcher into a Vercel .func directory. */\r\nfunction emitVercelFunction(name: string, bundleText: string): void {\r\n const funcDir = path.join(FUNCTIONS_DIR, `${name}.func`);\r\n fs.mkdirSync(funcDir, { recursive: true });\r\n fs.writeFileSync(path.join(funcDir, 'index.mjs'), bundleText);\r\n fs.writeFileSync(\r\n path.join(funcDir, '.vc-config.json'),\r\n JSON.stringify({ runtime: 'nodejs20.x', handler: 'index.mjs', launcherType: 'Nodejs' }, null, 2),\r\n );\r\n}\r\n\r\n// \u2500\u2500\u2500 API dispatcher source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Generates a single TypeScript dispatcher that imports every API route module,\r\n * matches the incoming URL against each route's regex, injects captured params,\r\n * and calls the right HTTP-method export (GET, POST, \u2026) or default export.\r\n */\r\nfunction makeApiDispatcherSource(\r\n routes: Array<{ absPath: string; srcRegex: string; paramNames: string[] }>,\r\n): string {\r\n const imports = routes\r\n .map((r, i) => `import * as __api_${i}__ from ${JSON.stringify(r.absPath)};`)\r\n .join('\\n');\r\n\r\n const routeEntries = routes\r\n .map((r, i) =>\r\n ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, mod: __api_${i}__ },`,\r\n )\r\n .join('\\n');\r\n\r\n return `\\\r\nimport type { IncomingMessage, ServerResponse } from 'http';\r\n${imports}\r\n\r\nfunction enhance(res: ServerResponse) {\r\n (res as any).json = function(data: any, status = 200) {\r\n this.statusCode = status;\r\n this.setHeader('Content-Type', 'application/json');\r\n this.end(JSON.stringify(data));\r\n };\r\n (res as any).status = function(code: number) { this.statusCode = code; return this; };\r\n return res;\r\n}\r\n\r\nasync function parseBody(req: IncomingMessage): Promise<any> {\r\n return new Promise((resolve, reject) => {\r\n let body = '';\r\n req.on('data', (chunk: any) => { body += chunk.toString(); });\r\n req.on('end', () => {\r\n try {\r\n resolve(\r\n body && req.headers['content-type']?.includes('application/json')\r\n ? JSON.parse(body)\r\n : body,\r\n );\r\n } catch (e) { reject(e); }\r\n });\r\n req.on('error', reject);\r\n });\r\n}\r\n\r\nconst ROUTES = [\r\n${routeEntries}\r\n];\r\n\r\nexport default async function handler(req: IncomingMessage, res: ServerResponse) {\r\n const url = new URL(req.url || '/', 'http://localhost');\r\n const pathname = url.pathname;\r\n\r\n for (const route of ROUTES) {\r\n const m = pathname.match(new RegExp(route.regex));\r\n if (!m) continue;\r\n\r\n const method = (req.method || 'GET').toUpperCase();\r\n const apiRes = enhance(res);\r\n const apiReq = req as any;\r\n\r\n apiReq.body = await parseBody(req);\r\n apiReq.query = Object.fromEntries(url.searchParams);\r\n apiReq.params = {};\r\n route.params.forEach((name: string, i: number) => { apiReq.params[name] = m[i + 1]; });\r\n\r\n const fn = (route.mod as any)[method] ?? (route.mod as any)['default'];\r\n if (typeof fn !== 'function') {\r\n (apiRes as any).json({ error: \\`Method \\${method} not allowed\\` }, 405);\r\n return;\r\n }\r\n await fn(apiReq, apiRes);\r\n return;\r\n }\r\n\r\n res.statusCode = 404;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: 'Not Found' }));\r\n}\r\n`;\r\n}\r\n\r\n// \u2500\u2500\u2500 Pages dispatcher source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Generates a TypeScript dispatcher that imports each page's pre-generated\r\n * adapter, matches the incoming URL, encodes captured dynamic params as\r\n * query-string values (catch-all params use repeated keys), then delegates\r\n * to the matching handler.\r\n */\r\nfunction makePagesDispatcherSource(\r\n routes: Array<{\r\n adapterPath: string;\r\n srcRegex: string;\r\n paramNames: string[];\r\n catchAllNames: string[];\r\n }>,\r\n): string {\r\n const imports = routes\r\n .map((r, i) => `import __page_${i}__ from ${JSON.stringify(r.adapterPath)};`)\r\n .join('\\n');\r\n\r\n const routeEntries = routes\r\n .map((r, i) =>\r\n ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, catchAll: ${JSON.stringify(r.catchAllNames)}, handler: __page_${i}__ },`,\r\n )\r\n .join('\\n');\r\n\r\n return `\\\r\nimport type { IncomingMessage, ServerResponse } from 'http';\r\n${imports}\r\n\r\nconst ROUTES: Array<{\r\n regex: string;\r\n params: string[];\r\n catchAll: string[];\r\n handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;\r\n}> = [\r\n${routeEntries}\r\n];\r\n\r\nexport default async function handler(req: IncomingMessage, res: ServerResponse) {\r\n const url = new URL(req.url || '/', 'http://localhost');\r\n const pathname = url.pathname;\r\n\r\n for (const route of ROUTES) {\r\n const m = pathname.match(new RegExp(route.regex));\r\n if (!m) continue;\r\n\r\n const catchAllSet = new Set(route.catchAll);\r\n route.params.forEach((name, i) => {\r\n const raw = m[i + 1] ?? '';\r\n if (catchAllSet.has(name)) {\r\n // Encode catch-all as repeated keys so the handler can getAll() \u2192 string[]\r\n raw.split('/').filter(Boolean).forEach(seg => url.searchParams.append(name, seg));\r\n } else {\r\n url.searchParams.set(name, raw);\r\n }\r\n });\r\n req.url = pathname + (url.search || '');\r\n\r\n return route.handler(req, res);\r\n }\r\n\r\n res.statusCode = 404;\r\n res.setHeader('Content-Type', 'text/plain; charset=utf-8');\r\n res.end('Not Found');\r\n}\r\n`;\r\n}\r\n\r\n// \u2500\u2500\u2500 Build API function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst vercelRoutes: VercelRoute[] = [];\r\n\r\nconst apiFiles = walkFiles(SERVER_DIR);\r\nif (apiFiles.length === 0) console.warn(`\u26A0 No server files found in ${SERVER_DIR}`);\r\n\r\nconst apiRoutes = apiFiles\r\n .map(relPath => ({ ...analyzeFile(relPath, 'api'), absPath: path.join(SERVER_DIR, relPath) }))\r\n .sort((a, b) => b.specificity - a.specificity);\r\n\r\nif (apiRoutes.length > 0) {\r\n const dispatcherPath = path.join(SERVER_DIR, `_api_dispatcher_${randomBytes(4).toString('hex')}.ts`);\r\n fs.writeFileSync(dispatcherPath, makeApiDispatcherSource(apiRoutes));\r\n\r\n try {\r\n const result = await build({\r\n entryPoints: [dispatcherPath],\r\n bundle: true,\r\n format: 'esm',\r\n platform: 'node',\r\n target: 'node20',\r\n banner: CJS_COMPAT_BANNER,\r\n external: NODE_BUILTINS,\r\n write: false,\r\n });\r\n emitVercelFunction('api', result.outputFiles[0].text);\r\n console.log(` built API dispatcher \u2192 api.func (${apiRoutes.length} route(s))`);\r\n } finally {\r\n fs.unlinkSync(dispatcherPath);\r\n }\r\n\r\n // API routes are listed first \u2014 they win on any URL collision with pages.\r\n for (const { srcRegex } of apiRoutes)\r\n vercelRoutes.push({ src: srcRegex, dest: '/api' });\r\n}\r\n\r\n// \u2500\u2500\u2500 Build Pages function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst serverPages = collectServerPages(PAGES_DIR);\r\n\r\nif (serverPages.length > 0) {\r\n // Pass 1 \u2014 bundle all client components to static files.\r\n const globalRegistry = collectGlobalClientRegistry(serverPages, PAGES_DIR);\r\n const prerenderedHtml = await bundleClientComponents(globalRegistry, PAGES_DIR, STATIC_DIR);\r\n const prerenderedRecord = Object.fromEntries(prerenderedHtml);\r\n\r\n // Pass 2 \u2014 write one temp adapter per page next to its source file (so\r\n // relative imports resolve correctly), then bundle everything in\r\n // one esbuild pass via the dispatcher.\r\n const tempAdapterPaths: string[] = [];\r\n\r\n for (const page of serverPages) {\r\n const adapterDir = path.dirname(page.absPath);\r\n const adapterPath = path.join(adapterDir, `_page_adapter_${randomBytes(4).toString('hex')}.ts`);\r\n\r\n const layoutPaths = findPageLayouts(page.absPath, PAGES_DIR);\r\n const { registry, clientComponentNames } = buildPerPageRegistry(page.absPath, layoutPaths, PAGES_DIR);\r\n\r\n const layoutImports = layoutPaths\r\n .map((lp, i) => {\r\n const rel = path.relative(adapterDir, lp).replace(/\\\\/g, '/');\r\n return `import __layout_${i}__ from ${JSON.stringify(rel.startsWith('.') ? rel : './' + rel)};`;\r\n })\r\n .join('\\n');\r\n\r\n fs.writeFileSync(\r\n adapterPath,\r\n makePageAdapterSource({\r\n pageImport: JSON.stringify('./' + path.basename(page.absPath)),\r\n layoutImports,\r\n clientComponentNames,\r\n allClientIds: [...registry.keys()],\r\n layoutArrayItems: layoutPaths.map((_, i) => `__layout_${i}__`).join(', '),\r\n prerenderedHtml: prerenderedRecord,\r\n catchAllNames: page.catchAllNames,\r\n }),\r\n );\r\n\r\n tempAdapterPaths.push(adapterPath);\r\n console.log(` prepared ${path.relative(PAGES_DIR, page.absPath)} \u2192 ${page.funcPath} [page]`);\r\n }\r\n\r\n const dispatcherRoutes = serverPages.map((page, i) => ({\r\n adapterPath: tempAdapterPaths[i],\r\n srcRegex: page.srcRegex,\r\n paramNames: page.paramNames,\r\n catchAllNames: page.catchAllNames,\r\n }));\r\n\r\n const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${randomBytes(4).toString('hex')}.ts`);\r\n fs.writeFileSync(dispatcherPath, makePagesDispatcherSource(dispatcherRoutes));\r\n\r\n try {\r\n const result = await build({\r\n entryPoints: [dispatcherPath],\r\n bundle: true,\r\n format: 'esm',\r\n platform: 'node',\r\n target: 'node20',\r\n jsx: 'automatic',\r\n banner: CJS_COMPAT_BANNER,\r\n external: NODE_BUILTINS,\r\n define: { 'process.env.NODE_ENV': '\"production\"' },\r\n write: false,\r\n });\r\n emitVercelFunction('pages', result.outputFiles[0].text);\r\n console.log(` built Pages dispatcher \u2192 pages.func (${serverPages.length} page(s))`);\r\n } finally {\r\n fs.unlinkSync(dispatcherPath);\r\n for (const p of tempAdapterPaths) if (fs.existsSync(p)) fs.unlinkSync(p);\r\n }\r\n\r\n for (const { srcRegex } of serverPages)\r\n vercelRoutes.push({ src: srcRegex, dest: '/pages' });\r\n}\r\n\r\n// \u2500\u2500\u2500 Vercel config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfs.writeFileSync(\r\n path.join(OUTPUT_DIR, 'config.json'),\r\n JSON.stringify({ version: 3, routes: vercelRoutes }, null, 2),\r\n);\r\nfs.writeFileSync(\r\n path.resolve('vercel.json'),\r\n JSON.stringify({ runtime: 'nodejs20.x' }, null, 2),\r\n);\r\n\r\n// \u2500\u2500\u2500 Static assets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nawait buildCombinedBundle(STATIC_DIR);\r\ncopyPublicFiles(PUBLIC_DIR, STATIC_DIR);\r\n\r\nconst fnCount = (apiRoutes.length > 0 ? 1 : 0) + (serverPages.length > 0 ? 1 : 0);\r\nconsole.log(`\\n\u2713 Vercel build complete \u2014 ${fnCount} function(s) \u2192 .vercel/output`);\r\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/builder.d.ts CHANGED
@@ -1,16 +1,10 @@
1
1
  /**
2
2
  * builder.ts — NukeJS Package Build Script
3
3
  *
4
- * Compiles the NukeJS source into dist/ with two separate esbuild passes:
5
- *
6
- * Pass 1 (main): All src/ files excluding as-is/, compiled to Node ESM.
7
- * Pass 2 (as-is): Link.tsx + useRouter.ts compiled to browser-neutral ESM,
8
- * then the original .ts/.tsx sources are also copied into
9
- * dist/as-is/ so end-users can reference them directly.
10
- *
11
- * After both passes, processDist() rewrites bare relative imports
12
- * (e.g. `from './utils'`) to include .js extensions, which is required for
13
- * Node's strict ESM resolver.
4
+ * Compiles the NukeJS source into dist/ via a single esbuild pass targeting
5
+ * Node ESM, followed by processDist() which rewrites bare relative imports
6
+ * (e.g. `from './utils'`) to include .js extensions as required by Node's
7
+ * strict ESM resolver.
14
8
  *
15
9
  * Finally, `tsc --emitDeclarationOnly` generates .d.ts files for consumers.
16
10
  */
package/dist/builder.js CHANGED
@@ -6,7 +6,6 @@ import { fileURLToPath } from "url";
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
7
  const srcDir = path.resolve(__dirname, "");
8
8
  const outDir = path.resolve(__dirname, "../dist");
9
- const AS_IS = "as-is";
10
9
  function cleanDist(dir) {
11
10
  if (!fs.existsSync(dir)) return;
12
11
  fs.rmSync(dir, { recursive: true, force: true });
@@ -24,44 +23,28 @@ function collectFiles(dir, exclude = []) {
24
23
  }
25
24
  return files;
26
25
  }
27
- function copyDir(src, dest) {
28
- if (!fs.existsSync(src)) return;
29
- fs.mkdirSync(dest, { recursive: true });
30
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
31
- const s = path.join(src, entry.name);
32
- const d = path.join(dest, entry.name);
33
- entry.isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d);
34
- }
35
- }
36
26
  function processDist(dir) {
37
- const excludeFolder = "as-is";
38
27
  (function walk(currentDir) {
39
28
  fs.readdirSync(currentDir, { withFileTypes: true }).forEach((d) => {
40
29
  const fullPath = path.join(currentDir, d.name);
41
30
  if (d.isDirectory()) {
42
- if (d.name !== excludeFolder) walk(fullPath);
31
+ walk(fullPath);
43
32
  } else if (fullPath.endsWith(".js")) {
44
33
  let content = fs.readFileSync(fullPath, "utf-8");
45
- content = content.replace(
46
- /from\s+['"](\.\/(?!as-is\/).*?)['"]/g,
47
- 'from "$1.js"'
48
- );
49
- content = content.replace(
50
- /import\(['"](\.\/(?!as-is\/).*?)['"]\)/g,
51
- 'import("$1.js")'
52
- );
34
+ content = content.replace(/from\s+['"](\.\/.*?)['"]/g, 'from "$1.js"');
35
+ content = content.replace(/import\(['"](\.\/.*?)['"]\)/g, 'import("$1.js")');
53
36
  fs.writeFileSync(fullPath, content, "utf-8");
54
37
  }
55
38
  });
56
39
  })(dir);
57
- console.log("\u{1F527} Post-processing done: .ts imports \u2192 .js (excluding as-is folder).");
40
+ console.log("\u{1F527} Post-processing done: relative imports \u2192 .js extensions.");
58
41
  }
59
42
  async function runBuild() {
60
43
  try {
61
44
  cleanDist(outDir);
62
- console.log("\u{1F680} Building main sources\u2026");
45
+ console.log("\u{1F680} Building sources\u2026");
63
46
  await build({
64
- entryPoints: collectFiles(srcDir, [AS_IS]),
47
+ entryPoints: collectFiles(srcDir),
65
48
  outdir: outDir,
66
49
  platform: "node",
67
50
  format: "esm",
@@ -69,21 +52,7 @@ async function runBuild() {
69
52
  packages: "external",
70
53
  sourcemap: true
71
54
  });
72
- console.log("\u2705 Main build done.");
73
- console.log("\u{1F680} Building as-is sources\u2026");
74
- await build({
75
- entryPoints: collectFiles(path.join(srcDir, AS_IS)),
76
- outdir: path.join(outDir, AS_IS),
77
- platform: "neutral",
78
- format: "esm",
79
- target: ["node20"],
80
- packages: "external",
81
- jsx: "automatic",
82
- sourcemap: true
83
- });
84
- console.log("\u2705 as-is build done.");
85
- copyDir(path.join(srcDir, AS_IS), path.join(outDir, AS_IS));
86
- console.log(`\u{1F4C1} Copied as-is sources \u2192 dist/${AS_IS}/`);
55
+ console.log("\u2705 Build done.");
87
56
  processDist(outDir);
88
57
  console.log("\u{1F4C4} Generating TypeScript declarations\u2026");
89
58
  execSync("tsc --emitDeclarationOnly --declaration --outDir dist", { stdio: "inherit" });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/builder.ts"],
4
- "sourcesContent": ["/**\n * builder.ts \u2014 NukeJS Package Build Script\n *\n * Compiles the NukeJS source into dist/ with two separate esbuild passes:\n *\n * Pass 1 (main): All src/ files excluding as-is/, compiled to Node ESM.\n * Pass 2 (as-is): Link.tsx + useRouter.ts compiled to browser-neutral ESM,\n * then the original .ts/.tsx sources are also copied into\n * dist/as-is/ so end-users can reference them directly.\n *\n * After both passes, processDist() rewrites bare relative imports\n * (e.g. `from './utils'`) to include .js extensions, which is required for\n * Node's strict ESM resolver.\n *\n * Finally, `tsc --emitDeclarationOnly` generates .d.ts files for consumers.\n */\n\nimport { build } from 'esbuild';\nimport fs from 'fs';\nimport { execSync } from 'child_process';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst srcDir = path.resolve(__dirname, '');\nconst outDir = path.resolve(__dirname, '../dist');\nconst AS_IS = 'as-is';\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction cleanDist(dir: string): void {\n if (!fs.existsSync(dir)) return;\n fs.rmSync(dir, { recursive: true, force: true });\n console.log(`\uD83D\uDDD1\uFE0F Cleared ${dir}`);\n}\n\n/** Collects all .ts/.tsx/.js/.jsx files under `dir`, skipping `exclude` dirs. */\nfunction collectFiles(dir: string, exclude: string[] = []): string[] {\n const files: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (!exclude.includes(entry.name)) files.push(...collectFiles(full, exclude));\n } else if (/\\.[tj]sx?$/.test(entry.name)) {\n files.push(full);\n }\n }\n return files;\n}\n\n/**\n * Copies a directory recursively, preserving structure.\n * Used to place the original as-is .ts/.tsx sources into dist/as-is/\n * so end-users can read and copy them.\n */\nfunction copyDir(src: string, dest: string): void {\n if (!fs.existsSync(src)) return;\n fs.mkdirSync(dest, { recursive: true });\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n const s = path.join(src, entry.name);\n const d = path.join(dest, entry.name);\n entry.isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d);\n }\n}\n\n// --- Post-process .js files ---\nfunction processDist(dir: string) {\n const excludeFolder = \"as-is\";\n\n (function walk(currentDir: string) {\n fs.readdirSync(currentDir, { withFileTypes: true }).forEach((d) => {\n const fullPath = path.join(currentDir, d.name);\n\n if (d.isDirectory()) {\n if (d.name !== excludeFolder) walk(fullPath);\n } else if (fullPath.endsWith(\".js\")) {\n let content = fs.readFileSync(fullPath, \"utf-8\");\n\n // Replace import/export paths ending with .ts \u2192 .js, skip paths containing excludeFolder\n content = content.replace(\n /from\\s+['\"](\\.\\/(?!as-is\\/).*?)['\"]/g,\n 'from \"$1.js\"'\n );\n content = content.replace(\n /import\\(['\"](\\.\\/(?!as-is\\/).*?)['\"]\\)/g,\n 'import(\"$1.js\")'\n );\n\n fs.writeFileSync(fullPath, content, \"utf-8\");\n }\n });\n })(dir);\n\n console.log(\"\uD83D\uDD27 Post-processing done: .ts imports \u2192 .js (excluding as-is folder).\");\n}\n\n// \u2500\u2500\u2500 Build \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function runBuild(): Promise<void> {\n try {\n cleanDist(outDir);\n\n // Pass 1: main source (Node platform, no JSX needed)\n console.log('\uD83D\uDE80 Building main sources\u2026');\n await build({\n entryPoints: collectFiles(srcDir, [AS_IS]),\n outdir: outDir,\n platform: 'node',\n format: 'esm',\n target: ['node20'],\n packages: 'external',\n sourcemap: true,\n });\n console.log('\u2705 Main build done.');\n\n // Pass 2: as-is sources (browser-neutral, needs JSX)\n console.log('\uD83D\uDE80 Building as-is sources\u2026');\n await build({\n entryPoints: collectFiles(path.join(srcDir, AS_IS)),\n outdir: path.join(outDir, AS_IS),\n platform: 'neutral',\n format: 'esm',\n target: ['node20'],\n packages: 'external',\n jsx: 'automatic',\n sourcemap: true,\n });\n console.log('\u2705 as-is build done.');\n\n // Copy original .ts/.tsx sources into dist/as-is/ for end-user reference\n copyDir(path.join(srcDir, AS_IS), path.join(outDir, AS_IS));\n console.log(`\uD83D\uDCC1 Copied as-is sources \u2192 dist/${AS_IS}/`);\n\n // Fix ESM import extensions across all compiled output\n processDist(outDir);\n\n // Emit .d.ts declaration files\n console.log('\uD83D\uDCC4 Generating TypeScript declarations\u2026');\n execSync('tsc --emitDeclarationOnly --declaration --outDir dist', { stdio: 'inherit' });\n\n console.log('\\n\uD83C\uDF89 Build complete \u2192 dist/');\n } catch (err) {\n console.error('\u274C Build failed:', err);\n process.exit(1);\n }\n}\n\nrunBuild();"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\r\n * builder.ts \u2014 NukeJS Package Build Script\r\n *\r\n * Compiles the NukeJS source into dist/ via a single esbuild pass targeting\r\n * Node ESM, followed by processDist() which rewrites bare relative imports\r\n * (e.g. `from './utils'`) to include .js extensions as required by Node's\r\n * strict ESM resolver.\r\n *\r\n * Finally, `tsc --emitDeclarationOnly` generates .d.ts files for consumers.\r\n */\r\n\r\nimport { build } from 'esbuild';\r\nimport fs from 'fs';\r\nimport { execSync } from 'child_process';\r\nimport path from 'path';\r\nimport { fileURLToPath } from 'url';\r\n\r\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\r\nconst srcDir = path.resolve(__dirname, '');\r\nconst outDir = path.resolve(__dirname, '../dist');\r\n\r\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfunction cleanDist(dir: string): void {\r\n if (!fs.existsSync(dir)) return;\r\n fs.rmSync(dir, { recursive: true, force: true });\r\n console.log(`\uD83D\uDDD1\uFE0F Cleared ${dir}`);\r\n}\r\n\r\n/** Collects all .ts/.tsx/.js/.jsx files under `dir`, skipping `exclude` dirs. */\r\nfunction collectFiles(dir: string, exclude: string[] = []): string[] {\r\n const files: string[] = [];\r\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\r\n const full = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n if (!exclude.includes(entry.name)) files.push(...collectFiles(full, exclude));\r\n } else if (/\\.[tj]sx?$/.test(entry.name)) {\r\n files.push(full);\r\n }\r\n }\r\n return files;\r\n}\r\n\r\n// \u2500\u2500\u2500 Post-process .js files \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/** Rewrites bare relative imports to include .js extensions for Node ESM. */\r\nfunction processDist(dir: string) {\r\n (function walk(currentDir: string) {\r\n fs.readdirSync(currentDir, { withFileTypes: true }).forEach((d) => {\r\n const fullPath = path.join(currentDir, d.name);\r\n if (d.isDirectory()) {\r\n walk(fullPath);\r\n } else if (fullPath.endsWith('.js')) {\r\n let content = fs.readFileSync(fullPath, 'utf-8');\r\n content = content.replace(/from\\s+['\"](\\.\\/.*?)['\"]/g, 'from \"$1.js\"');\r\n content = content.replace(/import\\(['\"](\\.\\/.*?)['\"]\\)/g, 'import(\"$1.js\")');\r\n fs.writeFileSync(fullPath, content, 'utf-8');\r\n }\r\n });\r\n })(dir);\r\n\r\n console.log('\uD83D\uDD27 Post-processing done: relative imports \u2192 .js extensions.');\r\n}\r\n\r\n// \u2500\u2500\u2500 Build \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nasync function runBuild(): Promise<void> {\r\n try {\r\n cleanDist(outDir);\r\n\r\n console.log('\uD83D\uDE80 Building sources\u2026');\r\n await build({\r\n entryPoints: collectFiles(srcDir),\r\n outdir: outDir,\r\n platform: 'node',\r\n format: 'esm',\r\n target: ['node20'],\r\n packages: 'external',\r\n sourcemap: true,\r\n });\r\n console.log('\u2705 Build done.');\r\n\r\n processDist(outDir);\r\n\r\n console.log('\uD83D\uDCC4 Generating TypeScript declarations\u2026');\r\n execSync('tsc --emitDeclarationOnly --declaration --outDir dist', { stdio: 'inherit' });\r\n\r\n console.log('\\n\uD83C\uDF89 Build complete \u2192 dist/');\r\n } catch (err) {\r\n console.error('\u274C Build failed:', err);\r\n process.exit(1);\r\n }\r\n}\r\n\r\nrunBuild();"],
5
+ "mappings": "AAWA,SAAS,aAAa;AACtB,OAAO,QAAQ;AACf,SAAS,gBAAgB;AACzB,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC7D,MAAM,SAAS,KAAK,QAAQ,WAAW,EAAE;AACzC,MAAM,SAAS,KAAK,QAAQ,WAAW,SAAS;AAIhD,SAAS,UAAU,KAAmB;AACpC,MAAI,CAAC,GAAG,WAAW,GAAG,EAAG;AACzB,KAAG,OAAO,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC/C,UAAQ,IAAI,4BAAgB,GAAG,EAAE;AACnC;AAGA,SAAS,aAAa,KAAa,UAAoB,CAAC,GAAa;AACnE,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;AAChE,UAAM,OAAO,KAAK,KAAK,KAAK,MAAM,IAAI;AACtC,QAAI,MAAM,YAAY,GAAG;AACvB,UAAI,CAAC,QAAQ,SAAS,MAAM,IAAI,EAAG,OAAM,KAAK,GAAG,aAAa,MAAM,OAAO,CAAC;AAAA,IAC9E,WAAW,aAAa,KAAK,MAAM,IAAI,GAAG;AACxC,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,YAAY,KAAa;AAChC,GAAC,SAAS,KAAK,YAAoB;AACjC,OAAG,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM;AACjE,YAAM,WAAW,KAAK,KAAK,YAAY,EAAE,IAAI;AAC7C,UAAI,EAAE,YAAY,GAAG;AACnB,aAAK,QAAQ;AAAA,MACf,WAAW,SAAS,SAAS,KAAK,GAAG;AACnC,YAAI,UAAU,GAAG,aAAa,UAAU,OAAO;AAC/C,kBAAU,QAAQ,QAAQ,6BAA6B,cAAc;AACrE,kBAAU,QAAQ,QAAQ,gCAAgC,iBAAiB;AAC3E,WAAG,cAAc,UAAU,SAAS,OAAO;AAAA,MAC7C;AAAA,IACF,CAAC;AAAA,EACH,GAAG,GAAG;AAEN,UAAQ,IAAI,yEAA6D;AAC3E;AAIA,eAAe,WAA0B;AACvC,MAAI;AACF,cAAU,MAAM;AAEhB,YAAQ,IAAI,mCAAuB;AACnC,UAAM,MAAM;AAAA,MACV,aAAa,aAAa,MAAM;AAAA,MAChC,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,QAAQ,CAAC,QAAQ;AAAA,MACjB,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AACD,YAAQ,IAAI,qBAAgB;AAE5B,gBAAY,MAAM;AAElB,YAAQ,IAAI,qDAAyC;AACrD,aAAS,yDAAyD,EAAE,OAAO,UAAU,CAAC;AAEtF,YAAQ,IAAI,0CAA8B;AAAA,EAC5C,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAoB,GAAG;AACrC,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,SAAS;",
6
6
  "names": []
7
7
  }
package/dist/bundle.js CHANGED
@@ -141,21 +141,76 @@ function fingerprint(el) {
141
141
  function syncHeadTags(doc) {
142
142
  const live = headBlock(document.head);
143
143
  const next = headBlock(doc.head);
144
- const liveMap = /* @__PURE__ */ new Map();
145
- for (const el of live.nodes) liveMap.set(fingerprint(el), el);
146
- const nextMap = /* @__PURE__ */ new Map();
147
- for (const el of next.nodes) nextMap.set(fingerprint(el), el);
148
144
  let anchor = live.closeComment;
149
145
  if (!anchor) {
150
146
  document.head.appendChild(document.createComment("n-head"));
151
147
  anchor = document.createComment("/n-head");
152
148
  document.head.appendChild(anchor);
153
149
  }
150
+ for (const el of live.nodes)
151
+ if (el.tagName === "SCRIPT") el.remove();
152
+ for (const el of next.nodes) {
153
+ if (el.tagName === "SCRIPT")
154
+ document.head.insertBefore(cloneScriptForExecution(el), anchor);
155
+ }
156
+ const liveMap = /* @__PURE__ */ new Map();
157
+ for (const el of live.nodes) if (el.tagName !== "SCRIPT") liveMap.set(fingerprint(el), el);
158
+ const nextMap = /* @__PURE__ */ new Map();
159
+ for (const el of next.nodes) if (el.tagName !== "SCRIPT") nextMap.set(fingerprint(el), el);
154
160
  for (const [fp, el] of nextMap)
155
161
  if (!liveMap.has(fp)) document.head.insertBefore(el, anchor);
156
162
  for (const [fp, el] of liveMap)
157
163
  if (!nextMap.has(fp)) el.remove();
158
164
  }
165
+ function bodyScriptsBlock(body) {
166
+ const nodes = [];
167
+ let closeComment = null;
168
+ let inside = false;
169
+ for (const child of Array.from(body.childNodes)) {
170
+ if (child.nodeType === Node.COMMENT_NODE) {
171
+ const text = child.data.trim();
172
+ if (text === "n-body-scripts") {
173
+ inside = true;
174
+ continue;
175
+ }
176
+ if (text === "/n-body-scripts") {
177
+ closeComment = child;
178
+ inside = false;
179
+ continue;
180
+ }
181
+ }
182
+ if (inside && child.nodeType === Node.ELEMENT_NODE)
183
+ nodes.push(child);
184
+ }
185
+ return { nodes, closeComment };
186
+ }
187
+ function cloneScriptForExecution(src) {
188
+ const el = document.createElement("script");
189
+ for (const { name, value } of Array.from(src.attributes)) {
190
+ if (name === "src") {
191
+ const url = new URL(value, location.href);
192
+ url.searchParams.set("t", String(Date.now()));
193
+ el.setAttribute("src", url.toString());
194
+ } else {
195
+ el.setAttribute(name, value);
196
+ }
197
+ }
198
+ if (src.textContent) el.textContent = src.textContent;
199
+ return el;
200
+ }
201
+ function syncBodyScripts(doc) {
202
+ const live = bodyScriptsBlock(document.body);
203
+ const next = bodyScriptsBlock(doc.body);
204
+ for (const el of live.nodes) el.remove();
205
+ let anchor = live.closeComment;
206
+ if (!anchor) {
207
+ document.body.appendChild(document.createComment("n-body-scripts"));
208
+ anchor = document.createComment("/n-body-scripts");
209
+ document.body.appendChild(anchor);
210
+ }
211
+ for (const el of next.nodes)
212
+ document.body.insertBefore(cloneScriptForExecution(el), anchor);
213
+ }
159
214
  function syncAttrs(live, next) {
160
215
  for (const { name, value } of Array.from(next.attributes))
161
216
  live.setAttribute(name, value);
@@ -177,6 +232,7 @@ function setupNavigation(log) {
177
232
  const currApp = document.getElementById("app");
178
233
  if (!newApp || !currApp) return;
179
234
  syncHeadTags(doc);
235
+ syncBodyScripts(doc);
180
236
  syncAttrs(document.documentElement, doc.documentElement);
181
237
  syncAttrs(document.body, doc.body);
182
238
  currApp.innerHTML = newApp.innerHTML;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/bundle.ts"],
4
- "sourcesContent": ["/**\r\n * bundle.ts \u2014 NukeJS Client Runtime\r\n *\r\n * This file is compiled by esbuild into /__n.js and served to every page.\r\n * It provides:\r\n *\r\n * initRuntime(data) \u2014 called once per page load to hydrate\r\n * \"use client\" components and wire up SPA nav\r\n * setupLocationChangeMonitor() \u2014 patches history.pushState/replaceState so\r\n * SPA navigation fires a 'locationchange' event\r\n *\r\n * Hydration model (partial hydration):\r\n * - The server renders the full page to HTML, wrapping each client component\r\n * in a <span data-hydrate-id=\"cc_\u2026\" data-hydrate-props=\"\u2026\"> marker.\r\n * - initRuntime loads the matching JS bundle for each marker and calls\r\n * hydrateRoot() on it, letting React take over just that subtree.\r\n * - Props serialized by the server may include nested React elements\r\n * (serialized as { __re: 'html'|'client', \u2026 }), which are reconstructed\r\n * back into React.createElement calls before mounting.\r\n *\r\n * SPA navigation:\r\n * - Link clicks / programmatic navigation dispatch a 'locationchange' event.\r\n * - The handler fetches the target URL as HTML, diffs the #app container,\r\n * unmounts the old React roots, and re-hydrates the new ones.\r\n * - HMR navigations add ?__hmr=1 so the server skips client-SSR (faster).\r\n *\r\n * Head tag management:\r\n * - The SSR renderer wraps every useHtml()-generated <meta>, <link>, <style>,\r\n * and <script> tag in <!--n-head-->\u2026<!--/n-head--> sentinel comments.\r\n * - On each navigation the client diffs the live sentinel block against the\r\n * incoming one by fingerprint, adding new tags and removing gone ones.\r\n * Tags shared between pages (e.g. a layout stylesheet) are left untouched\r\n * so there is no removal/re-insertion flash.\r\n * - New tags are always inserted before <!--/n-head--> so they stay inside\r\n * the tracked block and remain visible to the diff on subsequent navigations.\r\n */\r\n\r\n// \u2500\u2500\u2500 History patch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Patches history.pushState and history.replaceState to fire a custom\r\n * 'locationchange' event on window. Also listens to 'popstate' for\r\n * back/forward navigation.\r\n *\r\n * Called after initRuntime sets up the navigation listener so there is no\r\n * race between the event firing and the listener being registered.\r\n */\r\nexport function setupLocationChangeMonitor(): void {\r\n const originalPushState = window.history.pushState.bind(window.history);\r\n const originalReplaceState = window.history.replaceState.bind(window.history);\r\n\r\n const dispatch = (href?: any) =>\r\n window.dispatchEvent(new CustomEvent('locationchange', { detail: { href } }));\r\n\r\n window.history.pushState = function (...args) {\r\n originalPushState(...args);\r\n dispatch(args[2]); // args[2] is the URL\r\n };\r\n\r\n window.history.replaceState = function (...args) {\r\n originalReplaceState(...args);\r\n dispatch(args[2]);\r\n };\r\n\r\n // Back/forward navigation via the browser's native UI.\r\n window.addEventListener('popstate', () => dispatch(window.location.pathname));\r\n}\r\n\r\n// \u2500\u2500\u2500 Logger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\ntype ClientDebugLevel = 'silent' | 'error' | 'info' | 'verbose';\r\n\r\n/**\r\n * Returns a thin logger whose methods are no-ops unless `level` allows them.\r\n * The server embeds the active debug level in the __n_data JSON blob so the\r\n * client respects the same setting as the server.\r\n */\r\nfunction makeLogger(level: ClientDebugLevel) {\r\n return {\r\n verbose: (...a: any[]) => { if (level === 'verbose') console.log(...a); },\r\n info: (...a: any[]) => { if (level === 'verbose' || level === 'info') console.log(...a); },\r\n warn: (...a: any[]) => { if (level === 'verbose' || level === 'info') console.warn(...a); },\r\n error: (...a: any[]) => { if (level !== 'silent') console.error(...a); },\r\n };\r\n}\r\n\r\n// \u2500\u2500\u2500 Serialized node types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/** The wire format for React elements embedded in hydration props. */\r\ntype SerializedNode =\r\n | null\r\n | undefined\r\n | string\r\n | number\r\n | boolean\r\n | SerializedNode[]\r\n | { __re: 'html'; tag: string; props: Record<string, any> }\r\n | { __re: 'client'; componentId: string; props: Record<string, any> }\r\n | Record<string, any>;\r\n\r\ntype ModuleMap = Map<string, any>; // componentId \u2192 default export\r\n\r\n// \u2500\u2500\u2500 Prop reconstruction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Recursively turns the server's serialized node tree back into real React\r\n * elements so they can be passed as props to hydrated components.\r\n *\r\n * The server serializes JSX passed as props (e.g. `<Button icon={<Icon />}>`)\r\n * into a JSON-safe format. This function reverses that process.\r\n */\r\nasync function reconstructElement(node: SerializedNode, mods: ModuleMap): Promise<any> {\r\n if (node === null || node === undefined) return node;\r\n if (typeof node !== 'object') return node; // primitive \u2014 pass through\r\n\r\n if (Array.isArray(node)) {\r\n const items = await Promise.all(node.map(n => reconstructElement(n, mods)));\r\n // Add index-based keys to React elements in the array to avoid the\r\n // \"Each child in a list should have a unique key prop\" warning.\r\n const React = await import('react');\r\n return items.map((el, i) =>\r\n el && typeof el === 'object' && el.$$typeof\r\n ? React.default.cloneElement(el, { key: el.key ?? i })\r\n : el,\r\n );\r\n }\r\n\r\n // Client component \u2014 look up the loaded module by ID.\r\n if ((node as any).__re === 'client') {\r\n const n = node as { __re: 'client'; componentId: string; props: Record<string, any> };\r\n const Comp = mods.get(n.componentId);\r\n if (!Comp) return null;\r\n const React = await import('react');\r\n return React.default.createElement(Comp, await reconstructProps(n.props, mods));\r\n }\r\n\r\n // Native HTML element (e.g. <div>, <span>).\r\n if ((node as any).__re === 'html') {\r\n const n = node as { __re: 'html'; tag: string; props: Record<string, any> };\r\n const React = await import('react');\r\n return React.default.createElement(n.tag, await reconstructProps(n.props, mods));\r\n }\r\n\r\n // Plain object \u2014 pass through as-is.\r\n return node;\r\n}\r\n\r\n/** Reconstructs every value in a props object, handling nested serialized nodes. */\r\nasync function reconstructProps(\r\n props: Record<string, any> | null | undefined,\r\n mods: ModuleMap,\r\n): Promise<Record<string, any>> {\r\n if (!props || typeof props !== 'object' || Array.isArray(props))\r\n return reconstructElement(props as any, mods);\r\n\r\n const out: Record<string, any> = {};\r\n for (const [k, v] of Object.entries(props))\r\n out[k] = await reconstructElement(v, mods);\r\n return out;\r\n}\r\n\r\n// \u2500\u2500\u2500 Module loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Dynamically imports each client component bundle from /__client-component/.\r\n * All fetches are issued in parallel; failures are logged but do not abort\r\n * the rest of the hydration pass.\r\n *\r\n * @param bust Optional cache-busting suffix appended as `?t=<bust>`.\r\n * Used during HMR navigation to bypass the module cache.\r\n */\r\nasync function loadModules(\r\n ids: string[],\r\n log: ReturnType<typeof makeLogger>,\r\n bust = '',\r\n): Promise<ModuleMap> {\r\n const mods: ModuleMap = new Map();\r\n await Promise.all(\r\n ids.map(async (id) => {\r\n try {\r\n const url = `/__client-component/${id}.js` + (bust ? `?t=${bust}` : '');\r\n const m = await import(url);\r\n mods.set(id, m.default);\r\n log.verbose('\u2713 Loaded:', id);\r\n } catch (err) {\r\n log.error('\u2717 Load failed:', id, err);\r\n }\r\n }),\r\n );\r\n return mods;\r\n}\r\n\r\n// \u2500\u2500\u2500 Root mounting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/** All active React roots \u2014 tracked so they can be unmounted before navigation. */\r\ntype ReactRoot = { unmount(): void };\r\nconst activeRoots: ReactRoot[] = [];\r\n\r\n/**\r\n * Finds every `[data-hydrate-id]` span in the document and calls hydrateRoot()\r\n * on it. hydrateRoot reconciles React's virtual DOM against the existing server\r\n * HTML without discarding it, which avoids a visible flash on both initial load\r\n * and SPA navigation (where we set innerHTML to fresh SSR output before calling\r\n * mountNodes).\r\n *\r\n * Nested markers are skipped \u2014 the parent's React tree owns its children.\r\n */\r\nasync function mountNodes(\r\n mods: ModuleMap,\r\n log: ReturnType<typeof makeLogger>,\r\n): Promise<void> {\r\n const { hydrateRoot, createRoot } = await import('react-dom/client');\r\n const React = await import('react');\r\n\r\n const nodes = document.querySelectorAll<HTMLElement>('[data-hydrate-id]');\r\n log.verbose('Found', nodes.length, 'hydration point(s)');\r\n\r\n for (const node of nodes) {\r\n // Skip nested markers \u2014 the outer component owns its children.\r\n if (node.parentElement?.closest('[data-hydrate-id]')) continue;\r\n\r\n const id = node.getAttribute('data-hydrate-id')!;\r\n const Comp = mods.get(id);\r\n if (!Comp) { log.warn('No module for', id); continue; }\r\n\r\n let rawProps: Record<string, any> = {};\r\n try {\r\n rawProps = JSON.parse(node.getAttribute('data-hydrate-props') || '{}');\r\n } catch (e) {\r\n log.error('Props parse error for', id, e);\r\n }\r\n\r\n try {\r\n const element = React.default.createElement(Comp, await reconstructProps(rawProps, mods));\r\n\r\n // hydrateRoot reconciles against existing server HTML (initial page load).\r\n // createRoot renders fresh when the span is empty (HMR path \u2014 server sent\r\n // skipClientSSR=true so the span has no pre-rendered content to reconcile).\r\n let root: ReactRoot;\r\n if (node.innerHTML.trim()) {\r\n root = hydrateRoot(node, element);\r\n } else {\r\n const r = createRoot(node);\r\n r.render(element);\r\n root = r;\r\n }\r\n\r\n activeRoots.push(root);\r\n log.verbose('\u2713 Mounted:', id);\r\n } catch (err) {\r\n log.error('\u2717 Mount failed:', id, err);\r\n }\r\n }\r\n}\r\n\r\n// \u2500\u2500\u2500 Head tag sync \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Walks a <head> element and returns every Element node that lives between\r\n * the <!--n-head--> and <!--/n-head--> sentinel comments, plus the closing\r\n * comment node itself (used as the insertion anchor).\r\n *\r\n * The SSR renderer emits these sentinels around every useHtml()-generated tag\r\n * so the client can manage exactly that set without touching permanent tags\r\n * (charset, viewport, importmap, runtime <script>).\r\n */\r\nfunction headBlock(head: HTMLHeadElement): { nodes: Element[]; closeComment: Comment | null } {\r\n const nodes: Element[] = [];\r\n let closeComment: Comment | null = null;\r\n let inside = false;\r\n\r\n for (const child of Array.from(head.childNodes)) {\r\n if (child.nodeType === Node.COMMENT_NODE) {\r\n const text = (child as Comment).data.trim();\r\n if (text === 'n-head') { inside = true; continue; }\r\n if (text === '/n-head') { closeComment = child as Comment; inside = false; continue; }\r\n }\r\n if (inside && child.nodeType === Node.ELEMENT_NODE)\r\n nodes.push(child as Element);\r\n }\r\n\r\n return { nodes, closeComment };\r\n}\r\n\r\n/** Stable key for an Element: tag name + sorted attribute list (name=value pairs). */\r\nfunction fingerprint(el: Element): string {\r\n return el.tagName + '|' + Array.from(el.attributes)\r\n .sort((a, b) => a.name.localeCompare(b.name))\r\n .map(a => `${a.name}=${a.value}`)\r\n .join('&');\r\n}\r\n\r\n/**\r\n * Diffs the live <!--n-head--> block against the incoming document's block and\r\n * applies the minimal set of DOM mutations:\r\n *\r\n * - Tags present in `next` but not in `live` \u2192 inserted before <!--/n-head-->\r\n * so they remain inside the tracked block on future navigations.\r\n * - Tags present in `live` but not in `next` \u2192 removed.\r\n * - Tags present in both \u2192 left untouched (no removal/re-insertion flash).\r\n *\r\n * If the live head has no sentinel block yet (e.g. initial page had no useHtml\r\n * tags), both sentinel comments are created on the fly.\r\n */\r\nfunction syncHeadTags(doc: Document): void {\r\n const live = headBlock(document.head);\r\n const next = headBlock(doc.head);\r\n\r\n const liveMap = new Map<string, Element>();\r\n for (const el of live.nodes) liveMap.set(fingerprint(el), el);\r\n\r\n const nextMap = new Map<string, Element>();\r\n for (const el of next.nodes) nextMap.set(fingerprint(el), el);\r\n\r\n // Ensure we have an anchor to insert before.\r\n let anchor = live.closeComment;\r\n if (!anchor) {\r\n document.head.appendChild(document.createComment('n-head'));\r\n anchor = document.createComment('/n-head');\r\n document.head.appendChild(anchor);\r\n }\r\n\r\n for (const [fp, el] of nextMap)\r\n if (!liveMap.has(fp)) document.head.insertBefore(el, anchor);\r\n\r\n for (const [fp, el] of liveMap)\r\n if (!nextMap.has(fp)) el.remove();\r\n}\r\n\r\n// \u2500\u2500\u2500 SPA navigation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Syncs attributes from a parsed element onto the live document element.\r\n * Adds/updates attributes present in `next` and removes any that were set\r\n * on `live` but are absent in `next` (clears stale htmlAttrs/bodyAttrs).\r\n */\r\nfunction syncAttrs(live: Element, next: Element): void {\r\n for (const { name, value } of Array.from(next.attributes))\r\n live.setAttribute(name, value);\r\n for (const { name } of Array.from(live.attributes))\r\n if (!next.hasAttribute(name)) live.removeAttribute(name);\r\n}\r\n\r\n/**\r\n * Listens for 'locationchange' events and performs a soft navigation:\r\n *\r\n * 1. Fetch the target URL as HTML (?__hmr=1 skips client-SSR for HMR speed).\r\n * 2. Parse the response with DOMParser.\r\n * 3. Apply all visual DOM changes first (head tags, html/body attrs, #app\r\n * innerHTML, title, __n_data) so the new content is painted before React\r\n * cleanup effects run \u2014 prevents a useHtml restore from briefly undoing\r\n * the new document state.\r\n * 4. Unmount old React roots (runs cleanup effects against the already-updated DOM).\r\n * 5. Re-hydrate new client component markers.\r\n * 6. Scroll to top.\r\n *\r\n * Falls back to a full page reload if anything goes wrong.\r\n */\r\nfunction setupNavigation(log: ReturnType<typeof makeLogger>): void {\r\n window.addEventListener('locationchange', async ({ detail: { href, hmr } }: any) => {\r\n try {\r\n const fetchUrl = hmr\r\n ? href + (href.includes('?') ? '&' : '?') + '__hmr=1'\r\n : href;\r\n\r\n const response = await fetch(fetchUrl, { headers: { Accept: 'text/html' } });\r\n if (!response.ok) {\r\n log.error('Navigation fetch failed:', response.status);\r\n return;\r\n }\r\n\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(await response.text(), 'text/html');\r\n const newApp = doc.getElementById('app');\r\n const currApp = document.getElementById('app');\r\n if (!newApp || !currApp) return;\r\n\r\n // \u2500\u2500 Visual update \u2014 all DOM mutations before React teardown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n // Styles must be in place before new content appears to avoid an unstyled\r\n // flash. Unmounting runs useEffect cleanups (including useHtml restores)\r\n // which would temporarily revert document state if done first.\r\n\r\n // 1. Head tags \u2014 diff-based sync preserves shared layout tags untouched.\r\n syncHeadTags(doc);\r\n\r\n // 2. <html> and <body> attributes (lang, class, style, etc.).\r\n syncAttrs(document.documentElement, doc.documentElement);\r\n syncAttrs(document.body, doc.body);\r\n\r\n // 3. Page content.\r\n currApp.innerHTML = newApp.innerHTML;\r\n\r\n // 4. <title>.\r\n const newTitle = doc.querySelector('title');\r\n if (newTitle) document.title = newTitle.textContent ?? '';\r\n\r\n // 5. Runtime data blob \u2014 must come after innerHTML swap so the new\r\n // __n_data element is part of the live document.\r\n const newDataEl = doc.getElementById('__n_data');\r\n const currDataEl = document.getElementById('__n_data');\r\n if (newDataEl && currDataEl) currDataEl.textContent = newDataEl.textContent;\r\n\r\n // \u2500\u2500 React teardown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n // Unmount after the visual update. Cleanup effects now run against an\r\n // already-updated document, so there is nothing left to visually undo.\r\n activeRoots.splice(0).forEach(r => r.unmount());\r\n\r\n // \u2500\u2500 Re-hydration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n const navData = JSON.parse(currDataEl?.textContent ?? '{}') as RuntimeData;\r\n log.info('\uD83D\uDD04 Route \u2192', href, '\u2014 mounting', navData.hydrateIds?.length ?? 0, 'component(s)');\r\n\r\n const mods = await loadModules(navData.allIds ?? [], log, String(Date.now()));\r\n await mountNodes(mods, log);\r\n\r\n window.scrollTo(0, 0);\r\n log.info('\uD83C\uDF89 Navigation complete:', href);\r\n } catch (err) {\r\n log.error('Navigation error, falling back to full reload:', err);\r\n window.location.href = href;\r\n }\r\n });\r\n}\r\n\r\n// \u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/** Shape of the JSON blob embedded as #__n_data in every SSR page. */\r\nexport interface RuntimeData {\r\n /** IDs of client components actually rendered on this page (subset of allIds). */\r\n hydrateIds: string[];\r\n /** All client component IDs reachable from this page, including layouts.\r\n * Pre-loaded so SPA navigations to related pages feel instant. */\r\n allIds: string[];\r\n url: string;\r\n params: Record<string, any>;\r\n debug: ClientDebugLevel;\r\n}\r\n\r\n/**\r\n * Bootstraps the NukeJS client runtime.\r\n *\r\n * Called once per page load from the inline <script type=\"module\"> injected\r\n * by the SSR renderer:\r\n *\r\n * ```js\r\n * const { initRuntime } = await import('nukejs');\r\n * const data = JSON.parse(document.getElementById('__n_data').textContent);\r\n * await initRuntime(data);\r\n * ```\r\n *\r\n * Order of operations:\r\n * 1. Create the logger at the configured debug level.\r\n * 2. Wire up SPA navigation listener.\r\n * 3. Load all client component bundles in parallel.\r\n * 4. Hydrate every [data-hydrate-id] node.\r\n * 5. Patch history.pushState/replaceState so Link clicks trigger navigation.\r\n */\r\nexport async function initRuntime(data: RuntimeData): Promise<void> {\r\n const log = makeLogger(data.debug ?? 'silent');\r\n\r\n log.info('\uD83D\uDE80 Partial hydration:', data.hydrateIds.length, 'root component(s)');\r\n\r\n // Set up navigation first so any 'locationchange' fired during hydration\r\n // is captured (e.g. a redirect side-effect inside a component).\r\n setupNavigation(log);\r\n\r\n // Load all component bundles (not just hydrateIds) so SPA navigations to\r\n // related pages can mount their components without an extra network round-trip.\r\n const mods = await loadModules(data.allIds, log);\r\n await mountNodes(mods, log);\r\n\r\n log.info('\uD83C\uDF89 Done!');\r\n\r\n // Patch history last so pushState calls during hydration don't trigger a\r\n // navigation before roots are ready.\r\n setupLocationChangeMonitor();\r\n}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\r\n * bundle.ts \u2014 NukeJS Client Runtime\r\n *\r\n * This file is compiled by esbuild into /__n.js and served to every page.\r\n * It provides:\r\n *\r\n * initRuntime(data) \u2014 called once per page load to hydrate\r\n * \"use client\" components and wire up SPA nav\r\n * setupLocationChangeMonitor() \u2014 patches history.pushState/replaceState so\r\n * SPA navigation fires a 'locationchange' event\r\n *\r\n * Hydration model (partial hydration):\r\n * - The server renders the full page to HTML, wrapping each client component\r\n * in a <span data-hydrate-id=\"cc_\u2026\" data-hydrate-props=\"\u2026\"> marker.\r\n * - initRuntime loads the matching JS bundle for each marker and calls\r\n * hydrateRoot() on it, letting React take over just that subtree.\r\n * - Props serialized by the server may include nested React elements\r\n * (serialized as { __re: 'html'|'client', \u2026 }), which are reconstructed\r\n * back into React.createElement calls before mounting.\r\n *\r\n * SPA navigation:\r\n * - Link clicks / programmatic navigation dispatch a 'locationchange' event.\r\n * - The handler fetches the target URL as HTML, diffs the #app container,\r\n * unmounts the old React roots, and re-hydrates the new ones.\r\n * - HMR navigations add ?__hmr=1 so the server skips client-SSR (faster).\r\n *\r\n * Head tag management:\r\n * - The SSR renderer wraps every useHtml()-generated <meta>, <link>, <style>,\r\n * and <script> tag in <!--n-head-->\u2026<!--/n-head--> sentinel comments.\r\n * - On each navigation the client diffs the live sentinel block against the\r\n * incoming one by fingerprint, adding new tags and removing gone ones.\r\n * Tags shared between pages (e.g. a layout stylesheet) are left untouched\r\n * so there is no removal/re-insertion flash.\r\n * - New tags are always inserted before <!--/n-head--> so they stay inside\r\n * the tracked block and remain visible to the diff on subsequent navigations.\r\n */\r\n\r\n// \u2500\u2500\u2500 History patch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Patches history.pushState and history.replaceState to fire a custom\r\n * 'locationchange' event on window. Also listens to 'popstate' for\r\n * back/forward navigation.\r\n *\r\n * Called after initRuntime sets up the navigation listener so there is no\r\n * race between the event firing and the listener being registered.\r\n */\r\nexport function setupLocationChangeMonitor(): void {\r\n const originalPushState = window.history.pushState.bind(window.history);\r\n const originalReplaceState = window.history.replaceState.bind(window.history);\r\n\r\n const dispatch = (href?: any) =>\r\n window.dispatchEvent(new CustomEvent('locationchange', { detail: { href } }));\r\n\r\n window.history.pushState = function (...args) {\r\n originalPushState(...args);\r\n dispatch(args[2]); // args[2] is the URL\r\n };\r\n\r\n window.history.replaceState = function (...args) {\r\n originalReplaceState(...args);\r\n dispatch(args[2]);\r\n };\r\n\r\n // Back/forward navigation via the browser's native UI.\r\n window.addEventListener('popstate', () => dispatch(window.location.pathname));\r\n}\r\n\r\n// \u2500\u2500\u2500 Logger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\ntype ClientDebugLevel = 'silent' | 'error' | 'info' | 'verbose';\r\n\r\n/**\r\n * Returns a thin logger whose methods are no-ops unless `level` allows them.\r\n * The server embeds the active debug level in the __n_data JSON blob so the\r\n * client respects the same setting as the server.\r\n */\r\nfunction makeLogger(level: ClientDebugLevel) {\r\n return {\r\n verbose: (...a: any[]) => { if (level === 'verbose') console.log(...a); },\r\n info: (...a: any[]) => { if (level === 'verbose' || level === 'info') console.log(...a); },\r\n warn: (...a: any[]) => { if (level === 'verbose' || level === 'info') console.warn(...a); },\r\n error: (...a: any[]) => { if (level !== 'silent') console.error(...a); },\r\n };\r\n}\r\n\r\n// \u2500\u2500\u2500 Serialized node types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/** The wire format for React elements embedded in hydration props. */\r\ntype SerializedNode =\r\n | null\r\n | undefined\r\n | string\r\n | number\r\n | boolean\r\n | SerializedNode[]\r\n | { __re: 'html'; tag: string; props: Record<string, any> }\r\n | { __re: 'client'; componentId: string; props: Record<string, any> }\r\n | Record<string, any>;\r\n\r\ntype ModuleMap = Map<string, any>; // componentId \u2192 default export\r\n\r\n// \u2500\u2500\u2500 Prop reconstruction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Recursively turns the server's serialized node tree back into real React\r\n * elements so they can be passed as props to hydrated components.\r\n *\r\n * The server serializes JSX passed as props (e.g. `<Button icon={<Icon />}>`)\r\n * into a JSON-safe format. This function reverses that process.\r\n */\r\nasync function reconstructElement(node: SerializedNode, mods: ModuleMap): Promise<any> {\r\n if (node === null || node === undefined) return node;\r\n if (typeof node !== 'object') return node; // primitive \u2014 pass through\r\n\r\n if (Array.isArray(node)) {\r\n const items = await Promise.all(node.map(n => reconstructElement(n, mods)));\r\n // Add index-based keys to React elements in the array to avoid the\r\n // \"Each child in a list should have a unique key prop\" warning.\r\n const React = await import('react');\r\n return items.map((el, i) =>\r\n el && typeof el === 'object' && el.$$typeof\r\n ? React.default.cloneElement(el, { key: el.key ?? i })\r\n : el,\r\n );\r\n }\r\n\r\n // Client component \u2014 look up the loaded module by ID.\r\n if ((node as any).__re === 'client') {\r\n const n = node as { __re: 'client'; componentId: string; props: Record<string, any> };\r\n const Comp = mods.get(n.componentId);\r\n if (!Comp) return null;\r\n const React = await import('react');\r\n return React.default.createElement(Comp, await reconstructProps(n.props, mods));\r\n }\r\n\r\n // Native HTML element (e.g. <div>, <span>).\r\n if ((node as any).__re === 'html') {\r\n const n = node as { __re: 'html'; tag: string; props: Record<string, any> };\r\n const React = await import('react');\r\n return React.default.createElement(n.tag, await reconstructProps(n.props, mods));\r\n }\r\n\r\n // Plain object \u2014 pass through as-is.\r\n return node;\r\n}\r\n\r\n/** Reconstructs every value in a props object, handling nested serialized nodes. */\r\nasync function reconstructProps(\r\n props: Record<string, any> | null | undefined,\r\n mods: ModuleMap,\r\n): Promise<Record<string, any>> {\r\n if (!props || typeof props !== 'object' || Array.isArray(props))\r\n return reconstructElement(props as any, mods);\r\n\r\n const out: Record<string, any> = {};\r\n for (const [k, v] of Object.entries(props))\r\n out[k] = await reconstructElement(v, mods);\r\n return out;\r\n}\r\n\r\n// \u2500\u2500\u2500 Module loading \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Dynamically imports each client component bundle from /__client-component/.\r\n * All fetches are issued in parallel; failures are logged but do not abort\r\n * the rest of the hydration pass.\r\n *\r\n * @param bust Optional cache-busting suffix appended as `?t=<bust>`.\r\n * Used during HMR navigation to bypass the module cache.\r\n */\r\nasync function loadModules(\r\n ids: string[],\r\n log: ReturnType<typeof makeLogger>,\r\n bust = '',\r\n): Promise<ModuleMap> {\r\n const mods: ModuleMap = new Map();\r\n await Promise.all(\r\n ids.map(async (id) => {\r\n try {\r\n const url = `/__client-component/${id}.js` + (bust ? `?t=${bust}` : '');\r\n const m = await import(url);\r\n mods.set(id, m.default);\r\n log.verbose('\u2713 Loaded:', id);\r\n } catch (err) {\r\n log.error('\u2717 Load failed:', id, err);\r\n }\r\n }),\r\n );\r\n return mods;\r\n}\r\n\r\n// \u2500\u2500\u2500 Root mounting \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/** All active React roots \u2014 tracked so they can be unmounted before navigation. */\r\ntype ReactRoot = { unmount(): void };\r\nconst activeRoots: ReactRoot[] = [];\r\n\r\n/**\r\n * Finds every `[data-hydrate-id]` span in the document and calls hydrateRoot()\r\n * on it. hydrateRoot reconciles React's virtual DOM against the existing server\r\n * HTML without discarding it, which avoids a visible flash on both initial load\r\n * and SPA navigation (where we set innerHTML to fresh SSR output before calling\r\n * mountNodes).\r\n *\r\n * Nested markers are skipped \u2014 the parent's React tree owns its children.\r\n */\r\nasync function mountNodes(\r\n mods: ModuleMap,\r\n log: ReturnType<typeof makeLogger>,\r\n): Promise<void> {\r\n const { hydrateRoot, createRoot } = await import('react-dom/client');\r\n const React = await import('react');\r\n\r\n const nodes = document.querySelectorAll<HTMLElement>('[data-hydrate-id]');\r\n log.verbose('Found', nodes.length, 'hydration point(s)');\r\n\r\n for (const node of nodes) {\r\n // Skip nested markers \u2014 the outer component owns its children.\r\n if (node.parentElement?.closest('[data-hydrate-id]')) continue;\r\n\r\n const id = node.getAttribute('data-hydrate-id')!;\r\n const Comp = mods.get(id);\r\n if (!Comp) { log.warn('No module for', id); continue; }\r\n\r\n let rawProps: Record<string, any> = {};\r\n try {\r\n rawProps = JSON.parse(node.getAttribute('data-hydrate-props') || '{}');\r\n } catch (e) {\r\n log.error('Props parse error for', id, e);\r\n }\r\n\r\n try {\r\n const element = React.default.createElement(Comp, await reconstructProps(rawProps, mods));\r\n\r\n // hydrateRoot reconciles against existing server HTML (initial page load).\r\n // createRoot renders fresh when the span is empty (HMR path \u2014 server sent\r\n // skipClientSSR=true so the span has no pre-rendered content to reconcile).\r\n let root: ReactRoot;\r\n if (node.innerHTML.trim()) {\r\n root = hydrateRoot(node, element);\r\n } else {\r\n const r = createRoot(node);\r\n r.render(element);\r\n root = r;\r\n }\r\n\r\n activeRoots.push(root);\r\n log.verbose('\u2713 Mounted:', id);\r\n } catch (err) {\r\n log.error('\u2717 Mount failed:', id, err);\r\n }\r\n }\r\n}\r\n\r\n// \u2500\u2500\u2500 Head tag sync \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Walks a <head> element and returns every Element node that lives between\r\n * the <!--n-head--> and <!--/n-head--> sentinel comments, plus the closing\r\n * comment node itself (used as the insertion anchor).\r\n *\r\n * The SSR renderer emits these sentinels around every useHtml()-generated tag\r\n * so the client can manage exactly that set without touching permanent tags\r\n * (charset, viewport, importmap, runtime <script>).\r\n */\r\nfunction headBlock(head: HTMLHeadElement): { nodes: Element[]; closeComment: Comment | null } {\r\n const nodes: Element[] = [];\r\n let closeComment: Comment | null = null;\r\n let inside = false;\r\n\r\n for (const child of Array.from(head.childNodes)) {\r\n if (child.nodeType === Node.COMMENT_NODE) {\r\n const text = (child as Comment).data.trim();\r\n if (text === 'n-head') { inside = true; continue; }\r\n if (text === '/n-head') { closeComment = child as Comment; inside = false; continue; }\r\n }\r\n if (inside && child.nodeType === Node.ELEMENT_NODE)\r\n nodes.push(child as Element);\r\n }\r\n\r\n return { nodes, closeComment };\r\n}\r\n\r\n/** Stable key for an Element: tag name + sorted attribute list (name=value pairs). */\r\nfunction fingerprint(el: Element): string {\r\n return el.tagName + '|' + Array.from(el.attributes)\r\n .sort((a, b) => a.name.localeCompare(b.name))\r\n .map(a => `${a.name}=${a.value}`)\r\n .join('&');\r\n}\r\n\r\n/**\r\n * Diffs the live <!--n-head--> block against the incoming document's block and\r\n * applies the minimal set of DOM mutations:\r\n *\r\n * - Non-script tags (meta, link, style): fingerprint-diffed so shared layout\r\n * tags are left untouched (avoids stylesheet flash on navigation).\r\n * - Script tags: always removed and re-inserted as fresh elements so the\r\n * browser re-executes them and re-fetches any changed src file.\r\n * (Fingerprint diffing silently skips re-execution when src is unchanged.)\r\n *\r\n * If the live head has no sentinel block yet (e.g. initial page had no useHtml\r\n * tags), both sentinel comments are created on the fly.\r\n */\r\nfunction syncHeadTags(doc: Document): void {\r\n const live = headBlock(document.head);\r\n const next = headBlock(doc.head);\r\n\r\n // Ensure we have an anchor to insert before.\r\n let anchor = live.closeComment;\r\n if (!anchor) {\r\n document.head.appendChild(document.createComment('n-head'));\r\n anchor = document.createComment('/n-head');\r\n document.head.appendChild(anchor);\r\n }\r\n\r\n // \u2500\u2500 Scripts: always replace \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n // Remove all live script tags and re-insert fresh ones so the browser\r\n // executes them. src gets cache-busted so the latest file is fetched.\r\n for (const el of live.nodes)\r\n if (el.tagName === 'SCRIPT') el.remove();\r\n\r\n for (const el of next.nodes) {\r\n if (el.tagName === 'SCRIPT')\r\n document.head.insertBefore(cloneScriptForExecution(el), anchor);\r\n }\r\n\r\n // \u2500\u2500 Everything else: fingerprint diff \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n const liveMap = new Map<string, Element>();\r\n for (const el of live.nodes) if (el.tagName !== 'SCRIPT') liveMap.set(fingerprint(el), el);\r\n\r\n const nextMap = new Map<string, Element>();\r\n for (const el of next.nodes) if (el.tagName !== 'SCRIPT') nextMap.set(fingerprint(el), el);\r\n\r\n for (const [fp, el] of nextMap)\r\n if (!liveMap.has(fp)) document.head.insertBefore(el, anchor);\r\n\r\n for (const [fp, el] of liveMap)\r\n if (!nextMap.has(fp)) el.remove();\r\n}\r\n\r\n/**\r\n * Walks a <body> element and returns every Element node that lives between\r\n * the <!--n-body-scripts--> and <!--/n-body-scripts--> sentinel comments,\r\n * plus the closing comment node used as the insertion anchor.\r\n *\r\n * The SSR renderer emits these sentinels around every useHtml() body script\r\n * so the client can manage exactly that set without touching permanent nodes.\r\n */\r\nfunction bodyScriptsBlock(body: HTMLBodyElement | Element): { nodes: Element[]; closeComment: Comment | null } {\r\n const nodes: Element[] = [];\r\n let closeComment: Comment | null = null;\r\n let inside = false;\r\n\r\n for (const child of Array.from(body.childNodes)) {\r\n if (child.nodeType === Node.COMMENT_NODE) {\r\n const text = (child as Comment).data.trim();\r\n if (text === 'n-body-scripts') { inside = true; continue; }\r\n if (text === '/n-body-scripts') { closeComment = child as Comment; inside = false; continue; }\r\n }\r\n if (inside && child.nodeType === Node.ELEMENT_NODE)\r\n nodes.push(child as Element);\r\n }\r\n\r\n return { nodes, closeComment };\r\n}\r\n\r\n/**\r\n * Creates a fresh <script> element from a parsed source element so the browser\r\n * actually executes it when inserted into the live document.\r\n *\r\n * Why: browsers only execute a <script> that is *created and inserted* into\r\n * the live document. Nodes moved from a DOMParser document are auto-adopted\r\n * but their script is silently skipped. Cloning via createElement is required.\r\n *\r\n * Cache-busting: src-based scripts get a ?t=<timestamp> query appended so the\r\n * browser always fetches the latest version from the server on HMR updates,\r\n * bypassing the module/response cache.\r\n */\r\nfunction cloneScriptForExecution(src: Element): HTMLScriptElement {\r\n const el = document.createElement('script');\r\n for (const { name, value } of Array.from(src.attributes)) {\r\n if (name === 'src') {\r\n // Append a timestamp to force the browser to re-fetch the script file.\r\n const url = new URL(value, location.href);\r\n url.searchParams.set('t', String(Date.now()));\r\n el.setAttribute('src', url.toString());\r\n } else {\r\n el.setAttribute(name, value);\r\n }\r\n }\r\n // Copy inline content (for content-based scripts).\r\n if (src.textContent) el.textContent = src.textContent;\r\n return el;\r\n}\r\n\r\n/**\r\n * Replaces all body scripts in the <!--n-body-scripts--> sentinel block with\r\n * fresh elements from the incoming document.\r\n *\r\n * Unlike syncHeadTags (which diffs by fingerprint to avoid removing shared\r\n * stylesheets), body scripts must ALWAYS be removed and re-inserted so that:\r\n * - File changes picked up by HMR are actually executed by the browser.\r\n * - src-based scripts are cache-busted so the browser re-fetches them.\r\n *\r\n * Fingerprint diffing would silently skip re-execution of any script whose\r\n * src/attributes haven't changed, even if the file contents changed on disk.\r\n */\r\nfunction syncBodyScripts(doc: Document): void {\r\n const live = bodyScriptsBlock(document.body);\r\n const next = bodyScriptsBlock(doc.body);\r\n\r\n // Always remove every existing body script \u2014 never leave stale ones.\r\n for (const el of live.nodes) el.remove();\r\n\r\n // Ensure we have a sentinel anchor to insert before.\r\n let anchor = live.closeComment;\r\n if (!anchor) {\r\n document.body.appendChild(document.createComment('n-body-scripts'));\r\n anchor = document.createComment('/n-body-scripts');\r\n document.body.appendChild(anchor);\r\n }\r\n\r\n // Insert every script from the incoming document as a brand-new element\r\n // so the browser executes it. src gets a timestamp to bust any cache.\r\n for (const el of next.nodes)\r\n document.body.insertBefore(cloneScriptForExecution(el), anchor);\r\n}\r\n\r\n\r\n\r\n/**\r\n * Syncs attributes from a parsed element onto the live document element.\r\n * Adds/updates attributes present in `next` and removes any that were set\r\n * on `live` but are absent in `next` (clears stale htmlAttrs/bodyAttrs).\r\n */\r\nfunction syncAttrs(live: Element, next: Element): void {\r\n for (const { name, value } of Array.from(next.attributes))\r\n live.setAttribute(name, value);\r\n for (const { name } of Array.from(live.attributes))\r\n if (!next.hasAttribute(name)) live.removeAttribute(name);\r\n}\r\n\r\n/**\r\n * Listens for 'locationchange' events and performs a soft navigation:\r\n *\r\n * 1. Fetch the target URL as HTML (?__hmr=1 skips client-SSR for HMR speed).\r\n * 2. Parse the response with DOMParser.\r\n * 3. Apply all visual DOM changes first (head tags, html/body attrs, #app\r\n * innerHTML, title, __n_data) so the new content is painted before React\r\n * cleanup effects run \u2014 prevents a useHtml restore from briefly undoing\r\n * the new document state.\r\n * 4. Unmount old React roots (runs cleanup effects against the already-updated DOM).\r\n * 5. Re-hydrate new client component markers.\r\n * 6. Scroll to top.\r\n *\r\n * Falls back to a full page reload if anything goes wrong.\r\n */\r\nfunction setupNavigation(log: ReturnType<typeof makeLogger>): void {\r\n window.addEventListener('locationchange', async ({ detail: { href, hmr } }: any) => {\r\n try {\r\n const fetchUrl = hmr\r\n ? href + (href.includes('?') ? '&' : '?') + '__hmr=1'\r\n : href;\r\n\r\n const response = await fetch(fetchUrl, { headers: { Accept: 'text/html' } });\r\n if (!response.ok) {\r\n log.error('Navigation fetch failed:', response.status);\r\n return;\r\n }\r\n\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(await response.text(), 'text/html');\r\n const newApp = doc.getElementById('app');\r\n const currApp = document.getElementById('app');\r\n if (!newApp || !currApp) return;\r\n\r\n // \u2500\u2500 Visual update \u2014 all DOM mutations before React teardown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n // Styles must be in place before new content appears to avoid an unstyled\r\n // flash. Unmounting runs useEffect cleanups (including useHtml restores)\r\n // which would temporarily revert document state if done first.\r\n\r\n // 1. Head tags \u2014 diff-based sync preserves shared layout tags untouched.\r\n syncHeadTags(doc);\r\n\r\n // 2. Body scripts (position='body') \u2014 diff-based sync mirrors head tag logic.\r\n syncBodyScripts(doc);\r\n\r\n // 3. <html> and <body> attributes (lang, class, style, etc.).\r\n syncAttrs(document.documentElement, doc.documentElement);\r\n syncAttrs(document.body, doc.body);\r\n\r\n // 4. Page content.\r\n currApp.innerHTML = newApp.innerHTML;\r\n\r\n // 5. <title>.\r\n const newTitle = doc.querySelector('title');\r\n if (newTitle) document.title = newTitle.textContent ?? '';\r\n\r\n // 6. Runtime data blob \u2014 must come after innerHTML swap so the new\r\n // __n_data element is part of the live document.\r\n const newDataEl = doc.getElementById('__n_data');\r\n const currDataEl = document.getElementById('__n_data');\r\n if (newDataEl && currDataEl) currDataEl.textContent = newDataEl.textContent;\r\n\r\n // \u2500\u2500 React teardown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n // Unmount after the visual update. Cleanup effects now run against an\r\n // already-updated document, so there is nothing left to visually undo.\r\n activeRoots.splice(0).forEach(r => r.unmount());\r\n\r\n // \u2500\u2500 Re-hydration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n const navData = JSON.parse(currDataEl?.textContent ?? '{}') as RuntimeData;\r\n log.info('\uD83D\uDD04 Route \u2192', href, '\u2014 mounting', navData.hydrateIds?.length ?? 0, 'component(s)');\r\n\r\n const mods = await loadModules(navData.allIds ?? [], log, String(Date.now()));\r\n await mountNodes(mods, log);\r\n\r\n window.scrollTo(0, 0);\r\n log.info('\uD83C\uDF89 Navigation complete:', href);\r\n } catch (err) {\r\n log.error('Navigation error, falling back to full reload:', err);\r\n window.location.href = href;\r\n }\r\n });\r\n}\r\n\r\n// \u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/** Shape of the JSON blob embedded as #__n_data in every SSR page. */\r\nexport interface RuntimeData {\r\n /** IDs of client components actually rendered on this page (subset of allIds). */\r\n hydrateIds: string[];\r\n /** All client component IDs reachable from this page, including layouts.\r\n * Pre-loaded so SPA navigations to related pages feel instant. */\r\n allIds: string[];\r\n url: string;\r\n params: Record<string, any>;\r\n debug: ClientDebugLevel;\r\n}\r\n\r\n/**\r\n * Bootstraps the NukeJS client runtime.\r\n *\r\n * Called once per page load from the inline <script type=\"module\"> injected\r\n * by the SSR renderer:\r\n *\r\n * ```js\r\n * const { initRuntime } = await import('nukejs');\r\n * const data = JSON.parse(document.getElementById('__n_data').textContent);\r\n * await initRuntime(data);\r\n * ```\r\n *\r\n * Order of operations:\r\n * 1. Create the logger at the configured debug level.\r\n * 2. Wire up SPA navigation listener.\r\n * 3. Load all client component bundles in parallel.\r\n * 4. Hydrate every [data-hydrate-id] node.\r\n * 5. Patch history.pushState/replaceState so Link clicks trigger navigation.\r\n */\r\nexport async function initRuntime(data: RuntimeData): Promise<void> {\r\n const log = makeLogger(data.debug ?? 'silent');\r\n\r\n log.info('\uD83D\uDE80 Partial hydration:', data.hydrateIds.length, 'root component(s)');\r\n\r\n // Set up navigation first so any 'locationchange' fired during hydration\r\n // is captured (e.g. a redirect side-effect inside a component).\r\n setupNavigation(log);\r\n\r\n // Load all component bundles (not just hydrateIds) so SPA navigations to\r\n // related pages can mount their components without an extra network round-trip.\r\n const mods = await loadModules(data.allIds, log);\r\n await mountNodes(mods, log);\r\n\r\n log.info('\uD83C\uDF89 Done!');\r\n\r\n // Patch history last so pushState calls during hydration don't trigger a\r\n // navigation before roots are ready.\r\n setupLocationChangeMonitor();\r\n}"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -32,6 +32,12 @@ export interface ComponentInfo {
32
32
  isClientComponent: boolean;
33
33
  /** Stable hash-based ID, present only for client components. */
34
34
  clientComponentId?: string;
35
+ /**
36
+ * The name of the default-exported component function.
37
+ * Handles both source format (`export default Link`) and esbuild's compiled
38
+ * format (`var Link_default = Link; export { Link_default as default }`).
39
+ */
40
+ exportedName?: string;
35
41
  }
36
42
  /**
37
43
  * Analyses a component file and returns cached results on subsequent calls.
@@ -16,13 +16,24 @@ function isClientComponent(filePath) {
16
16
  function getClientComponentId(filePath, pagesDir) {
17
17
  return "cc_" + createHash("md5").update(path.relative(pagesDir, filePath)).digest("hex").substring(0, 8);
18
18
  }
19
+ function getExportedDefaultName(filePath) {
20
+ const content = fs.readFileSync(filePath, "utf-8");
21
+ let m = content.match(/export\s+default\s+(?:function\s+)?(\w+)/);
22
+ if (m?.[1]) return m[1];
23
+ m = content.match(/var\s+\w+_default\s*=\s*(\w+)/);
24
+ if (m?.[1]) return m[1];
25
+ m = content.match(/export\s*\{[^}]*\b(\w+)\s+as\s+default\b[^}]*\}/);
26
+ if (m?.[1] && !m[1].endsWith("_default")) return m[1];
27
+ return void 0;
28
+ }
19
29
  function analyzeComponent(filePath, pagesDir) {
20
30
  if (componentCache.has(filePath)) return componentCache.get(filePath);
21
31
  const isClient = isClientComponent(filePath);
22
32
  const info = {
23
33
  filePath,
24
34
  isClientComponent: isClient,
25
- clientComponentId: isClient ? getClientComponentId(filePath, pagesDir) : void 0
35
+ clientComponentId: isClient ? getClientComponentId(filePath, pagesDir) : void 0,
36
+ exportedName: isClient ? getExportedDefaultName(filePath) : void 0
26
37
  };
27
38
  componentCache.set(filePath, info);
28
39
  return info;