@webtypen/webframez-react 0.0.1

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/dist/http.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+
3
+ export type CreateNodeHandlerOptions = {
4
+ distRootDir: string;
5
+ pagesDir?: string;
6
+ manifestPath?: string;
7
+ assetsPrefix?: string;
8
+ rscPath?: string;
9
+ clientScriptUrl?: string;
10
+ basePath?: string;
11
+ liveReloadPath?: string | false;
12
+ };
13
+
14
+ export function createNodeRequestHandler(
15
+ options: CreateNodeHandlerOptions
16
+ ): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
package/dist/http.js ADDED
@@ -0,0 +1,678 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/http.ts
10
+ import fs2 from "node:fs";
11
+ import path2 from "node:path";
12
+ import { execFile } from "node:child_process";
13
+
14
+ // src/server.ts
15
+ import { renderToPipeableStream } from "react-server-dom-webpack/server";
16
+ function defaultOnError(err) {
17
+ console.error("[webframez-react] RSC render error", err);
18
+ }
19
+ function createHTMLShell(options = {}) {
20
+ const {
21
+ title = "RSC App",
22
+ rscEndpoint = "/rsc",
23
+ clientScriptUrl = "/client.js",
24
+ headTags = "",
25
+ rootHtml = "",
26
+ basename = "",
27
+ liveReloadPath,
28
+ liveReloadServerId
29
+ } = options;
30
+ const liveReloadScript = liveReloadPath && liveReloadServerId ? `<script>
31
+ (() => {
32
+ const endpoint = ${JSON.stringify(liveReloadPath)};
33
+ let currentServerId = ${JSON.stringify(liveReloadServerId)};
34
+
35
+ const connect = () => {
36
+ const source = new EventSource(endpoint);
37
+
38
+ source.onmessage = (event) => {
39
+ try {
40
+ const data = JSON.parse(event.data);
41
+ const nextServerId =
42
+ data && typeof data.serverId === "string" ? data.serverId : "";
43
+
44
+ if (nextServerId && nextServerId !== currentServerId) {
45
+ currentServerId = nextServerId;
46
+ window.location.reload();
47
+ }
48
+ } catch {
49
+ // Ignore malformed payloads in dev mode.
50
+ }
51
+ };
52
+
53
+ source.onerror = () => {
54
+ source.close();
55
+ setTimeout(connect, 350);
56
+ };
57
+ };
58
+
59
+ connect();
60
+ })();
61
+ </script>` : "";
62
+ return `<!doctype html>
63
+ <html lang="en">
64
+ <head>
65
+ <meta charset="utf-8" />
66
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
67
+ <title>${title}</title>
68
+ ${headTags}
69
+ </head>
70
+ <body>
71
+ <div id="root">${rootHtml}</div>
72
+ <script>window.__RSC_ENDPOINT = "${rscEndpoint}";</script>
73
+ <script>window.__RSC_BASENAME = "${basename}";</script>
74
+ <script type="module" src="${clientScriptUrl}"></script>
75
+ ${liveReloadScript}
76
+ </body>
77
+ </html>`;
78
+ }
79
+ function sendRSC(res, model, options = {}) {
80
+ const {
81
+ moduleMap = {},
82
+ onError = defaultOnError,
83
+ statusCode = 200,
84
+ contentType = "text/x-component"
85
+ } = options;
86
+ res.statusCode = statusCode;
87
+ res.setHeader("Content-Type", contentType);
88
+ const stream = renderToPipeableStream(model, moduleMap, { onError });
89
+ stream.pipe(res);
90
+ return stream;
91
+ }
92
+
93
+ // src/file-router.tsx
94
+ import fs from "node:fs";
95
+ import path from "node:path";
96
+
97
+ // src/router-runtime.tsx
98
+ import React from "react";
99
+ var ROUTE_CHILDREN_TAG = "webframez-route-children";
100
+ var ROUTE_CHILDREN_SENTINEL = "__webframezRouteChildren";
101
+ var ROUTE_CHILDREN_DISPLAY_NAME = "WebframezRouteChildren";
102
+ var RouteChildrenImpl = () => React.createElement(ROUTE_CHILDREN_TAG);
103
+ RouteChildrenImpl.displayName = ROUTE_CHILDREN_DISPLAY_NAME;
104
+ RouteChildrenImpl[ROUTE_CHILDREN_SENTINEL] = true;
105
+ var RouteChildren = RouteChildrenImpl;
106
+ function isRouteChildrenType(type) {
107
+ if (type === ROUTE_CHILDREN_TAG || type === RouteChildren) {
108
+ return true;
109
+ }
110
+ if (!type || typeof type !== "function" && typeof type !== "object") {
111
+ return false;
112
+ }
113
+ try {
114
+ const candidate = type;
115
+ return candidate[ROUTE_CHILDREN_SENTINEL] === true || candidate.displayName === ROUTE_CHILDREN_DISPLAY_NAME || candidate.name === "RouteChildren";
116
+ } catch {
117
+ return false;
118
+ }
119
+ }
120
+ function injectRouteChildren(node, routeChildren) {
121
+ if (node === null || node === void 0 || typeof node === "boolean") {
122
+ return node;
123
+ }
124
+ if (Array.isArray(node)) {
125
+ let changed = false;
126
+ const next = node.map((child) => {
127
+ const injected = injectRouteChildren(child, routeChildren);
128
+ if (injected !== child) {
129
+ changed = true;
130
+ }
131
+ return injected;
132
+ });
133
+ return changed ? next : node;
134
+ }
135
+ if (!React.isValidElement(node)) {
136
+ return node;
137
+ }
138
+ if (isRouteChildrenType(node.type)) {
139
+ return routeChildren;
140
+ }
141
+ const props = node.props;
142
+ if (!("children" in props)) {
143
+ return node;
144
+ }
145
+ const nextChildren = injectRouteChildren(props.children, routeChildren);
146
+ if (nextChildren === props.children) {
147
+ return node;
148
+ }
149
+ if (Array.isArray(nextChildren)) {
150
+ return React.cloneElement(node, void 0, ...nextChildren);
151
+ }
152
+ return React.cloneElement(node, void 0, nextChildren);
153
+ }
154
+
155
+ // src/file-router.tsx
156
+ import { jsx, jsxs } from "react/jsx-runtime";
157
+ function normalizePathname(pathname) {
158
+ const trimmed = pathname.replace(/\/+$/, "") || "/";
159
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
160
+ }
161
+ function splitSegments(pathname) {
162
+ const normalized = normalizePathname(pathname);
163
+ if (normalized === "/") {
164
+ return [];
165
+ }
166
+ return normalized.slice(1).split("/").filter(Boolean);
167
+ }
168
+ function walkFiles(dir) {
169
+ if (!fs.existsSync(dir)) {
170
+ return [];
171
+ }
172
+ const result = [];
173
+ const stack = [dir];
174
+ while (stack.length > 0) {
175
+ const current = stack.pop();
176
+ if (!current) {
177
+ continue;
178
+ }
179
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
180
+ const fullPath = path.join(current, entry.name);
181
+ if (entry.isDirectory()) {
182
+ stack.push(fullPath);
183
+ } else if (entry.isFile()) {
184
+ result.push(fullPath);
185
+ }
186
+ }
187
+ }
188
+ return result;
189
+ }
190
+ function readSearchParams(urlSearchParams) {
191
+ const output = {};
192
+ for (const key of urlSearchParams.keys()) {
193
+ const values = urlSearchParams.getAll(key);
194
+ output[key] = values.length <= 1 ? values[0] ?? "" : values;
195
+ }
196
+ return output;
197
+ }
198
+ function escapeHtml(value) {
199
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#39;");
200
+ }
201
+ function toRouteEntry(pagesDir, filePath) {
202
+ const normalized = filePath.replace(/\\/g, "/");
203
+ if (!normalized.endsWith("/index.js")) {
204
+ return null;
205
+ }
206
+ const relativeDir = path.dirname(path.relative(pagesDir, filePath)).replace(/\\/g, "/");
207
+ const segments = relativeDir === "." ? [] : relativeDir.split("/").filter(Boolean);
208
+ const staticCount = segments.filter((segment) => !segment.startsWith("[")).length;
209
+ return {
210
+ filePath,
211
+ segments,
212
+ staticCount
213
+ };
214
+ }
215
+ function matchRoute(entry, pathname) {
216
+ const targetSegments = splitSegments(pathname);
217
+ if (entry.segments.length !== targetSegments.length) {
218
+ return null;
219
+ }
220
+ const params = {};
221
+ for (let index = 0; index < entry.segments.length; index += 1) {
222
+ const routeSegment = entry.segments[index];
223
+ const targetSegment = targetSegments[index];
224
+ if (routeSegment.startsWith("[") && routeSegment.endsWith("]")) {
225
+ const paramName = routeSegment.slice(1, -1);
226
+ if (!paramName) {
227
+ return null;
228
+ }
229
+ params[paramName] = decodeURIComponent(targetSegment);
230
+ continue;
231
+ }
232
+ if (routeSegment !== targetSegment) {
233
+ return null;
234
+ }
235
+ }
236
+ return params;
237
+ }
238
+ function mergeHead(...configs) {
239
+ const merged = {
240
+ meta: [],
241
+ links: []
242
+ };
243
+ for (const config of configs) {
244
+ if (!config) {
245
+ continue;
246
+ }
247
+ if (config.title) {
248
+ merged.title = config.title;
249
+ }
250
+ if (config.description) {
251
+ merged.description = config.description;
252
+ }
253
+ if (config.favicon) {
254
+ merged.favicon = config.favicon;
255
+ }
256
+ if (config.meta) {
257
+ merged.meta?.push(...config.meta);
258
+ }
259
+ if (config.links) {
260
+ merged.links?.push(...config.links);
261
+ }
262
+ }
263
+ return merged;
264
+ }
265
+ function renderHeadToString(head) {
266
+ const tags = [];
267
+ if (head.description) {
268
+ tags.push(
269
+ `<meta name="description" content="${escapeHtml(head.description)}" />`
270
+ );
271
+ }
272
+ if (head.favicon) {
273
+ tags.push(`<link rel="icon" href="${escapeHtml(head.favicon)}" />`);
274
+ }
275
+ for (const meta of head.meta ?? []) {
276
+ const attrs = Object.entries(meta).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
277
+ tags.push(`<meta ${attrs} />`);
278
+ }
279
+ for (const link of head.links ?? []) {
280
+ const attrs = Object.entries(link).filter(([, value]) => Boolean(value)).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
281
+ tags.push(`<link ${attrs} />`);
282
+ }
283
+ return tags.join("\n");
284
+ }
285
+ function resolveModule(modulePath) {
286
+ delete __require.cache[modulePath];
287
+ return __require(modulePath);
288
+ }
289
+ async function resolveHead(candidate, context) {
290
+ if (!candidate.Head) {
291
+ return void 0;
292
+ }
293
+ return candidate.Head(context);
294
+ }
295
+ function findBestMatch(entries, pathname) {
296
+ const matches = [];
297
+ for (const entry of entries) {
298
+ const params = matchRoute(entry, pathname);
299
+ if (params) {
300
+ matches.push({ entry, params });
301
+ }
302
+ }
303
+ matches.sort((a, b) => {
304
+ if (b.entry.staticCount !== a.entry.staticCount) {
305
+ return b.entry.staticCount - a.entry.staticCount;
306
+ }
307
+ return b.entry.segments.length - a.entry.segments.length;
308
+ });
309
+ return matches[0] ?? null;
310
+ }
311
+ function normalizeAbortStatus(value) {
312
+ if (typeof value !== "number" || !Number.isFinite(value)) {
313
+ return 404;
314
+ }
315
+ const normalized = Math.trunc(value);
316
+ if (normalized < 100 || normalized > 599) {
317
+ return 404;
318
+ }
319
+ return normalized;
320
+ }
321
+ function createAbort(options) {
322
+ return {
323
+ __webframezRouteAbort: true,
324
+ statusCode: normalizeAbortStatus(options?.status),
325
+ message: options?.message?.trim() || "Page not found",
326
+ payload: options?.payload
327
+ };
328
+ }
329
+ function isRouteAbort(value) {
330
+ if (!value || typeof value !== "object") {
331
+ return false;
332
+ }
333
+ try {
334
+ return value.__webframezRouteAbort === true;
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
339
+ function createFileRouter(options) {
340
+ const pagesDir = options.pagesDir;
341
+ const layoutPath = path.join(pagesDir, "layout.js");
342
+ const errorPath = path.join(pagesDir, "errors.js");
343
+ function buildRouteEntries() {
344
+ const files = walkFiles(pagesDir);
345
+ return files.map((filePath) => toRouteEntry(pagesDir, filePath)).filter((entry) => entry !== null).sort((a, b) => {
346
+ if (b.staticCount !== a.staticCount) {
347
+ return b.staticCount - a.staticCount;
348
+ }
349
+ return b.segments.length - a.segments.length;
350
+ });
351
+ }
352
+ async function renderError(context, statusCode, message, payload) {
353
+ const errorProps = {
354
+ ...context,
355
+ statusCode,
356
+ message,
357
+ payload
358
+ };
359
+ const layoutModule = fs.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
360
+ if (!fs.existsSync(errorPath)) {
361
+ const fallback = /* @__PURE__ */ jsxs("main", { style: { fontFamily: "system-ui, sans-serif", padding: 24 }, children: [
362
+ /* @__PURE__ */ jsx("h1", { children: statusCode }),
363
+ /* @__PURE__ */ jsx("p", { children: message })
364
+ ] });
365
+ return {
366
+ statusCode,
367
+ model: fallback,
368
+ head: {
369
+ title: `${statusCode} - ${message}`
370
+ }
371
+ };
372
+ }
373
+ const errorModule = resolveModule(errorPath);
374
+ const errorNode = errorModule.default(errorProps);
375
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
376
+ const errorHead = await resolveHead(errorModule, errorProps);
377
+ const model = layoutModule ? injectRouteChildren(layoutModule.default(context), errorNode) : errorNode;
378
+ return {
379
+ statusCode,
380
+ model,
381
+ head: mergeHead(layoutHead, errorHead)
382
+ };
383
+ }
384
+ async function resolve(input) {
385
+ const pathname = normalizePathname(input.pathname);
386
+ const contextBase = {
387
+ pathname,
388
+ params: {},
389
+ searchParams: input.searchParams,
390
+ cookies: input.cookies ?? {},
391
+ abort: (options2) => {
392
+ throw createAbort(options2);
393
+ }
394
+ };
395
+ let activeContext = contextBase;
396
+ try {
397
+ const entries = buildRouteEntries();
398
+ const match = findBestMatch(entries, pathname);
399
+ if (!match) {
400
+ return renderError(contextBase, 404, "Page not found");
401
+ }
402
+ const context = {
403
+ ...contextBase,
404
+ params: match.params
405
+ };
406
+ activeContext = context;
407
+ const pageModule = resolveModule(match.entry.filePath);
408
+ const layoutModule = fs.existsSync(layoutPath) ? resolveModule(layoutPath) : null;
409
+ const pageNode = pageModule.default(context);
410
+ const layoutHead = layoutModule ? await resolveHead(layoutModule, context) : void 0;
411
+ const pageHead = await resolveHead(pageModule, context);
412
+ const model = layoutModule ? injectRouteChildren(layoutModule.default(context), pageNode) : pageNode;
413
+ return {
414
+ statusCode: 200,
415
+ model,
416
+ head: mergeHead(layoutHead, pageHead)
417
+ };
418
+ } catch (error) {
419
+ if (isRouteAbort(error)) {
420
+ return renderError(activeContext, error.statusCode, error.message, error.payload);
421
+ }
422
+ console.error("[webframez-react] Failed to resolve route", error);
423
+ return renderError(contextBase, 500, "Internal server error");
424
+ }
425
+ }
426
+ return {
427
+ resolve,
428
+ readSearchParams
429
+ };
430
+ }
431
+ function parseSearchParams(query) {
432
+ return readSearchParams(query);
433
+ }
434
+
435
+ // src/http.ts
436
+ function normalizeBasePath(basePath) {
437
+ if (!basePath || basePath === "/") {
438
+ return "";
439
+ }
440
+ const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
441
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
442
+ }
443
+ function stripBasePath(pathname, basePath) {
444
+ if (!basePath) {
445
+ return pathname;
446
+ }
447
+ if (pathname === basePath) {
448
+ return "/";
449
+ }
450
+ if (pathname.startsWith(`${basePath}/`)) {
451
+ return pathname.slice(basePath.length) || "/";
452
+ }
453
+ return pathname;
454
+ }
455
+ function runNodeCommand(args) {
456
+ return new Promise((resolve, reject) => {
457
+ execFile(process.execPath, args, { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 }, (error, stdout, stderr) => {
458
+ if (error) {
459
+ const out = stderr && stderr.trim() !== "" ? stderr : stdout;
460
+ reject(new Error(out || error.message));
461
+ return;
462
+ }
463
+ resolve({ stdout, stderr });
464
+ });
465
+ });
466
+ }
467
+ async function renderInitialHtmlInWorker(options) {
468
+ const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
469
+ const script = `
470
+ const path = require("node:path");
471
+ const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
472
+ globalThis.__RSC_BASENAME = input.basename || "";
473
+ const { createFileRouter } = require("webframez-react/router");
474
+ const reactDomPkg = require.resolve("react-dom/package.json", {
475
+ paths: [process.cwd(), input.pagesDir]
476
+ });
477
+ const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
478
+
479
+ (async () => {
480
+ const router = createFileRouter({ pagesDir: input.pagesDir });
481
+ const resolved = await router.resolve({
482
+ pathname: input.pathname,
483
+ searchParams: input.searchParams || {},
484
+ cookies: input.cookies || {},
485
+ });
486
+ const html = reactDomServer.renderToString(resolved.model);
487
+ process.stdout.write(JSON.stringify({ html }));
488
+ })().catch((error) => {
489
+ process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
490
+ process.exit(1);
491
+ });
492
+ `;
493
+ const { stdout } = await runNodeCommand(["-e", script, payload]);
494
+ const parsed = JSON.parse(stdout || "{}");
495
+ return parsed.html || "";
496
+ }
497
+ function withRequestBasename(basename, fn) {
498
+ const target = globalThis;
499
+ const previous = target.__RSC_BASENAME;
500
+ target.__RSC_BASENAME = basename;
501
+ const finish = () => {
502
+ target.__RSC_BASENAME = previous;
503
+ };
504
+ try {
505
+ const result = fn();
506
+ if (result && typeof result.then === "function") {
507
+ return result.finally(finish);
508
+ }
509
+ finish();
510
+ return result;
511
+ } catch (error) {
512
+ finish();
513
+ throw error;
514
+ }
515
+ }
516
+ function parseCookies(rawCookieHeader) {
517
+ const raw = Array.isArray(rawCookieHeader) ? rawCookieHeader.join("; ") : rawCookieHeader ?? "";
518
+ const output = {};
519
+ if (!raw || raw.trim() === "") {
520
+ return output;
521
+ }
522
+ for (const pair of raw.split(";")) {
523
+ const part = pair.trim();
524
+ if (!part) {
525
+ continue;
526
+ }
527
+ const eqIndex = part.indexOf("=");
528
+ const name = eqIndex >= 0 ? part.slice(0, eqIndex).trim() : part.trim();
529
+ const value = eqIndex >= 0 ? part.slice(eqIndex + 1).trim() : "";
530
+ if (!name) {
531
+ continue;
532
+ }
533
+ output[name] = decodeURIComponent(value);
534
+ }
535
+ return output;
536
+ }
537
+ function createNodeRequestHandler(options) {
538
+ const devServerId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
539
+ const distRootDir = path2.resolve(options.distRootDir);
540
+ const pagesDir = path2.resolve(options.pagesDir ?? path2.join(distRootDir, "pages"));
541
+ const manifestPath = path2.resolve(
542
+ options.manifestPath ?? path2.join(distRootDir, "react-client-manifest.json")
543
+ );
544
+ const assetsPrefix = options.assetsPrefix ?? "/assets/";
545
+ const rscPath = options.rscPath ?? "/rsc";
546
+ const clientScriptUrl = options.clientScriptUrl ?? "/assets/client.js";
547
+ const basePath = normalizeBasePath(options.basePath);
548
+ const nodeEnv = process.env.NODE_ENV || "";
549
+ const runningInWatchMode = Array.isArray(process.execArgv) && process.execArgv.includes("--watch");
550
+ const liveReloadEnabled = options.liveReloadPath !== false && (nodeEnv === "development" || runningInWatchMode);
551
+ const liveReloadPath = !liveReloadEnabled ? "" : options.liveReloadPath ?? `${basePath || ""}/__webframez_live_reload`;
552
+ const liveReloadClients = /* @__PURE__ */ new Set();
553
+ const router = createFileRouter({ pagesDir });
554
+ const moduleMap = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
555
+ return async function handleRequest(req, res) {
556
+ if (!req.url) {
557
+ res.statusCode = 400;
558
+ res.end("Bad request");
559
+ return;
560
+ }
561
+ const url = new URL(req.url, "http://localhost");
562
+ const requestCookies = parseCookies(req.headers.cookie);
563
+ if (liveReloadPath && url.pathname === liveReloadPath) {
564
+ const accept = String(req.headers.accept || "");
565
+ if (accept.includes("text/event-stream")) {
566
+ res.statusCode = 200;
567
+ res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
568
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
569
+ res.setHeader("Connection", "keep-alive");
570
+ res.setHeader("X-Accel-Buffering", "no");
571
+ if (typeof res.flushHeaders === "function") {
572
+ res.flushHeaders();
573
+ }
574
+ res.write(`retry: 400
575
+ `);
576
+ res.write(`data: ${JSON.stringify({ serverId: devServerId })}
577
+
578
+ `);
579
+ liveReloadClients.add(res);
580
+ const heartbeat = setInterval(() => {
581
+ if (!res.writableEnded && !res.destroyed) {
582
+ res.write(`: ping
583
+
584
+ `);
585
+ }
586
+ }, 15e3);
587
+ const cleanup = () => {
588
+ clearInterval(heartbeat);
589
+ liveReloadClients.delete(res);
590
+ };
591
+ req.on("close", cleanup);
592
+ res.on("close", cleanup);
593
+ return;
594
+ }
595
+ res.statusCode = 200;
596
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
597
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
598
+ res.end(JSON.stringify({ serverId: devServerId, clients: liveReloadClients.size }));
599
+ return;
600
+ }
601
+ if (url.pathname === rscPath) {
602
+ const pathname = stripBasePath(url.searchParams.get("path") || "/", basePath);
603
+ const search = new URLSearchParams(url.searchParams.get("search") || "");
604
+ const resolved2 = await withRequestBasename(
605
+ basePath,
606
+ () => router.resolve({
607
+ pathname,
608
+ searchParams: parseSearchParams(search),
609
+ cookies: requestCookies
610
+ })
611
+ );
612
+ sendRSC(res, resolved2.model, {
613
+ moduleMap,
614
+ statusCode: resolved2.statusCode
615
+ });
616
+ return;
617
+ }
618
+ if (url.pathname.startsWith(assetsPrefix)) {
619
+ const relative = url.pathname.slice(assetsPrefix.length);
620
+ const filePath = path2.resolve(distRootDir, relative);
621
+ if (!filePath.startsWith(distRootDir)) {
622
+ res.statusCode = 400;
623
+ res.end("Invalid path");
624
+ return;
625
+ }
626
+ const ext = path2.extname(filePath);
627
+ if (ext === ".js" || ext === ".mjs") {
628
+ res.setHeader("Content-Type", "text/javascript; charset=utf-8");
629
+ } else if (ext === ".json") {
630
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
631
+ }
632
+ const stream = fs2.createReadStream(filePath);
633
+ stream.on("error", () => {
634
+ res.statusCode = 404;
635
+ res.end("Not found");
636
+ });
637
+ stream.pipe(res);
638
+ return;
639
+ }
640
+ const resolved = await withRequestBasename(
641
+ basePath,
642
+ () => router.resolve({
643
+ pathname: stripBasePath(url.pathname, basePath),
644
+ searchParams: parseSearchParams(url.searchParams),
645
+ cookies: requestCookies
646
+ })
647
+ );
648
+ let rootHtml = "";
649
+ try {
650
+ rootHtml = await renderInitialHtmlInWorker({
651
+ pagesDir,
652
+ pathname: stripBasePath(url.pathname, basePath),
653
+ searchParams: parseSearchParams(url.searchParams),
654
+ cookies: requestCookies,
655
+ basename: basePath
656
+ });
657
+ } catch (error) {
658
+ console.error("[webframez-react] Failed to render initial HTML", error);
659
+ }
660
+ res.statusCode = resolved.statusCode;
661
+ res.setHeader("Content-Type", "text/html");
662
+ res.end(
663
+ createHTMLShell({
664
+ title: resolved.head.title || "Webframez React",
665
+ headTags: renderHeadToString(resolved.head),
666
+ clientScriptUrl,
667
+ rscEndpoint: rscPath,
668
+ rootHtml,
669
+ basename: basePath,
670
+ liveReloadPath: liveReloadPath || void 0,
671
+ liveReloadServerId: liveReloadPath ? devServerId : void 0
672
+ })
673
+ );
674
+ };
675
+ }
676
+ export {
677
+ createNodeRequestHandler
678
+ };