nukejs 0.0.4 → 0.0.6

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.
@@ -1,6 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import crypto from "crypto";
3
+ import { randomBytes } from "node:crypto";
4
4
  import { build } from "esbuild";
5
5
  import { loadConfig } from "./config.js";
6
6
  import {
@@ -12,21 +12,55 @@ import {
12
12
  findPageLayouts,
13
13
  buildPerPageRegistry,
14
14
  makePageAdapterSource,
15
- buildReactBundle,
16
- buildNukeBundle,
15
+ buildCombinedBundle,
17
16
  copyPublicFiles
18
17
  } from "./build-common.js";
19
18
  const OUTPUT_DIR = path.resolve(".vercel/output");
20
19
  const FUNCTIONS_DIR = path.join(OUTPUT_DIR, "functions");
21
20
  const STATIC_DIR = path.join(OUTPUT_DIR, "static");
22
- fs.mkdirSync(FUNCTIONS_DIR, { recursive: true });
23
- fs.mkdirSync(STATIC_DIR, { recursive: true });
21
+ for (const dir of [FUNCTIONS_DIR, STATIC_DIR])
22
+ fs.mkdirSync(dir, { recursive: true });
24
23
  const config = await loadConfig();
25
24
  const SERVER_DIR = path.resolve(config.serverDir);
26
25
  const PAGES_DIR = path.resolve("./app/pages");
27
26
  const PUBLIC_DIR = path.resolve("./app/public");
27
+ const NODE_BUILTINS = [
28
+ "node:*",
29
+ "http",
30
+ "https",
31
+ "fs",
32
+ "path",
33
+ "url",
34
+ "crypto",
35
+ "stream",
36
+ "buffer",
37
+ "events",
38
+ "util",
39
+ "os",
40
+ "net",
41
+ "tls",
42
+ "child_process",
43
+ "worker_threads",
44
+ "cluster",
45
+ "dgram",
46
+ "dns",
47
+ "readline",
48
+ "zlib",
49
+ "assert",
50
+ "module",
51
+ "perf_hooks",
52
+ "string_decoder",
53
+ "timers",
54
+ "async_hooks",
55
+ "v8",
56
+ "vm"
57
+ ];
58
+ const CJS_COMPAT_BANNER = {
59
+ js: `import { createRequire } from 'module';
60
+ const require = createRequire(import.meta.url);`
61
+ };
28
62
  function emitVercelFunction(name, bundleText) {
29
- const funcDir = path.join(FUNCTIONS_DIR, name + ".func");
63
+ const funcDir = path.join(FUNCTIONS_DIR, `${name}.func`);
30
64
  fs.mkdirSync(funcDir, { recursive: true });
31
65
  fs.writeFileSync(path.join(funcDir, "index.mjs"), bundleText);
32
66
  fs.writeFileSync(
@@ -108,15 +142,16 @@ export default async function handler(req: IncomingMessage, res: ServerResponse)
108
142
  function makePagesDispatcherSource(routes) {
109
143
  const imports = routes.map((r, i) => `import __page_${i}__ from ${JSON.stringify(r.adapterPath)};`).join("\n");
110
144
  const routeEntries = routes.map(
111
- (r, i) => ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, handler: __page_${i}__ },`
145
+ (r, i) => ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, catchAll: ${JSON.stringify(r.catchAllNames)}, handler: __page_${i}__ },`
112
146
  ).join("\n");
113
147
  return `import type { IncomingMessage, ServerResponse } from 'http';
114
148
  ${imports}
115
149
 
116
150
  const ROUTES: Array<{
117
- regex: string;
118
- params: string[];
119
- handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
151
+ regex: string;
152
+ params: string[];
153
+ catchAll: string[];
154
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
120
155
  }> = [
121
156
  ${routeEntries}
122
157
  ];
@@ -129,9 +164,16 @@ export default async function handler(req: IncomingMessage, res: ServerResponse)
129
164
  const m = pathname.match(new RegExp(route.regex));
130
165
  if (!m) continue;
131
166
 
132
- // Inject dynamic params as query-string values so page handlers can read
133
- // them via new URL(req.url).searchParams \u2014 the same way they always have.
134
- route.params.forEach((name, i) => url.searchParams.set(name, m[i + 1]));
167
+ const catchAllSet = new Set(route.catchAll);
168
+ route.params.forEach((name, i) => {
169
+ const raw = m[i + 1] ?? '';
170
+ if (catchAllSet.has(name)) {
171
+ // Encode catch-all as repeated keys so the handler can getAll() \u2192 string[]
172
+ raw.split('/').filter(Boolean).forEach(seg => url.searchParams.append(name, seg));
173
+ } else {
174
+ url.searchParams.set(name, raw);
175
+ }
176
+ });
135
177
  req.url = pathname + (url.search || '');
136
178
 
137
179
  return route.handler(req, res);
@@ -148,9 +190,8 @@ const apiFiles = walkFiles(SERVER_DIR);
148
190
  if (apiFiles.length === 0) console.warn(`\u26A0 No server files found in ${SERVER_DIR}`);
149
191
  const apiRoutes = apiFiles.map((relPath) => ({ ...analyzeFile(relPath, "api"), absPath: path.join(SERVER_DIR, relPath) })).sort((a, b) => b.specificity - a.specificity);
150
192
  if (apiRoutes.length > 0) {
151
- const dispatcherSource = makeApiDispatcherSource(apiRoutes);
152
- const dispatcherPath = path.join(SERVER_DIR, `_api_dispatcher_${crypto.randomBytes(4).toString("hex")}.ts`);
153
- fs.writeFileSync(dispatcherPath, dispatcherSource);
193
+ const dispatcherPath = path.join(SERVER_DIR, `_api_dispatcher_${randomBytes(4).toString("hex")}.ts`);
194
+ fs.writeFileSync(dispatcherPath, makeApiDispatcherSource(apiRoutes));
154
195
  try {
155
196
  const result = await build({
156
197
  entryPoints: [dispatcherPath],
@@ -158,7 +199,8 @@ if (apiRoutes.length > 0) {
158
199
  format: "esm",
159
200
  platform: "node",
160
201
  target: "node20",
161
- packages: "external",
202
+ banner: CJS_COMPAT_BANNER,
203
+ external: NODE_BUILTINS,
162
204
  write: false
163
205
  });
164
206
  emitVercelFunction("api", result.outputFiles[0].text);
@@ -166,22 +208,20 @@ if (apiRoutes.length > 0) {
166
208
  } finally {
167
209
  fs.unlinkSync(dispatcherPath);
168
210
  }
169
- for (const { srcRegex } of apiRoutes) {
211
+ for (const { srcRegex } of apiRoutes)
170
212
  vercelRoutes.push({ src: srcRegex, dest: "/api" });
171
- }
172
213
  }
173
214
  const serverPages = collectServerPages(PAGES_DIR);
174
215
  if (serverPages.length > 0) {
175
- const globalClientRegistry = collectGlobalClientRegistry(serverPages, PAGES_DIR);
176
- const prerenderedHtml = await bundleClientComponents(globalClientRegistry, PAGES_DIR, STATIC_DIR);
177
- const prerenderedHtmlRecord = Object.fromEntries(prerenderedHtml);
216
+ const globalRegistry = collectGlobalClientRegistry(serverPages, PAGES_DIR);
217
+ const prerenderedHtml = await bundleClientComponents(globalRegistry, PAGES_DIR, STATIC_DIR);
218
+ const prerenderedRecord = Object.fromEntries(prerenderedHtml);
178
219
  const tempAdapterPaths = [];
179
220
  for (const page of serverPages) {
180
- const { absPath } = page;
181
- const adapterDir = path.dirname(absPath);
182
- const adapterPath = path.join(adapterDir, `_page_adapter_${crypto.randomBytes(4).toString("hex")}.ts`);
183
- const layoutPaths = findPageLayouts(absPath, PAGES_DIR);
184
- const { registry, clientComponentNames } = buildPerPageRegistry(absPath, layoutPaths, PAGES_DIR);
221
+ const adapterDir = path.dirname(page.absPath);
222
+ const adapterPath = path.join(adapterDir, `_page_adapter_${randomBytes(4).toString("hex")}.ts`);
223
+ const layoutPaths = findPageLayouts(page.absPath, PAGES_DIR);
224
+ const { registry, clientComponentNames } = buildPerPageRegistry(page.absPath, layoutPaths, PAGES_DIR);
185
225
  const layoutImports = layoutPaths.map((lp, i) => {
186
226
  const rel = path.relative(adapterDir, lp).replace(/\\/g, "/");
187
227
  return `import __layout_${i}__ from ${JSON.stringify(rel.startsWith(".") ? rel : "./" + rel)};`;
@@ -189,23 +229,25 @@ if (serverPages.length > 0) {
189
229
  fs.writeFileSync(
190
230
  adapterPath,
191
231
  makePageAdapterSource({
192
- pageImport: JSON.stringify("./" + path.basename(absPath)),
232
+ pageImport: JSON.stringify("./" + path.basename(page.absPath)),
193
233
  layoutImports,
194
234
  clientComponentNames,
195
235
  allClientIds: [...registry.keys()],
196
236
  layoutArrayItems: layoutPaths.map((_, i) => `__layout_${i}__`).join(", "),
197
- prerenderedHtml: prerenderedHtmlRecord
237
+ prerenderedHtml: prerenderedRecord,
238
+ catchAllNames: page.catchAllNames
198
239
  })
199
240
  );
200
241
  tempAdapterPaths.push(adapterPath);
201
- console.log(` prepared ${path.relative(PAGES_DIR, absPath)} \u2192 ${page.funcPath} [page]`);
242
+ console.log(` prepared ${path.relative(PAGES_DIR, page.absPath)} \u2192 ${page.funcPath} [page]`);
202
243
  }
203
244
  const dispatcherRoutes = serverPages.map((page, i) => ({
204
245
  adapterPath: tempAdapterPaths[i],
205
246
  srcRegex: page.srcRegex,
206
- paramNames: page.paramNames
247
+ paramNames: page.paramNames,
248
+ catchAllNames: page.catchAllNames
207
249
  }));
208
- const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${crypto.randomBytes(4).toString("hex")}.ts`);
250
+ const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${randomBytes(4).toString("hex")}.ts`);
209
251
  fs.writeFileSync(dispatcherPath, makePagesDispatcherSource(dispatcherRoutes));
210
252
  try {
211
253
  const result = await build({
@@ -215,37 +257,8 @@ if (serverPages.length > 0) {
215
257
  platform: "node",
216
258
  target: "node20",
217
259
  jsx: "automatic",
218
- external: [
219
- "node:*",
220
- "http",
221
- "https",
222
- "fs",
223
- "path",
224
- "url",
225
- "crypto",
226
- "stream",
227
- "buffer",
228
- "events",
229
- "util",
230
- "os",
231
- "net",
232
- "tls",
233
- "child_process",
234
- "worker_threads",
235
- "cluster",
236
- "dgram",
237
- "dns",
238
- "readline",
239
- "zlib",
240
- "assert",
241
- "module",
242
- "perf_hooks",
243
- "string_decoder",
244
- "timers",
245
- "async_hooks",
246
- "v8",
247
- "vm"
248
- ],
260
+ banner: CJS_COMPAT_BANNER,
261
+ external: NODE_BUILTINS,
249
262
  define: { "process.env.NODE_ENV": '"production"' },
250
263
  write: false
251
264
  });
@@ -255,9 +268,8 @@ if (serverPages.length > 0) {
255
268
  fs.unlinkSync(dispatcherPath);
256
269
  for (const p of tempAdapterPaths) if (fs.existsSync(p)) fs.unlinkSync(p);
257
270
  }
258
- for (const { srcRegex } of serverPages) {
271
+ for (const { srcRegex } of serverPages)
259
272
  vercelRoutes.push({ src: srcRegex, dest: "/pages" });
260
- }
261
273
  }
262
274
  fs.writeFileSync(
263
275
  path.join(OUTPUT_DIR, "config.json"),
@@ -267,8 +279,7 @@ fs.writeFileSync(
267
279
  path.resolve("vercel.json"),
268
280
  JSON.stringify({ runtime: "nodejs20.x" }, null, 2)
269
281
  );
270
- await buildReactBundle(STATIC_DIR);
271
- await buildNukeBundle(STATIC_DIR);
282
+ await buildCombinedBundle(STATIC_DIR);
272
283
  copyPublicFiles(PUBLIC_DIR, STATIC_DIR);
273
284
  const fnCount = (apiRoutes.length > 0 ? 1 : 0) + (serverPages.length > 0 ? 1 : 0);
274
285
  console.log(`
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/build-vercel.ts"],
4
- "sourcesContent": ["import fs from 'fs';\r\nimport path from 'path';\r\nimport crypto from '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 buildReactBundle,\r\n buildNukeBundle,\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\nfs.mkdirSync(FUNCTIONS_DIR, { recursive: true });\r\nfs.mkdirSync(STATIC_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 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 dispatcher that imports every API route module directly,\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\n * enhance / parseBody helpers are included once rather than once per route.\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 dispatcher that imports each page's pre-generated adapter by its\r\n * temp file path, matches the incoming URL, injects captured dynamic params as\r\n * query-string values (page handlers read params from req.url searchParams),\r\n * then delegates to the matching handler.\r\n */\r\nfunction makePagesDispatcherSource(\r\n routes: Array<{ adapterPath: string; srcRegex: string; paramNames: string[] }>,\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)}, 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 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 // Inject dynamic params as query-string values so page handlers can read\r\n // them via new URL(req.url).searchParams \u2014 the same way they always have.\r\n route.params.forEach((name, i) => url.searchParams.set(name, m[i + 1]));\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 dispatcherSource = makeApiDispatcherSource(apiRoutes);\r\n const dispatcherPath = path.join(SERVER_DIR, `_api_dispatcher_${crypto.randomBytes(4).toString('hex')}.ts`);\r\n fs.writeFileSync(dispatcherPath, dispatcherSource);\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 packages: 'external',\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\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 globalClientRegistry = collectGlobalClientRegistry(serverPages, PAGES_DIR);\r\n const prerenderedHtml = await bundleClientComponents(globalClientRegistry, PAGES_DIR, STATIC_DIR);\r\n const prerenderedHtmlRecord = 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 inside the component resolve correctly), then\r\n // bundle everything in one esbuild pass via the dispatcher.\r\n const tempAdapterPaths: string[] = [];\r\n\r\n for (const page of serverPages) {\r\n const { absPath } = page;\r\n const adapterDir = path.dirname(absPath);\r\n const adapterPath = path.join(adapterDir, `_page_adapter_${crypto.randomBytes(4).toString('hex')}.ts`);\r\n\r\n const layoutPaths = findPageLayouts(absPath, PAGES_DIR);\r\n const { registry, clientComponentNames } = buildPerPageRegistry(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(absPath)),\r\n layoutImports,\r\n clientComponentNames,\r\n allClientIds: [...registry.keys()],\r\n layoutArrayItems: layoutPaths.map((_, i) => `__layout_${i}__`).join(', '),\r\n prerenderedHtml: prerenderedHtmlRecord,\r\n }),\r\n );\r\n\r\n tempAdapterPaths.push(adapterPath);\r\n console.log(` prepared ${path.relative(PAGES_DIR, absPath)} \u2192 ${page.funcPath} [page]`);\r\n }\r\n\r\n // Write the dispatcher and let esbuild bundle all adapters in one pass.\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 }));\r\n\r\n const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${crypto.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 external: [\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 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\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\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 buildReactBundle(STATIC_DIR);\r\nawait buildNukeBundle(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`);"],
5
- "mappings": "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",
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"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/builder.d.ts CHANGED
@@ -1 +1,17 @@
1
+ /**
2
+ * builder.ts — NukeJS Package Build Script
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.
14
+ *
15
+ * Finally, `tsc --emitDeclarationOnly` generates .d.ts files for consumers.
16
+ */
1
17
  export {};
package/dist/builder.js CHANGED
@@ -3,80 +3,43 @@ import fs from "fs";
3
3
  import { execSync } from "child_process";
4
4
  import path from "path";
5
5
  import { fileURLToPath } from "url";
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
7
  const srcDir = path.resolve(__dirname, "");
9
8
  const outDir = path.resolve(__dirname, "../dist");
10
- const excludeFolder = "as-is";
9
+ const AS_IS = "as-is";
11
10
  function cleanDist(dir) {
12
11
  if (!fs.existsSync(dir)) return;
13
12
  fs.rmSync(dir, { recursive: true, force: true });
14
- console.log(`\u{1F5D1}\uFE0F Cleared dist folder: ${dir}`);
13
+ console.log(`\u{1F5D1}\uFE0F Cleared ${dir}`);
15
14
  }
16
- function collectFiles(dir, exclude) {
17
- const entries = fs.readdirSync(dir, { withFileTypes: true });
15
+ function collectFiles(dir, exclude = []) {
18
16
  const files = [];
19
- for (const entry of entries) {
20
- const fullPath = path.join(dir, entry.name);
17
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
18
+ const full = path.join(dir, entry.name);
21
19
  if (entry.isDirectory()) {
22
- if (!exclude.includes(entry.name)) {
23
- files.push(...collectFiles(fullPath, exclude));
24
- }
25
- } else if (entry.isFile() && fullPath.match(/\.[tj]s$/)) {
26
- files.push(fullPath);
20
+ if (!exclude.includes(entry.name)) files.push(...collectFiles(full, exclude));
21
+ } else if (/\.[tj]sx?$/.test(entry.name)) {
22
+ files.push(full);
27
23
  }
28
24
  }
29
25
  return files;
30
26
  }
31
- const entryPoints = collectFiles(srcDir, [excludeFolder]);
32
- async function runBuild() {
33
- try {
34
- cleanDist(outDir);
35
- console.log("\u{1F680} Starting esbuild...");
36
- await build({
37
- entryPoints,
38
- outdir: outDir,
39
- platform: "node",
40
- format: "esm",
41
- target: ["node20"],
42
- packages: "external",
43
- sourcemap: true
44
- });
45
- console.log("\u2705 Build finished.");
46
- copyFolder(path.join(srcDir, excludeFolder), path.join(outDir, excludeFolder));
47
- processDist(outDir);
48
- console.log("\u{1F4C4} Generating TypeScript types...");
49
- execSync("tsc --emitDeclarationOnly --declaration --outDir dist", {
50
- stdio: "inherit"
51
- });
52
- console.log("\u{1F389} Build complete. dist folder is ready!");
53
- } catch (err) {
54
- console.error("\u274C Build failed:", err);
55
- process.exit(1);
56
- }
57
- }
58
- function copyFolder(src, dest) {
27
+ function copyDir(src, dest) {
59
28
  if (!fs.existsSync(src)) return;
60
- if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
61
- const entries = fs.readdirSync(src, { withFileTypes: true });
62
- for (const entry of entries) {
63
- const srcPath = path.join(src, entry.name);
64
- const destPath = path.join(dest, entry.name);
65
- if (entry.isDirectory()) {
66
- copyFolder(srcPath, destPath);
67
- } else {
68
- fs.copyFileSync(srcPath, destPath);
69
- }
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);
70
34
  }
71
- console.log(`\u{1F4C1} Copied as-is folder to ${dest}`);
72
35
  }
73
36
  function processDist(dir) {
74
- const excludeFolder2 = "as-is";
37
+ const excludeFolder = "as-is";
75
38
  (function walk(currentDir) {
76
39
  fs.readdirSync(currentDir, { withFileTypes: true }).forEach((d) => {
77
40
  const fullPath = path.join(currentDir, d.name);
78
41
  if (d.isDirectory()) {
79
- if (d.name !== excludeFolder2) walk(fullPath);
42
+ if (d.name !== excludeFolder) walk(fullPath);
80
43
  } else if (fullPath.endsWith(".js")) {
81
44
  let content = fs.readFileSync(fullPath, "utf-8");
82
45
  content = content.replace(
@@ -93,5 +56,42 @@ function processDist(dir) {
93
56
  })(dir);
94
57
  console.log("\u{1F527} Post-processing done: .ts imports \u2192 .js (excluding as-is folder).");
95
58
  }
59
+ async function runBuild() {
60
+ try {
61
+ cleanDist(outDir);
62
+ console.log("\u{1F680} Building main sources\u2026");
63
+ await build({
64
+ entryPoints: collectFiles(srcDir, [AS_IS]),
65
+ outdir: outDir,
66
+ platform: "node",
67
+ format: "esm",
68
+ target: ["node20"],
69
+ packages: "external",
70
+ sourcemap: true
71
+ });
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}/`);
87
+ processDist(outDir);
88
+ console.log("\u{1F4C4} Generating TypeScript declarations\u2026");
89
+ execSync("tsc --emitDeclarationOnly --declaration --outDir dist", { stdio: "inherit" });
90
+ console.log("\n\u{1F389} Build complete \u2192 dist/");
91
+ } catch (err) {
92
+ console.error("\u274C Build failed:", err);
93
+ process.exit(1);
94
+ }
95
+ }
96
96
  runBuild();
97
97
  //# sourceMappingURL=builder.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/builder.ts"],
4
- "sourcesContent": ["import { 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\n// Equivalent of __dirname in ESM\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\nconst srcDir = path.resolve(__dirname, \"\");\r\nconst outDir = path.resolve(__dirname, \"../dist\");\r\nconst excludeFolder = \"as-is\";\r\n\r\n// --- Step 0: Clean dist folder ---\r\nfunction cleanDist(dir: string) {\r\n if (!fs.existsSync(dir)) return;\r\n fs.rmSync(dir, { recursive: true, force: true });\r\n console.log(`\uD83D\uDDD1\uFE0F Cleared dist folder: ${dir}`);\r\n}\r\n\r\n// --- Step 1: Collect entry points ---\r\nfunction collectFiles(dir: string, exclude: string[]): string[] {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true });\r\n const files: string[] = [];\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name);\r\n\r\n if (entry.isDirectory()) {\r\n if (!exclude.includes(entry.name)) {\r\n files.push(...collectFiles(fullPath, exclude));\r\n }\r\n } else if (entry.isFile() && fullPath.match(/\\.[tj]s$/)) {\r\n files.push(fullPath);\r\n }\r\n }\r\n\r\n return files;\r\n}\r\n\r\nconst entryPoints = collectFiles(srcDir, [excludeFolder]);\r\n\r\n// --- Step 2: Build with esbuild ---\r\nasync function runBuild() {\r\n try {\r\n cleanDist(outDir); // Clear dist first\r\n\r\n console.log(\"\uD83D\uDE80 Starting esbuild...\");\r\n await build({\r\n entryPoints,\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 finished.\");\r\n\r\n // --- Step 3: Copy as-is folder ---\r\n copyFolder(path.join(srcDir, excludeFolder), path.join(outDir, excludeFolder));\r\n\r\n // --- Step 4: Post-process .js files ---\r\n processDist(outDir);\r\n\r\n // --- Step 5: Compile types ---\r\n console.log(\"\uD83D\uDCC4 Generating TypeScript types...\");\r\n execSync(\"tsc --emitDeclarationOnly --declaration --outDir dist\", {\r\n stdio: \"inherit\",\r\n });\r\n\r\n console.log(\"\uD83C\uDF89 Build complete. dist folder is ready!\");\r\n } catch (err) {\r\n console.error(\"\u274C Build failed:\", err);\r\n process.exit(1);\r\n }\r\n}\r\n\r\n// --- Copy folder recursively ---\r\nfunction copyFolder(src: string, dest: string) {\r\n if (!fs.existsSync(src)) return;\r\n if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });\r\n\r\n const entries = fs.readdirSync(src, { withFileTypes: true });\r\n\r\n for (const entry of entries) {\r\n const srcPath = path.join(src, entry.name);\r\n const destPath = path.join(dest, entry.name);\r\n\r\n if (entry.isDirectory()) {\r\n copyFolder(srcPath, destPath);\r\n } else {\r\n fs.copyFileSync(srcPath, destPath);\r\n }\r\n }\r\n console.log(`\uD83D\uDCC1 Copied as-is folder to ${dest}`);\r\n}\r\n\r\n// --- Post-process .js files ---\r\nfunction processDist(dir: string) {\r\n const excludeFolder = \"as-is\";\r\n\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\r\n if (d.isDirectory()) {\r\n if (d.name !== excludeFolder) walk(fullPath);\r\n } else if (fullPath.endsWith(\".js\")) {\r\n let content = fs.readFileSync(fullPath, \"utf-8\");\r\n\r\n // Replace import/export paths ending with .ts \u2192 .js, skip paths containing excludeFolder\r\n content = content.replace(\r\n /from\\s+['\"](\\.\\/(?!as-is\\/).*?)['\"]/g,\r\n 'from \"$1.js\"'\r\n );\r\n content = content.replace(\r\n /import\\(['\"](\\.\\/(?!as-is\\/).*?)['\"]\\)/g,\r\n 'import(\"$1.js\")'\r\n );\r\n\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: .ts imports \u2192 .js (excluding as-is folder).\");\r\n}\r\n\r\n// Run the full build\r\nrunBuild();"],
5
- "mappings": "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",
6
- "names": ["excludeFolder"]
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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",
6
+ "names": []
7
7
  }
package/dist/bundle.d.ts CHANGED
@@ -23,14 +23,24 @@
23
23
  * - The handler fetches the target URL as HTML, diffs the #app container,
24
24
  * unmounts the old React roots, and re-hydrates the new ones.
25
25
  * - HMR navigations add ?__hmr=1 so the server skips client-SSR (faster).
26
+ *
27
+ * Head tag management:
28
+ * - The SSR renderer wraps every useHtml()-generated <meta>, <link>, <style>,
29
+ * and <script> tag in <!--n-head-->…<!--/n-head--> sentinel comments.
30
+ * - On each navigation the client diffs the live sentinel block against the
31
+ * incoming one by fingerprint, adding new tags and removing gone ones.
32
+ * Tags shared between pages (e.g. a layout stylesheet) are left untouched
33
+ * so there is no removal/re-insertion flash.
34
+ * - New tags are always inserted before <!--/n-head--> so they stay inside
35
+ * the tracked block and remain visible to the diff on subsequent navigations.
26
36
  */
27
37
  /**
28
38
  * Patches history.pushState and history.replaceState to fire a custom
29
39
  * 'locationchange' event on window. Also listens to 'popstate' for
30
40
  * back/forward navigation.
31
41
  *
32
- * This must be called after initRuntime sets up the navigation listener so
33
- * there's no race between the event firing and the listener being registered.
42
+ * Called after initRuntime sets up the navigation listener so there is no
43
+ * race between the event firing and the listener being registered.
34
44
  */
35
45
  export declare function setupLocationChangeMonitor(): void;
36
46
  type ClientDebugLevel = 'silent' | 'error' | 'info' | 'verbose';