nukejs 0.0.1 → 0.0.3
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/LICENSE +21 -0
- package/README.md +529 -0
- package/bin/index.mjs +126 -0
- package/dist/app.d.ts +18 -0
- package/dist/app.js +124 -0
- package/dist/app.js.map +7 -0
- package/dist/as-is/Link.d.ts +6 -0
- package/dist/as-is/Link.tsx +20 -0
- package/dist/as-is/useRouter.d.ts +7 -0
- package/dist/as-is/useRouter.ts +33 -0
- package/dist/build-common.d.ts +192 -0
- package/dist/build-common.js +737 -0
- package/dist/build-common.js.map +7 -0
- package/dist/build-node.d.ts +1 -0
- package/dist/build-node.js +170 -0
- package/dist/build-node.js.map +7 -0
- package/dist/build-vercel.d.ts +1 -0
- package/dist/build-vercel.js +65 -0
- package/dist/build-vercel.js.map +7 -0
- package/dist/builder.d.ts +1 -0
- package/dist/builder.js +97 -0
- package/dist/builder.js.map +7 -0
- package/dist/bundle.d.ts +68 -0
- package/dist/bundle.js +166 -0
- package/dist/bundle.js.map +7 -0
- package/dist/bundler.d.ts +58 -0
- package/dist/bundler.js +100 -0
- package/dist/bundler.js.map +7 -0
- package/dist/component-analyzer.d.ts +72 -0
- package/dist/component-analyzer.js +102 -0
- package/dist/component-analyzer.js.map +7 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +30 -0
- package/dist/config.js.map +7 -0
- package/dist/hmr-bundle.d.ts +25 -0
- package/dist/hmr-bundle.js +76 -0
- package/dist/hmr-bundle.js.map +7 -0
- package/dist/hmr.d.ts +55 -0
- package/dist/hmr.js +62 -0
- package/dist/hmr.js.map +7 -0
- package/dist/html-store.d.ts +121 -0
- package/dist/html-store.js +42 -0
- package/dist/html-store.js.map +7 -0
- package/dist/http-server.d.ts +99 -0
- package/dist/http-server.js +166 -0
- package/dist/http-server.js.map +7 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +7 -0
- package/dist/logger.d.ts +58 -0
- package/dist/logger.js +53 -0
- package/dist/logger.js.map +7 -0
- package/dist/metadata.d.ts +50 -0
- package/dist/metadata.js +43 -0
- package/dist/metadata.js.map +7 -0
- package/dist/middleware-loader.d.ts +50 -0
- package/dist/middleware-loader.js +50 -0
- package/dist/middleware-loader.js.map +7 -0
- package/dist/middleware.d.ts +22 -0
- package/dist/middleware.example.d.ts +8 -0
- package/dist/middleware.example.js +58 -0
- package/dist/middleware.example.js.map +7 -0
- package/dist/middleware.js +72 -0
- package/dist/middleware.js.map +7 -0
- package/dist/renderer.d.ts +44 -0
- package/dist/renderer.js +130 -0
- package/dist/renderer.js.map +7 -0
- package/dist/router.d.ts +84 -0
- package/dist/router.js +104 -0
- package/dist/router.js.map +7 -0
- package/dist/ssr.d.ts +39 -0
- package/dist/ssr.js +168 -0
- package/dist/ssr.js.map +7 -0
- package/dist/use-html.d.ts +64 -0
- package/dist/use-html.js +125 -0
- package/dist/use-html.js.map +7 -0
- package/dist/utils.d.ts +26 -0
- package/dist/utils.js +62 -0
- package/dist/utils.js.map +7 -0
- package/package.json +63 -12
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import crypto from "crypto";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
5
|
+
import { build } from "esbuild";
|
|
6
|
+
import { findClientComponentsInTree } from "./component-analyzer.js";
|
|
7
|
+
function walkFiles(dir, base = dir) {
|
|
8
|
+
if (!fs.existsSync(dir)) return [];
|
|
9
|
+
const results = [];
|
|
10
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
11
|
+
const full = path.join(dir, entry.name);
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
results.push(...walkFiles(full, base));
|
|
14
|
+
} else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
|
|
15
|
+
results.push(path.relative(base, full));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return results;
|
|
19
|
+
}
|
|
20
|
+
function analyzeFile(relPath, prefix = "api") {
|
|
21
|
+
const normalized = relPath.replace(/\\/g, "/").replace(/\.(tsx?)$/, "");
|
|
22
|
+
let segments = normalized.split("/");
|
|
23
|
+
if (segments.at(-1) === "index") segments = segments.slice(0, -1);
|
|
24
|
+
const paramNames = [];
|
|
25
|
+
const regexParts = [];
|
|
26
|
+
let specificity = 0;
|
|
27
|
+
for (const seg of segments) {
|
|
28
|
+
const optCatchAll = seg.match(/^\[\[\.\.\.(.+)\]\]$/);
|
|
29
|
+
if (optCatchAll) {
|
|
30
|
+
paramNames.push(optCatchAll[1]);
|
|
31
|
+
regexParts.push("(.*)");
|
|
32
|
+
specificity += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const catchAll = seg.match(/^\[\.\.\.(.+)\]$/);
|
|
36
|
+
if (catchAll) {
|
|
37
|
+
paramNames.push(catchAll[1]);
|
|
38
|
+
regexParts.push("(.+)");
|
|
39
|
+
specificity += 10;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const dynamic = seg.match(/^\[(.+)\]$/);
|
|
43
|
+
if (dynamic) {
|
|
44
|
+
paramNames.push(dynamic[1]);
|
|
45
|
+
regexParts.push("([^/]+)");
|
|
46
|
+
specificity += 100;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
regexParts.push(seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
50
|
+
specificity += 1e3;
|
|
51
|
+
}
|
|
52
|
+
const srcRegex = segments.length === 0 ? "^/$" : "^/" + regexParts.join("/") + "$";
|
|
53
|
+
const funcSegments = normalized.split("/");
|
|
54
|
+
if (funcSegments.at(-1) === "index") funcSegments.pop();
|
|
55
|
+
const funcPath = funcSegments.length === 0 ? `/${prefix}/_index` : `/${prefix}/` + funcSegments.join("/");
|
|
56
|
+
return { srcRegex, paramNames, funcPath, specificity };
|
|
57
|
+
}
|
|
58
|
+
function isServerComponent(filePath) {
|
|
59
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
60
|
+
for (const line of content.split("\n").slice(0, 5)) {
|
|
61
|
+
const trimmed = line.trim();
|
|
62
|
+
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*")) continue;
|
|
63
|
+
if (/^["']use client["'];?$/.test(trimmed)) return false;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
function findPageLayouts(routeFilePath, pagesDir) {
|
|
69
|
+
const layouts = [];
|
|
70
|
+
const rootLayout = path.join(pagesDir, "layout.tsx");
|
|
71
|
+
if (fs.existsSync(rootLayout)) layouts.push(rootLayout);
|
|
72
|
+
const relativePath = path.relative(pagesDir, path.dirname(routeFilePath));
|
|
73
|
+
if (!relativePath || relativePath === ".") return layouts;
|
|
74
|
+
const segments = relativePath.split(path.sep).filter(Boolean);
|
|
75
|
+
for (let i = 1; i <= segments.length; i++) {
|
|
76
|
+
const layoutPath = path.join(pagesDir, ...segments.slice(0, i), "layout.tsx");
|
|
77
|
+
if (fs.existsSync(layoutPath)) layouts.push(layoutPath);
|
|
78
|
+
}
|
|
79
|
+
return layouts;
|
|
80
|
+
}
|
|
81
|
+
function extractDefaultExportName(filePath) {
|
|
82
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
83
|
+
const match = content.match(/export\s+default\s+(?:function\s+)?(\w+)/);
|
|
84
|
+
return match?.[1] ?? null;
|
|
85
|
+
}
|
|
86
|
+
function collectServerPages(pagesDir) {
|
|
87
|
+
if (!fs.existsSync(pagesDir)) return [];
|
|
88
|
+
return walkFiles(pagesDir).filter((relPath) => {
|
|
89
|
+
const base = path.basename(relPath, path.extname(relPath));
|
|
90
|
+
if (base === "layout") return false;
|
|
91
|
+
return isServerComponent(path.join(pagesDir, relPath));
|
|
92
|
+
}).map((relPath) => ({
|
|
93
|
+
...analyzeFile(relPath, "page"),
|
|
94
|
+
absPath: path.join(pagesDir, relPath)
|
|
95
|
+
})).sort((a, b) => b.specificity - a.specificity);
|
|
96
|
+
}
|
|
97
|
+
function collectGlobalClientRegistry(serverPages, pagesDir) {
|
|
98
|
+
const registry = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const { absPath } of serverPages) {
|
|
100
|
+
for (const [id, p] of findClientComponentsInTree(absPath, pagesDir)) {
|
|
101
|
+
registry.set(id, p);
|
|
102
|
+
}
|
|
103
|
+
for (const layoutPath of findPageLayouts(absPath, pagesDir)) {
|
|
104
|
+
for (const [id, p] of findClientComponentsInTree(layoutPath, pagesDir)) {
|
|
105
|
+
registry.set(id, p);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return registry;
|
|
110
|
+
}
|
|
111
|
+
function buildPerPageRegistry(absPath, layoutPaths, pagesDir) {
|
|
112
|
+
const registry = /* @__PURE__ */ new Map();
|
|
113
|
+
for (const [id, p] of findClientComponentsInTree(absPath, pagesDir)) {
|
|
114
|
+
registry.set(id, p);
|
|
115
|
+
}
|
|
116
|
+
for (const lp of layoutPaths) {
|
|
117
|
+
for (const [id, p] of findClientComponentsInTree(lp, pagesDir)) {
|
|
118
|
+
registry.set(id, p);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const clientComponentNames = {};
|
|
122
|
+
for (const [id, filePath] of registry) {
|
|
123
|
+
const name = extractDefaultExportName(filePath);
|
|
124
|
+
if (name) clientComponentNames[name] = id;
|
|
125
|
+
}
|
|
126
|
+
return { registry, clientComponentNames };
|
|
127
|
+
}
|
|
128
|
+
async function buildPages(pagesDir, staticDir) {
|
|
129
|
+
const serverPages = collectServerPages(pagesDir);
|
|
130
|
+
if (fs.existsSync(pagesDir) && walkFiles(pagesDir).length > 0 && serverPages.length === 0) {
|
|
131
|
+
console.warn(`\u26A0 Pages found in ${pagesDir} but none are server components`);
|
|
132
|
+
}
|
|
133
|
+
if (serverPages.length === 0) return [];
|
|
134
|
+
const globalClientRegistry = collectGlobalClientRegistry(serverPages, pagesDir);
|
|
135
|
+
const prerenderedHtml = await bundleClientComponents(globalClientRegistry, pagesDir, staticDir);
|
|
136
|
+
const prerenderedHtmlRecord = Object.fromEntries(prerenderedHtml);
|
|
137
|
+
const builtPages = [];
|
|
138
|
+
for (const page of serverPages) {
|
|
139
|
+
const { funcPath, absPath } = page;
|
|
140
|
+
console.log(` building ${fs.existsSync(absPath) ? absPath : absPath} \u2192 ${funcPath} [page]`);
|
|
141
|
+
const layoutPaths = findPageLayouts(absPath, pagesDir);
|
|
142
|
+
const { registry, clientComponentNames } = buildPerPageRegistry(absPath, layoutPaths, pagesDir);
|
|
143
|
+
const bundleText = await bundlePageHandler({
|
|
144
|
+
absPath,
|
|
145
|
+
pagesDir,
|
|
146
|
+
clientComponentNames,
|
|
147
|
+
allClientIds: [...registry.keys()],
|
|
148
|
+
layoutPaths,
|
|
149
|
+
prerenderedHtml: prerenderedHtmlRecord
|
|
150
|
+
});
|
|
151
|
+
builtPages.push({ ...page, bundleText });
|
|
152
|
+
}
|
|
153
|
+
return builtPages;
|
|
154
|
+
}
|
|
155
|
+
function makeApiAdapterSource(handlerFilename) {
|
|
156
|
+
return `
|
|
157
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
158
|
+
import * as mod from ${JSON.stringify("./" + handlerFilename)};
|
|
159
|
+
|
|
160
|
+
function enhance(res: ServerResponse) {
|
|
161
|
+
(res as any).json = function (data: any, status = 200) {
|
|
162
|
+
this.statusCode = status;
|
|
163
|
+
this.setHeader('Content-Type', 'application/json');
|
|
164
|
+
this.end(JSON.stringify(data));
|
|
165
|
+
};
|
|
166
|
+
(res as any).status = function (code: number) { this.statusCode = code; return this; };
|
|
167
|
+
return res;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function parseBody(req: IncomingMessage): Promise<any> {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
let body = '';
|
|
173
|
+
req.on('data', chunk => { body += chunk.toString(); });
|
|
174
|
+
req.on('end', () => {
|
|
175
|
+
try {
|
|
176
|
+
resolve(body && req.headers['content-type']?.includes('application/json')
|
|
177
|
+
? JSON.parse(body) : body);
|
|
178
|
+
} catch (e) { reject(e); }
|
|
179
|
+
});
|
|
180
|
+
req.on('error', reject);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export default async function handler(req: IncomingMessage, res: ServerResponse) {
|
|
185
|
+
const method = (req.method || 'GET').toUpperCase();
|
|
186
|
+
const apiRes = enhance(res);
|
|
187
|
+
const apiReq = req as any;
|
|
188
|
+
|
|
189
|
+
apiReq.body = await parseBody(req);
|
|
190
|
+
apiReq.query = Object.fromEntries(new URL(req.url || '/', 'http://localhost').searchParams);
|
|
191
|
+
apiReq.params = apiReq.query;
|
|
192
|
+
|
|
193
|
+
const fn = (mod as any)[method] ?? (mod as any).default;
|
|
194
|
+
if (typeof fn !== 'function') {
|
|
195
|
+
(apiRes as any).json({ error: \`Method \${method} not allowed\` }, 405);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
await fn(apiReq, apiRes);
|
|
199
|
+
}
|
|
200
|
+
`.trimStart();
|
|
201
|
+
}
|
|
202
|
+
function makePageAdapterSource(opts) {
|
|
203
|
+
const {
|
|
204
|
+
pageImport,
|
|
205
|
+
layoutImports,
|
|
206
|
+
clientComponentNames,
|
|
207
|
+
allClientIds,
|
|
208
|
+
layoutArrayItems,
|
|
209
|
+
prerenderedHtml
|
|
210
|
+
} = opts;
|
|
211
|
+
return `
|
|
212
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
213
|
+
import * as __page__ from ${pageImport};
|
|
214
|
+
${layoutImports}
|
|
215
|
+
|
|
216
|
+
// \u2500\u2500\u2500 Pre-built client component registry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
217
|
+
// Computed at BUILD TIME from the import tree. Source files are not deployed,
|
|
218
|
+
// so we must not read them with fs.readFileSync at runtime.
|
|
219
|
+
// Key: default-export function name \u2192 Value: stable content-hash ID
|
|
220
|
+
const CLIENT_COMPONENTS: Record<string, string> = ${JSON.stringify(clientComponentNames)};
|
|
221
|
+
|
|
222
|
+
// All client component IDs reachable from this page (page + layouts).
|
|
223
|
+
// Sent to initRuntime so the browser pre-loads all bundles for SPA navigation.
|
|
224
|
+
const ALL_CLIENT_IDS: string[] = ${JSON.stringify(allClientIds)};
|
|
225
|
+
|
|
226
|
+
// Pre-rendered HTML for each client component, produced at BUILD TIME by
|
|
227
|
+
// renderToString with default props. Used directly in the span wrapper so
|
|
228
|
+
// the server response contains real markup and React hydration never sees a
|
|
229
|
+
// mismatch. No react-dom/server is needed at runtime.
|
|
230
|
+
const PRERENDERED_HTML: Record<string, string> = ${JSON.stringify(prerenderedHtml)};
|
|
231
|
+
|
|
232
|
+
// \u2500\u2500\u2500 html-store (inlined \u2014 no external refs) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
233
|
+
// Uses the same globalThis Symbol key as html-store.ts so any useHtml() call
|
|
234
|
+
// (however imported) writes into the same store that runWithHtmlStore reads.
|
|
235
|
+
type TitleValue = string | ((prev: string) => string);
|
|
236
|
+
interface HtmlStore {
|
|
237
|
+
titleOps: TitleValue[];
|
|
238
|
+
htmlAttrs: Record<string, string | undefined>;
|
|
239
|
+
bodyAttrs: Record<string, string | undefined>;
|
|
240
|
+
meta: Record<string, string | undefined>[];
|
|
241
|
+
link: Record<string, string | undefined>[];
|
|
242
|
+
script: Record<string, any>[];
|
|
243
|
+
style: { content?: string; media?: string }[];
|
|
244
|
+
}
|
|
245
|
+
const __STORE_KEY__ = Symbol.for('__nukejs_html_store__');
|
|
246
|
+
function __getStore(): HtmlStore | null { return (globalThis as any)[__STORE_KEY__] ?? null; }
|
|
247
|
+
function __setStore(s: HtmlStore | null): void { (globalThis as any)[__STORE_KEY__] = s; }
|
|
248
|
+
function __emptyStore(): HtmlStore {
|
|
249
|
+
return { titleOps: [], htmlAttrs: {}, bodyAttrs: {}, meta: [], link: [], script: [], style: [] };
|
|
250
|
+
}
|
|
251
|
+
async function runWithHtmlStore(fn: () => Promise<void>): Promise<HtmlStore> {
|
|
252
|
+
__setStore(__emptyStore());
|
|
253
|
+
try { await fn(); return { ...(__getStore() ?? __emptyStore()) }; }
|
|
254
|
+
finally { __setStore(null); }
|
|
255
|
+
}
|
|
256
|
+
function resolveTitle(ops: TitleValue[], fallback = ''): string {
|
|
257
|
+
let t = fallback;
|
|
258
|
+
for (let i = ops.length - 1; i >= 0; i--) {
|
|
259
|
+
const op = ops[i]; t = typeof op === 'string' ? op : op(t);
|
|
260
|
+
}
|
|
261
|
+
return t;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// \u2500\u2500\u2500 HTML 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
|
|
265
|
+
function escapeHtml(s: string): string {
|
|
266
|
+
return String(s)
|
|
267
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
268
|
+
.replace(/"/g, '"').replace(/'/g, ''');
|
|
269
|
+
}
|
|
270
|
+
function escapeAttr(s: string): string {
|
|
271
|
+
return String(s).replace(/&/g, '&').replace(/"/g, '"');
|
|
272
|
+
}
|
|
273
|
+
function renderAttrs(attrs: Record<string, string | boolean | undefined>): string {
|
|
274
|
+
return Object.entries(attrs)
|
|
275
|
+
.filter(([, v]) => v !== undefined && v !== false)
|
|
276
|
+
.map(([k, v]) => v === true ? k : \`\${k}="\${escapeAttr(String(v))}"\`)
|
|
277
|
+
.join(' ');
|
|
278
|
+
}
|
|
279
|
+
function openTag(tag: string, attrs: Record<string, string | undefined>): string {
|
|
280
|
+
const s = renderAttrs(attrs as Record<string, string | boolean | undefined>);
|
|
281
|
+
return s ? \`<\${tag} \${s}>\` : \`<\${tag}>\`;
|
|
282
|
+
}
|
|
283
|
+
function metaKey(k: string): string { return k === 'httpEquiv' ? 'http-equiv' : k; }
|
|
284
|
+
function linkKey(k: string): string {
|
|
285
|
+
if (k === 'hrefLang') return 'hreflang';
|
|
286
|
+
if (k === 'crossOrigin') return 'crossorigin';
|
|
287
|
+
return k;
|
|
288
|
+
}
|
|
289
|
+
function renderMetaTag(tag: Record<string, string | undefined>): string {
|
|
290
|
+
const attrs: Record<string, string | undefined> = {};
|
|
291
|
+
for (const [k, v] of Object.entries(tag)) if (v !== undefined) attrs[metaKey(k)] = v;
|
|
292
|
+
return \` <meta \${renderAttrs(attrs as any)} />\`;
|
|
293
|
+
}
|
|
294
|
+
function renderLinkTag(tag: Record<string, string | undefined>): string {
|
|
295
|
+
const attrs: Record<string, string | undefined> = {};
|
|
296
|
+
for (const [k, v] of Object.entries(tag)) if (v !== undefined) attrs[linkKey(k)] = v;
|
|
297
|
+
return \` <link \${renderAttrs(attrs as any)} />\`;
|
|
298
|
+
}
|
|
299
|
+
function renderScriptTag(tag: any): string {
|
|
300
|
+
const attrs: Record<string, any> = {
|
|
301
|
+
src: tag.src, type: tag.type, crossorigin: tag.crossOrigin,
|
|
302
|
+
integrity: tag.integrity, defer: tag.defer, async: tag.async, nomodule: tag.noModule,
|
|
303
|
+
};
|
|
304
|
+
const s = renderAttrs(attrs);
|
|
305
|
+
const open = s ? \`<script \${s}>\` : '<script>';
|
|
306
|
+
return \` \${open}\${tag.src ? '' : (tag.content ?? '')}</script>\`;
|
|
307
|
+
}
|
|
308
|
+
function renderStyleTag(tag: any): string {
|
|
309
|
+
const media = tag.media ? \` media="\${escapeAttr(tag.media)}"\` : '';
|
|
310
|
+
return \` <style\${media}>\${tag.content ?? ''}</style>\`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// \u2500\u2500\u2500 Void element set \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
314
|
+
const VOID_TAGS = new Set([
|
|
315
|
+
'area','base','br','col','embed','hr','img','input',
|
|
316
|
+
'link','meta','param','source','track','wbr',
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
// \u2500\u2500\u2500 Prop serialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
320
|
+
// Converts React element trees in props to a JSON-safe format that
|
|
321
|
+
// bundle.ts / initRuntime can reconstruct on the client.
|
|
322
|
+
function serializeProps(value: any): any {
|
|
323
|
+
if (value == null || typeof value !== 'object') return value;
|
|
324
|
+
if (typeof value === 'function') return undefined;
|
|
325
|
+
if (Array.isArray(value)) {
|
|
326
|
+
return value.map(serializeProps).filter((v: any) => v !== undefined);
|
|
327
|
+
}
|
|
328
|
+
if ((value as any).$$typeof) {
|
|
329
|
+
const { type, props: elProps } = value as any;
|
|
330
|
+
if (typeof type === 'string') {
|
|
331
|
+
return { __re: 'html', tag: type, props: serializeProps(elProps) };
|
|
332
|
+
}
|
|
333
|
+
if (typeof type === 'function') {
|
|
334
|
+
const cid = CLIENT_COMPONENTS[type.name];
|
|
335
|
+
if (cid) return { __re: 'client', componentId: cid, props: serializeProps(elProps) };
|
|
336
|
+
}
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
const out: any = {};
|
|
340
|
+
for (const [k, v] of Object.entries(value as Record<string, any>)) {
|
|
341
|
+
const s = serializeProps(v);
|
|
342
|
+
if (s !== undefined) out[k] = s;
|
|
343
|
+
}
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// \u2500\u2500\u2500 Async recursive renderer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
348
|
+
// Handles: null/undefined/boolean \u2192 '', strings/numbers \u2192 escaped text, arrays,
|
|
349
|
+
// Fragment, void/non-void HTML elements, class components, sync + async functions.
|
|
350
|
+
// Client components \u2192 <span data-hydrate-id="\u2026"> markers for browser hydration.
|
|
351
|
+
async function renderNode(node: any, hydrated: Set<string>): Promise<string> {
|
|
352
|
+
if (node == null || typeof node === 'boolean') return '';
|
|
353
|
+
if (typeof node === 'string') return escapeHtml(node);
|
|
354
|
+
if (typeof node === 'number') return String(node);
|
|
355
|
+
if (Array.isArray(node)) {
|
|
356
|
+
return (await Promise.all(node.map(n => renderNode(n, hydrated)))).join('');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const { type, props } = node as { type: any; props: Record<string, any> };
|
|
360
|
+
if (!type) return '';
|
|
361
|
+
|
|
362
|
+
if (type === Symbol.for('react.fragment')) {
|
|
363
|
+
return renderNode(props?.children ?? null, hydrated);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (typeof type === 'string') {
|
|
367
|
+
const { children, dangerouslySetInnerHTML, ...rest } = props || {};
|
|
368
|
+
const attrParts: string[] = [];
|
|
369
|
+
for (const [k, v] of Object.entries(rest as Record<string, any>)) {
|
|
370
|
+
const name = k === 'className' ? 'class' : k === 'htmlFor' ? 'for' : k;
|
|
371
|
+
if (typeof v === 'boolean') { if (v) attrParts.push(name); continue; }
|
|
372
|
+
if (v == null) continue;
|
|
373
|
+
if (k === 'style' && typeof v === 'object') {
|
|
374
|
+
const css = Object.entries(v as Record<string, any>)
|
|
375
|
+
.map(([p, val]) => \`\${p.replace(/[A-Z]/g, m => \`-\${m.toLowerCase()}\`)}:\${escapeHtml(String(val))}\`)
|
|
376
|
+
.join(';');
|
|
377
|
+
attrParts.push(\`style="\${css}"\`);
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
attrParts.push(\`\${name}="\${escapeHtml(String(v))}"\`);
|
|
381
|
+
}
|
|
382
|
+
const attrStr = attrParts.length ? ' ' + attrParts.join(' ') : '';
|
|
383
|
+
if (VOID_TAGS.has(type)) return \`<\${type}\${attrStr} />\`;
|
|
384
|
+
const inner = dangerouslySetInnerHTML
|
|
385
|
+
? (dangerouslySetInnerHTML as any).__html
|
|
386
|
+
: await renderNode(children ?? null, hydrated);
|
|
387
|
+
return \`<\${type}\${attrStr}>\${inner}</\${type}>\`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (typeof type === 'function') {
|
|
391
|
+
const clientId = CLIENT_COMPONENTS[type.name];
|
|
392
|
+
if (clientId) {
|
|
393
|
+
hydrated.add(clientId);
|
|
394
|
+
const serializedProps = serializeProps(props ?? {});
|
|
395
|
+
// Render with actual props so children/content appear in the SSR HTML.
|
|
396
|
+
// Fall back to the build-time pre-rendered HTML if the component throws
|
|
397
|
+
// (e.g. it references browser-only APIs during render).
|
|
398
|
+
let ssrHtml: string;
|
|
399
|
+
try {
|
|
400
|
+
const result = await (type as Function)(props || {});
|
|
401
|
+
ssrHtml = await renderNode(result, new Set());
|
|
402
|
+
} catch {
|
|
403
|
+
ssrHtml = PRERENDERED_HTML[clientId] ?? '';
|
|
404
|
+
}
|
|
405
|
+
return \`<span data-hydrate-id="\${clientId}" data-hydrate-props="\${escapeHtml(JSON.stringify(serializedProps))}">\${ssrHtml}</span>\`;
|
|
406
|
+
}
|
|
407
|
+
const instance = type.prototype?.isReactComponent ? new (type as any)(props) : null;
|
|
408
|
+
const result = instance ? instance.render() : await (type as Function)(props || {});
|
|
409
|
+
return renderNode(result, hydrated);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return '';
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// \u2500\u2500\u2500 Layout wrapping \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
416
|
+
const LAYOUT_COMPONENTS: Array<(props: any) => any> = [${layoutArrayItems}];
|
|
417
|
+
|
|
418
|
+
function wrapWithLayouts(element: any): any {
|
|
419
|
+
let el = element;
|
|
420
|
+
for (let i = LAYOUT_COMPONENTS.length - 1; i >= 0; i--) {
|
|
421
|
+
el = { type: LAYOUT_COMPONENTS[i], props: { children: el }, key: null, ref: null };
|
|
422
|
+
}
|
|
423
|
+
return el;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// \u2500\u2500\u2500 Handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
427
|
+
export default async function handler(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
428
|
+
try {
|
|
429
|
+
const parsed = new URL(req.url || '/', 'http://localhost');
|
|
430
|
+
const params: Record<string, string> = {};
|
|
431
|
+
parsed.searchParams.forEach((v, k) => { params[k] = v; });
|
|
432
|
+
const url = req.url || '/';
|
|
433
|
+
|
|
434
|
+
const hydrated = new Set<string>();
|
|
435
|
+
const pageElement = { type: __page__.default, props: params as any, key: null, ref: null };
|
|
436
|
+
const wrapped = wrapWithLayouts(pageElement);
|
|
437
|
+
|
|
438
|
+
let appHtml = '';
|
|
439
|
+
const store = await runWithHtmlStore(async () => {
|
|
440
|
+
appHtml = await renderNode(wrapped, hydrated);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const pageTitle = resolveTitle(store.titleOps, 'SSR App');
|
|
444
|
+
const headLines: string[] = [
|
|
445
|
+
' <meta charset="utf-8" />',
|
|
446
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
447
|
+
\` <title>\${escapeHtml(pageTitle)}</title>\`,
|
|
448
|
+
...store.meta.map(renderMetaTag),
|
|
449
|
+
...store.link.map(renderLinkTag),
|
|
450
|
+
...store.style.map(renderStyleTag),
|
|
451
|
+
...store.script.map(renderScriptTag),
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
const runtimeData = JSON.stringify({
|
|
455
|
+
hydrateIds: [...hydrated],
|
|
456
|
+
allIds: ALL_CLIENT_IDS,
|
|
457
|
+
url,
|
|
458
|
+
params,
|
|
459
|
+
debug: 'silent',
|
|
460
|
+
}).replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/&/g, '\\u0026');
|
|
461
|
+
|
|
462
|
+
const html = \`<!DOCTYPE html>
|
|
463
|
+
\${openTag('html', store.htmlAttrs)}
|
|
464
|
+
<head>
|
|
465
|
+
\${headLines.join('\\n')}
|
|
466
|
+
</head>
|
|
467
|
+
\${openTag('body', store.bodyAttrs)}
|
|
468
|
+
<div id="app">\${appHtml}</div>
|
|
469
|
+
|
|
470
|
+
<script id="__n_data" type="application/json">\${runtimeData}</script>
|
|
471
|
+
|
|
472
|
+
<script type="importmap">
|
|
473
|
+
{
|
|
474
|
+
"imports": {
|
|
475
|
+
"react": "/__react.js",
|
|
476
|
+
"react-dom/client": "/__react.js",
|
|
477
|
+
"react/jsx-runtime": "/__react.js",
|
|
478
|
+
"nukejs": "/__n.js"
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
</script>
|
|
482
|
+
|
|
483
|
+
<script type="module">
|
|
484
|
+
const { initRuntime } = await import('nukejs');
|
|
485
|
+
const data = JSON.parse(document.getElementById('__n_data').textContent);
|
|
486
|
+
await initRuntime(data);
|
|
487
|
+
</script>
|
|
488
|
+
</body>
|
|
489
|
+
</html>\`;
|
|
490
|
+
|
|
491
|
+
res.statusCode = 200;
|
|
492
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
493
|
+
res.end(html);
|
|
494
|
+
} catch (err: any) {
|
|
495
|
+
console.error('[page render error]', err);
|
|
496
|
+
res.statusCode = 500;
|
|
497
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
498
|
+
res.end('Internal Server Error');
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
`.trimStart();
|
|
502
|
+
}
|
|
503
|
+
async function bundleApiHandler(absPath) {
|
|
504
|
+
const adapterName = `_api_adapter_${crypto.randomBytes(4).toString("hex")}.ts`;
|
|
505
|
+
const adapterPath = path.join(path.dirname(absPath), adapterName);
|
|
506
|
+
fs.writeFileSync(adapterPath, makeApiAdapterSource(path.basename(absPath)));
|
|
507
|
+
let text;
|
|
508
|
+
try {
|
|
509
|
+
const result = await build({
|
|
510
|
+
entryPoints: [adapterPath],
|
|
511
|
+
bundle: true,
|
|
512
|
+
format: "esm",
|
|
513
|
+
platform: "node",
|
|
514
|
+
target: "node20",
|
|
515
|
+
packages: "external",
|
|
516
|
+
write: false
|
|
517
|
+
});
|
|
518
|
+
text = result.outputFiles[0].text;
|
|
519
|
+
} finally {
|
|
520
|
+
fs.unlinkSync(adapterPath);
|
|
521
|
+
}
|
|
522
|
+
return text;
|
|
523
|
+
}
|
|
524
|
+
async function bundlePageHandler(opts) {
|
|
525
|
+
const { absPath, clientComponentNames, allClientIds, layoutPaths, prerenderedHtml } = opts;
|
|
526
|
+
const adapterName = `_page_adapter_${crypto.randomBytes(4).toString("hex")}.ts`;
|
|
527
|
+
const adapterDir = path.dirname(absPath);
|
|
528
|
+
const adapterPath = path.join(adapterDir, adapterName);
|
|
529
|
+
const layoutImports = layoutPaths.map((lp, i) => {
|
|
530
|
+
const rel = path.relative(adapterDir, lp).replace(/\\/g, "/");
|
|
531
|
+
const importPath = rel.startsWith(".") ? rel : "./" + rel;
|
|
532
|
+
return `import __layout_${i}__ from ${JSON.stringify(importPath)};`;
|
|
533
|
+
}).join("\n");
|
|
534
|
+
const layoutArrayItems = layoutPaths.map((_, i) => `__layout_${i}__`).join(", ");
|
|
535
|
+
fs.writeFileSync(adapterPath, makePageAdapterSource({
|
|
536
|
+
pageImport: JSON.stringify("./" + path.basename(absPath)),
|
|
537
|
+
layoutImports,
|
|
538
|
+
clientComponentNames,
|
|
539
|
+
allClientIds,
|
|
540
|
+
layoutArrayItems,
|
|
541
|
+
prerenderedHtml
|
|
542
|
+
}));
|
|
543
|
+
let text;
|
|
544
|
+
try {
|
|
545
|
+
const result = await build({
|
|
546
|
+
entryPoints: [adapterPath],
|
|
547
|
+
bundle: true,
|
|
548
|
+
format: "esm",
|
|
549
|
+
platform: "node",
|
|
550
|
+
target: "node20",
|
|
551
|
+
jsx: "automatic",
|
|
552
|
+
external: [
|
|
553
|
+
// Node built-ins only — all npm packages (react, nukejs, …) are inlined
|
|
554
|
+
"node:*",
|
|
555
|
+
"http",
|
|
556
|
+
"https",
|
|
557
|
+
"fs",
|
|
558
|
+
"path",
|
|
559
|
+
"url",
|
|
560
|
+
"crypto",
|
|
561
|
+
"stream",
|
|
562
|
+
"buffer",
|
|
563
|
+
"events",
|
|
564
|
+
"util",
|
|
565
|
+
"os",
|
|
566
|
+
"net",
|
|
567
|
+
"tls",
|
|
568
|
+
"child_process",
|
|
569
|
+
"worker_threads",
|
|
570
|
+
"cluster",
|
|
571
|
+
"dgram",
|
|
572
|
+
"dns",
|
|
573
|
+
"readline",
|
|
574
|
+
"zlib",
|
|
575
|
+
"assert",
|
|
576
|
+
"module",
|
|
577
|
+
"perf_hooks",
|
|
578
|
+
"string_decoder",
|
|
579
|
+
"timers",
|
|
580
|
+
"async_hooks",
|
|
581
|
+
"v8",
|
|
582
|
+
"vm"
|
|
583
|
+
],
|
|
584
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
585
|
+
write: false
|
|
586
|
+
});
|
|
587
|
+
text = result.outputFiles[0].text;
|
|
588
|
+
} finally {
|
|
589
|
+
fs.unlinkSync(adapterPath);
|
|
590
|
+
}
|
|
591
|
+
return text;
|
|
592
|
+
}
|
|
593
|
+
async function bundleClientComponents(globalRegistry, pagesDir, staticDir) {
|
|
594
|
+
if (globalRegistry.size === 0) return /* @__PURE__ */ new Map();
|
|
595
|
+
const outDir = path.join(staticDir, "__client-component");
|
|
596
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
597
|
+
const prerendered = /* @__PURE__ */ new Map();
|
|
598
|
+
for (const [id, filePath] of globalRegistry) {
|
|
599
|
+
console.log(` bundling client ${id} (${path.relative(pagesDir, filePath)})`);
|
|
600
|
+
const browserResult = await build({
|
|
601
|
+
entryPoints: [filePath],
|
|
602
|
+
bundle: true,
|
|
603
|
+
format: "esm",
|
|
604
|
+
platform: "browser",
|
|
605
|
+
jsx: "automatic",
|
|
606
|
+
minify: true,
|
|
607
|
+
external: ["react", "react-dom/client", "react/jsx-runtime"],
|
|
608
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
609
|
+
write: false
|
|
610
|
+
});
|
|
611
|
+
fs.writeFileSync(path.join(outDir, `${id}.js`), browserResult.outputFiles[0].text);
|
|
612
|
+
const ssrTmp = path.join(
|
|
613
|
+
path.dirname(filePath),
|
|
614
|
+
`_ssr_${id}_${crypto.randomBytes(4).toString("hex")}.mjs`
|
|
615
|
+
);
|
|
616
|
+
try {
|
|
617
|
+
const ssrResult = await build({
|
|
618
|
+
entryPoints: [filePath],
|
|
619
|
+
bundle: true,
|
|
620
|
+
format: "esm",
|
|
621
|
+
platform: "node",
|
|
622
|
+
target: "node20",
|
|
623
|
+
jsx: "automatic",
|
|
624
|
+
packages: "external",
|
|
625
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
626
|
+
write: false
|
|
627
|
+
});
|
|
628
|
+
fs.writeFileSync(ssrTmp, ssrResult.outputFiles[0].text);
|
|
629
|
+
const { default: Component } = await import(pathToFileURL(ssrTmp).href);
|
|
630
|
+
const { createElement } = await import("react");
|
|
631
|
+
const { renderToString } = await import("react-dom/server");
|
|
632
|
+
prerendered.set(id, renderToString(createElement(Component, {})));
|
|
633
|
+
console.log(` prerendered ${id}`);
|
|
634
|
+
} catch (e) {
|
|
635
|
+
console.warn(` [SSR prerender failed for ${id}]`, e);
|
|
636
|
+
prerendered.set(id, "");
|
|
637
|
+
} finally {
|
|
638
|
+
if (fs.existsSync(ssrTmp)) fs.unlinkSync(ssrTmp);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
console.log(` bundled ${globalRegistry.size} client component(s) \u2192 ${path.relative(process.cwd(), outDir)}/`);
|
|
642
|
+
return prerendered;
|
|
643
|
+
}
|
|
644
|
+
async function buildReactBundle(staticDir) {
|
|
645
|
+
const result = await build({
|
|
646
|
+
stdin: {
|
|
647
|
+
contents: `
|
|
648
|
+
import React, {
|
|
649
|
+
useState, useEffect, useContext, useReducer, useCallback, useMemo,
|
|
650
|
+
useRef, useImperativeHandle, useLayoutEffect, useDebugValue,
|
|
651
|
+
useDeferredValue, useTransition, useId, useSyncExternalStore,
|
|
652
|
+
useInsertionEffect, createContext, forwardRef, memo, lazy,
|
|
653
|
+
Suspense, Fragment, StrictMode, Component, PureComponent
|
|
654
|
+
} from 'react';
|
|
655
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
656
|
+
import { hydrateRoot, createRoot } from 'react-dom/client';
|
|
657
|
+
|
|
658
|
+
export {
|
|
659
|
+
useState, useEffect, useContext, useReducer, useCallback, useMemo,
|
|
660
|
+
useRef, useImperativeHandle, useLayoutEffect, useDebugValue,
|
|
661
|
+
useDeferredValue, useTransition, useId, useSyncExternalStore,
|
|
662
|
+
useInsertionEffect, createContext, forwardRef, memo, lazy,
|
|
663
|
+
Suspense, Fragment, StrictMode, Component, PureComponent,
|
|
664
|
+
hydrateRoot, createRoot, jsx, jsxs
|
|
665
|
+
};
|
|
666
|
+
export default React;
|
|
667
|
+
`,
|
|
668
|
+
loader: "ts"
|
|
669
|
+
},
|
|
670
|
+
bundle: true,
|
|
671
|
+
write: false,
|
|
672
|
+
treeShaking: true,
|
|
673
|
+
minify: true,
|
|
674
|
+
format: "esm",
|
|
675
|
+
jsx: "automatic",
|
|
676
|
+
alias: {
|
|
677
|
+
react: path.dirname(fileURLToPath(import.meta.resolve("react/package.json"))),
|
|
678
|
+
"react-dom": path.dirname(fileURLToPath(import.meta.resolve("react-dom/package.json")))
|
|
679
|
+
},
|
|
680
|
+
define: { "process.env.NODE_ENV": '"production"' }
|
|
681
|
+
});
|
|
682
|
+
fs.writeFileSync(path.join(staticDir, "__react.js"), result.outputFiles[0].text);
|
|
683
|
+
console.log(" built __react.js");
|
|
684
|
+
}
|
|
685
|
+
async function buildNukeBundle(staticDir) {
|
|
686
|
+
const nukeDir = path.dirname(fileURLToPath(import.meta.url));
|
|
687
|
+
const result = await build({
|
|
688
|
+
entryPoints: [path.join(nukeDir, "bundle.js")],
|
|
689
|
+
bundle: true,
|
|
690
|
+
write: false,
|
|
691
|
+
format: "esm",
|
|
692
|
+
minify: true,
|
|
693
|
+
external: ["react", "react-dom/client"]
|
|
694
|
+
});
|
|
695
|
+
fs.writeFileSync(path.join(staticDir, "__n.js"), result.outputFiles[0].text);
|
|
696
|
+
console.log(" built __n.js");
|
|
697
|
+
}
|
|
698
|
+
function copyPublicFiles(publicDir, destDir) {
|
|
699
|
+
if (!fs.existsSync(publicDir)) return;
|
|
700
|
+
let count = 0;
|
|
701
|
+
(function walk(src, dest) {
|
|
702
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
703
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
704
|
+
const srcPath = path.join(src, entry.name);
|
|
705
|
+
const destPath = path.join(dest, entry.name);
|
|
706
|
+
if (entry.isDirectory()) {
|
|
707
|
+
walk(srcPath, destPath);
|
|
708
|
+
} else {
|
|
709
|
+
fs.copyFileSync(srcPath, destPath);
|
|
710
|
+
count++;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
})(publicDir, destDir);
|
|
714
|
+
if (count > 0) {
|
|
715
|
+
console.log(` copied ${count} public file(s) \u2192 ${path.relative(process.cwd(), destDir)}/`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
export {
|
|
719
|
+
analyzeFile,
|
|
720
|
+
buildNukeBundle,
|
|
721
|
+
buildPages,
|
|
722
|
+
buildPerPageRegistry,
|
|
723
|
+
buildReactBundle,
|
|
724
|
+
bundleApiHandler,
|
|
725
|
+
bundleClientComponents,
|
|
726
|
+
bundlePageHandler,
|
|
727
|
+
collectGlobalClientRegistry,
|
|
728
|
+
collectServerPages,
|
|
729
|
+
copyPublicFiles,
|
|
730
|
+
extractDefaultExportName,
|
|
731
|
+
findPageLayouts,
|
|
732
|
+
isServerComponent,
|
|
733
|
+
makeApiAdapterSource,
|
|
734
|
+
makePageAdapterSource,
|
|
735
|
+
walkFiles
|
|
736
|
+
};
|
|
737
|
+
//# sourceMappingURL=build-common.js.map
|