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.
- package/README.md +11 -5
- package/dist/as-is/Link.js +14 -0
- package/dist/as-is/Link.js.map +7 -0
- package/dist/as-is/useRouter.js +28 -0
- package/dist/as-is/useRouter.js.map +7 -0
- package/dist/build-common.d.ts +52 -78
- package/dist/build-common.js +158 -190
- package/dist/build-common.js.map +2 -2
- package/dist/build-node.d.ts +14 -0
- package/dist/build-node.js +52 -56
- package/dist/build-node.js.map +2 -2
- package/dist/build-vercel.d.ts +18 -0
- package/dist/build-vercel.js +78 -67
- package/dist/build-vercel.js.map +2 -2
- package/dist/builder.d.ts +16 -0
- package/dist/builder.js +54 -54
- package/dist/builder.js.map +3 -3
- package/dist/bundle.d.ts +12 -2
- package/dist/bundle.js +62 -14
- package/dist/bundle.js.map +2 -2
- package/dist/component-analyzer.d.ts +7 -10
- package/dist/component-analyzer.js +33 -20
- package/dist/component-analyzer.js.map +2 -2
- package/dist/router.d.ts +26 -22
- package/dist/router.js +14 -7
- package/dist/router.js.map +2 -2
- package/dist/ssr.d.ts +10 -4
- package/dist/ssr.js +12 -7
- package/dist/ssr.js.map +2 -2
- package/package.json +1 -1
package/dist/build-vercel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
fs.mkdirSync(
|
|
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
|
|
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:
|
|
118
|
-
params:
|
|
119
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
152
|
-
|
|
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
|
-
|
|
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
|
|
176
|
-
const prerenderedHtml = await bundleClientComponents(
|
|
177
|
-
const
|
|
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
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
const
|
|
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:
|
|
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_${
|
|
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
|
-
|
|
219
|
-
|
|
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
|
|
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(`
|
package/dist/build-vercel.js.map
CHANGED
|
@@ -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": "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
20
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
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
|
|
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 !==
|
|
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
|
package/dist/builder.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/builder.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": [
|
|
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": "AAiBA,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;AAChD,MAAM,QAAQ;AAId,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;AAOA,SAAS,QAAQ,KAAa,MAAoB;AAChD,MAAI,CAAC,GAAG,WAAW,GAAG,EAAG;AACzB,KAAG,UAAU,MAAM,EAAE,WAAW,KAAK,CAAC;AACtC,aAAW,SAAS,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;AAChE,UAAM,IAAI,KAAK,KAAK,KAAK,MAAM,IAAI;AACnC,UAAM,IAAI,KAAK,KAAK,MAAM,MAAM,IAAI;AACpC,UAAM,YAAY,IAAI,QAAQ,GAAG,CAAC,IAAI,GAAG,aAAa,GAAG,CAAC;AAAA,EAC5D;AACF;AAGA,SAAS,YAAY,KAAa;AAChC,QAAM,gBAAgB;AAEtB,GAAC,SAAS,KAAK,YAAoB;AACjC,OAAG,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM;AACjE,YAAM,WAAW,KAAK,KAAK,YAAY,EAAE,IAAI;AAE7C,UAAI,EAAE,YAAY,GAAG;AACnB,YAAI,EAAE,SAAS,cAAe,MAAK,QAAQ;AAAA,MAC7C,WAAW,SAAS,SAAS,KAAK,GAAG;AACnC,YAAI,UAAU,GAAG,aAAa,UAAU,OAAO;AAG/C,kBAAU,QAAQ;AAAA,UAChB;AAAA,UACA;AAAA,QACF;AACA,kBAAU,QAAQ;AAAA,UAChB;AAAA,UACA;AAAA,QACF;AAEA,WAAG,cAAc,UAAU,SAAS,OAAO;AAAA,MAC7C;AAAA,IACF,CAAC;AAAA,EACH,GAAG,GAAG;AAEN,UAAQ,IAAI,kFAAsE;AACpF;AAIA,eAAe,WAA0B;AACvC,MAAI;AACF,cAAU,MAAM;AAGhB,YAAQ,IAAI,wCAA4B;AACxC,UAAM,MAAM;AAAA,MACV,aAAa,aAAa,QAAQ,CAAC,KAAK,CAAC;AAAA,MACzC,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,QAAQ,CAAC,QAAQ;AAAA,MACjB,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AACD,YAAQ,IAAI,0BAAqB;AAGjC,YAAQ,IAAI,yCAA6B;AACzC,UAAM,MAAM;AAAA,MACV,aAAa,aAAa,KAAK,KAAK,QAAQ,KAAK,CAAC;AAAA,MAClD,QAAQ,KAAK,KAAK,QAAQ,KAAK;AAAA,MAC/B,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,QAAQ,CAAC,QAAQ;AAAA,MACjB,UAAU;AAAA,MACV,KAAK;AAAA,MACL,WAAW;AAAA,IACb,CAAC;AACD,YAAQ,IAAI,2BAAsB;AAGlC,YAAQ,KAAK,KAAK,QAAQ,KAAK,GAAG,KAAK,KAAK,QAAQ,KAAK,CAAC;AAC1D,YAAQ,IAAI,+CAAmC,KAAK,GAAG;AAGvD,gBAAY,MAAM;AAGlB,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
|
+
"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
|
-
*
|
|
33
|
-
*
|
|
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';
|