@webtypen/webframez-react 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.
@@ -481,57 +481,19 @@ function parseSearchParams(query) {
481
481
  }
482
482
 
483
483
  // src/http.ts
484
- function normalizeBasePath(basePath) {
485
- if (!basePath || basePath === "/") {
486
- return "";
487
- }
488
- const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
489
- return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
490
- }
491
- function stripBasePath(pathname, basePath) {
492
- if (!basePath) {
493
- return pathname;
494
- }
495
- if (pathname === basePath) {
496
- return "/";
497
- }
498
- if (pathname.startsWith(`${basePath}/`)) {
499
- return pathname.slice(basePath.length) || "/";
500
- }
501
- return pathname;
484
+ function createInitialHtmlErrorMarkup(message) {
485
+ return `<main style="font-family:system-ui,sans-serif;padding:24px"><h1 style="margin:0 0 12px">500</h1><p style="margin:0">${message}</p></main>`;
502
486
  }
503
- function runNodeCommand(args) {
504
- return new Promise((resolve, reject) => {
505
- const execArgs = [
506
- "--conditions",
507
- "react-server",
508
- "-r",
509
- "@webtypen/webframez-react/register",
510
- ...args
511
- ];
512
- (0, import_node_child_process.execFile)(
513
- process.execPath,
514
- execArgs,
515
- { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 },
516
- (error, stdout, stderr) => {
517
- if (error) {
518
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
519
- reject(new Error(out || error.message));
520
- return;
521
- }
522
- resolve({ stdout, stderr });
523
- }
524
- );
525
- });
526
- }
527
- async function renderInitialHtmlInWorker(options) {
528
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
529
- const script = `
487
+ var INITIAL_HTML_WORKER_SCRIPT = `
530
488
  const path = require("node:path");
531
489
  const Module = require("node:module");
532
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
533
- globalThis.__RSC_BASENAME = input.basename || "";
534
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
490
+
491
+ const pagesDir = process.env.WEBFRAMEZ_REACT_PAGES_DIR || "";
492
+ if (!pagesDir) {
493
+ throw new Error("Missing WEBFRAMEZ_REACT_PAGES_DIR");
494
+ }
495
+
496
+ const appRequire = Module.createRequire(path.join(pagesDir, "__webframez_react_worker__.js"));
535
497
  const originalResolveFilename = Module._resolveFilename;
536
498
  const forcedResolutions = new Map([
537
499
  ["react", appRequire.resolve("react")],
@@ -539,35 +501,181 @@ const forcedResolutions = new Map([
539
501
  ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
540
502
  ["react-dom/client", appRequire.resolve("react-dom/client")]
541
503
  ]);
504
+
542
505
  Module._resolveFilename = function(request, parent, isMain, options) {
543
506
  if (forcedResolutions.has(request)) {
544
507
  return forcedResolutions.get(request);
545
508
  }
546
509
  return originalResolveFilename.call(this, request, parent, isMain, options);
547
510
  };
511
+
548
512
  const { createFileRouter } = require("@webtypen/webframez-react/router");
549
513
  const reactDomPkg = require.resolve("react-dom/package.json", {
550
- paths: [process.cwd(), input.pagesDir]
514
+ paths: [process.cwd(), pagesDir]
551
515
  });
552
516
  const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
517
+ const router = createFileRouter({ pagesDir });
553
518
 
554
- (async () => {
555
- const router = createFileRouter({ pagesDir: input.pagesDir });
556
- const resolved = await router.resolve({
557
- pathname: input.pathname,
558
- searchParams: input.searchParams || {},
559
- cookies: input.cookies || {},
560
- });
561
- const html = reactDomServer.renderToString(resolved.model);
562
- process.stdout.write(JSON.stringify({ html }));
563
- })().catch((error) => {
564
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
565
- process.exit(1);
519
+ process.on("message", async (message) => {
520
+ if (!message || message.type !== "render") {
521
+ return;
522
+ }
523
+
524
+ const previousBasename = globalThis.__RSC_BASENAME;
525
+ globalThis.__RSC_BASENAME = message.payload.basename || "";
526
+
527
+ try {
528
+ const resolved = await router.resolve({
529
+ pathname: message.payload.pathname,
530
+ searchParams: message.payload.searchParams || {},
531
+ cookies: message.payload.cookies || {},
532
+ });
533
+ const html = reactDomServer.renderToString(resolved.model);
534
+ if (typeof process.send === "function") {
535
+ process.send({ id: message.id, ok: true, html });
536
+ }
537
+ } catch (error) {
538
+ const formatted = error && (error.stack || String(error))
539
+ ? (error.stack || String(error))
540
+ : "Unknown SSR worker error";
541
+ if (typeof process.send === "function") {
542
+ process.send({ id: message.id, ok: false, error: formatted });
543
+ }
544
+ } finally {
545
+ globalThis.__RSC_BASENAME = previousBasename;
546
+ }
566
547
  });
567
548
  `;
568
- const { stdout } = await runNodeCommand(["-e", script, payload]);
569
- const parsed = JSON.parse(stdout || "{}");
570
- return parsed.html || "";
549
+ function normalizeBasePath(basePath) {
550
+ if (!basePath || basePath === "/") {
551
+ return "";
552
+ }
553
+ const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
554
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
555
+ }
556
+ function stripBasePath(pathname, basePath) {
557
+ if (!basePath) {
558
+ return pathname;
559
+ }
560
+ if (pathname === basePath) {
561
+ return "/";
562
+ }
563
+ if (pathname.startsWith(`${basePath}/`)) {
564
+ return pathname.slice(basePath.length) || "/";
565
+ }
566
+ return pathname;
567
+ }
568
+ function sanitizeInitialHtmlWorkerNodeOptions(rawNodeOptions) {
569
+ if (!rawNodeOptions || rawNodeOptions.trim() === "") {
570
+ return "";
571
+ }
572
+ return rawNodeOptions.replace(/(^|\s)--conditions\s+react-server(?=\s|$)/g, " ").replace(/(^|\s)-r\s+(\S*webframez-react\/register)(?=\s|$)/g, " ").replace(/\s+/g, " ").trim();
573
+ }
574
+ function createInitialHtmlWorker(pagesDir) {
575
+ let child = null;
576
+ let nextRequestId = 1;
577
+ let stderrBuffer = "";
578
+ const pending = /* @__PURE__ */ new Map();
579
+ const rejectPending = (error) => {
580
+ for (const entry of pending.values()) {
581
+ clearTimeout(entry.timeout);
582
+ entry.reject(error);
583
+ }
584
+ pending.clear();
585
+ };
586
+ const stopWorker = () => {
587
+ if (!child) {
588
+ return;
589
+ }
590
+ child.removeAllListeners();
591
+ if (!child.killed) {
592
+ child.kill();
593
+ }
594
+ child = null;
595
+ };
596
+ const startWorker = () => {
597
+ if (child && child.connected && !child.killed) {
598
+ return child;
599
+ }
600
+ stderrBuffer = "";
601
+ child = (0, import_node_child_process.spawn)(process.execPath, ["-e", INITIAL_HTML_WORKER_SCRIPT], {
602
+ cwd: process.cwd(),
603
+ env: {
604
+ ...process.env,
605
+ NODE_OPTIONS: sanitizeInitialHtmlWorkerNodeOptions(process.env.NODE_OPTIONS),
606
+ WEBFRAMEZ_REACT_PAGES_DIR: pagesDir
607
+ },
608
+ stdio: ["ignore", "ignore", "pipe", "ipc"]
609
+ });
610
+ child.on("message", (message) => {
611
+ if (!message || typeof message.id !== "number") {
612
+ return;
613
+ }
614
+ const entry = pending.get(message.id);
615
+ if (!entry) {
616
+ return;
617
+ }
618
+ pending.delete(message.id);
619
+ clearTimeout(entry.timeout);
620
+ if (message.ok) {
621
+ entry.resolve(message.html);
622
+ return;
623
+ }
624
+ entry.reject(new Error(message.error));
625
+ });
626
+ child.stderr?.on("data", (chunk) => {
627
+ stderrBuffer = `${stderrBuffer}${chunk.toString("utf8")}`.slice(-8192);
628
+ });
629
+ child.on("exit", (code, signal) => {
630
+ const suffix = stderrBuffer.trim() !== "" ? `
631
+ ${stderrBuffer.trim()}` : "";
632
+ rejectPending(
633
+ new Error(
634
+ `[webframez-react] Initial HTML worker exited (${signal ?? code ?? "unknown"})${suffix}`
635
+ )
636
+ );
637
+ child = null;
638
+ });
639
+ child.on("error", (error) => {
640
+ rejectPending(error instanceof Error ? error : new Error(String(error)));
641
+ child = null;
642
+ });
643
+ return child;
644
+ };
645
+ return {
646
+ render(payload) {
647
+ const activeChild = startWorker();
648
+ const requestId = nextRequestId++;
649
+ return new Promise((resolve, reject) => {
650
+ const timeout = setTimeout(() => {
651
+ rejectPending(new Error("[webframez-react] Initial HTML worker timed out"));
652
+ stopWorker();
653
+ }, 1e4);
654
+ pending.set(requestId, { resolve, reject, timeout });
655
+ const request = {
656
+ id: requestId,
657
+ type: "render",
658
+ payload
659
+ };
660
+ activeChild.send(request, (error) => {
661
+ if (!error) {
662
+ return;
663
+ }
664
+ const entry = pending.get(requestId);
665
+ if (!entry) {
666
+ return;
667
+ }
668
+ pending.delete(requestId);
669
+ clearTimeout(entry.timeout);
670
+ entry.reject(error instanceof Error ? error : new Error(String(error)));
671
+ });
672
+ });
673
+ },
674
+ dispose() {
675
+ rejectPending(new Error("[webframez-react] Initial HTML worker disposed"));
676
+ stopWorker();
677
+ }
678
+ };
571
679
  }
572
680
  function withRequestBasename(basename, fn) {
573
681
  const target = globalThis;
@@ -627,6 +735,13 @@ function createNodeRequestHandler(options) {
627
735
  const liveReloadClients = /* @__PURE__ */ new Set();
628
736
  const router = createFileRouter({ pagesDir });
629
737
  const moduleMap = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf-8"));
738
+ const initialHtmlWorker = createInitialHtmlWorker(pagesDir);
739
+ const disposeInitialHtmlWorker = () => {
740
+ initialHtmlWorker.dispose();
741
+ };
742
+ process.once("exit", disposeInitialHtmlWorker);
743
+ process.once("SIGINT", disposeInitialHtmlWorker);
744
+ process.once("SIGTERM", disposeInitialHtmlWorker);
630
745
  return async function handleRequest(req, res) {
631
746
  if (!req.url) {
632
747
  res.statusCode = 400;
@@ -726,8 +841,7 @@ function createNodeRequestHandler(options) {
726
841
  );
727
842
  let rootHtml = "";
728
843
  try {
729
- rootHtml = await renderInitialHtmlInWorker({
730
- pagesDir,
844
+ rootHtml = await initialHtmlWorker.render({
731
845
  pathname: stripBasePath(url.pathname, basePath),
732
846
  searchParams: parseSearchParams(url.searchParams),
733
847
  cookies: requestCookies,
@@ -735,6 +849,32 @@ function createNodeRequestHandler(options) {
735
849
  });
736
850
  } catch (error) {
737
851
  console.error("[webframez-react] Failed to render initial HTML", error);
852
+ try {
853
+ initialHtmlWorker.dispose();
854
+ rootHtml = await initialHtmlWorker.render({
855
+ pathname: stripBasePath(url.pathname, basePath),
856
+ searchParams: parseSearchParams(url.searchParams),
857
+ cookies: requestCookies,
858
+ basename: basePath
859
+ });
860
+ } catch (retryError) {
861
+ console.error("[webframez-react] Retry for initial HTML failed", retryError);
862
+ res.statusCode = 500;
863
+ res.setHeader("Content-Type", "text/html");
864
+ res.end(
865
+ createHTMLShell({
866
+ title: "500 - Initial HTML render failed",
867
+ headTags: "",
868
+ clientScriptUrl,
869
+ rscEndpoint: rscPath,
870
+ rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
871
+ basename: basePath,
872
+ liveReloadPath: liveReloadPath || void 0,
873
+ liveReloadServerId: liveReloadPath ? devServerId : void 0
874
+ })
875
+ );
876
+ return;
877
+ }
738
878
  }
739
879
  res.statusCode = resolved.statusCode;
740
880
  res.setHeader("Content-Type", "text/html");
@@ -9,7 +9,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  // src/http.ts
10
10
  import fs2 from "node:fs";
11
11
  import path2 from "node:path";
12
- import { execFile } from "node:child_process";
12
+ import { spawn } from "node:child_process";
13
13
 
14
14
  // src/server.ts
15
15
  import { renderToPipeableStream } from "react-server-dom-webpack/server";
@@ -453,57 +453,19 @@ function parseSearchParams(query) {
453
453
  }
454
454
 
455
455
  // src/http.ts
456
- function normalizeBasePath(basePath) {
457
- if (!basePath || basePath === "/") {
458
- return "";
459
- }
460
- const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
461
- return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
462
- }
463
- function stripBasePath(pathname, basePath) {
464
- if (!basePath) {
465
- return pathname;
466
- }
467
- if (pathname === basePath) {
468
- return "/";
469
- }
470
- if (pathname.startsWith(`${basePath}/`)) {
471
- return pathname.slice(basePath.length) || "/";
472
- }
473
- return pathname;
474
- }
475
- function runNodeCommand(args) {
476
- return new Promise((resolve, reject) => {
477
- const execArgs = [
478
- "--conditions",
479
- "react-server",
480
- "-r",
481
- "@webtypen/webframez-react/register",
482
- ...args
483
- ];
484
- execFile(
485
- process.execPath,
486
- execArgs,
487
- { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 },
488
- (error, stdout, stderr) => {
489
- if (error) {
490
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
491
- reject(new Error(out || error.message));
492
- return;
493
- }
494
- resolve({ stdout, stderr });
495
- }
496
- );
497
- });
456
+ function createInitialHtmlErrorMarkup(message) {
457
+ return `<main style="font-family:system-ui,sans-serif;padding:24px"><h1 style="margin:0 0 12px">500</h1><p style="margin:0">${message}</p></main>`;
498
458
  }
499
- async function renderInitialHtmlInWorker(options) {
500
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
501
- const script = `
459
+ var INITIAL_HTML_WORKER_SCRIPT = `
502
460
  const path = require("node:path");
503
461
  const Module = require("node:module");
504
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
505
- globalThis.__RSC_BASENAME = input.basename || "";
506
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
462
+
463
+ const pagesDir = process.env.WEBFRAMEZ_REACT_PAGES_DIR || "";
464
+ if (!pagesDir) {
465
+ throw new Error("Missing WEBFRAMEZ_REACT_PAGES_DIR");
466
+ }
467
+
468
+ const appRequire = Module.createRequire(path.join(pagesDir, "__webframez_react_worker__.js"));
507
469
  const originalResolveFilename = Module._resolveFilename;
508
470
  const forcedResolutions = new Map([
509
471
  ["react", appRequire.resolve("react")],
@@ -511,35 +473,181 @@ const forcedResolutions = new Map([
511
473
  ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
512
474
  ["react-dom/client", appRequire.resolve("react-dom/client")]
513
475
  ]);
476
+
514
477
  Module._resolveFilename = function(request, parent, isMain, options) {
515
478
  if (forcedResolutions.has(request)) {
516
479
  return forcedResolutions.get(request);
517
480
  }
518
481
  return originalResolveFilename.call(this, request, parent, isMain, options);
519
482
  };
483
+
520
484
  const { createFileRouter } = require("@webtypen/webframez-react/router");
521
485
  const reactDomPkg = require.resolve("react-dom/package.json", {
522
- paths: [process.cwd(), input.pagesDir]
486
+ paths: [process.cwd(), pagesDir]
523
487
  });
524
488
  const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
489
+ const router = createFileRouter({ pagesDir });
525
490
 
526
- (async () => {
527
- const router = createFileRouter({ pagesDir: input.pagesDir });
528
- const resolved = await router.resolve({
529
- pathname: input.pathname,
530
- searchParams: input.searchParams || {},
531
- cookies: input.cookies || {},
532
- });
533
- const html = reactDomServer.renderToString(resolved.model);
534
- process.stdout.write(JSON.stringify({ html }));
535
- })().catch((error) => {
536
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
537
- process.exit(1);
491
+ process.on("message", async (message) => {
492
+ if (!message || message.type !== "render") {
493
+ return;
494
+ }
495
+
496
+ const previousBasename = globalThis.__RSC_BASENAME;
497
+ globalThis.__RSC_BASENAME = message.payload.basename || "";
498
+
499
+ try {
500
+ const resolved = await router.resolve({
501
+ pathname: message.payload.pathname,
502
+ searchParams: message.payload.searchParams || {},
503
+ cookies: message.payload.cookies || {},
504
+ });
505
+ const html = reactDomServer.renderToString(resolved.model);
506
+ if (typeof process.send === "function") {
507
+ process.send({ id: message.id, ok: true, html });
508
+ }
509
+ } catch (error) {
510
+ const formatted = error && (error.stack || String(error))
511
+ ? (error.stack || String(error))
512
+ : "Unknown SSR worker error";
513
+ if (typeof process.send === "function") {
514
+ process.send({ id: message.id, ok: false, error: formatted });
515
+ }
516
+ } finally {
517
+ globalThis.__RSC_BASENAME = previousBasename;
518
+ }
538
519
  });
539
520
  `;
540
- const { stdout } = await runNodeCommand(["-e", script, payload]);
541
- const parsed = JSON.parse(stdout || "{}");
542
- return parsed.html || "";
521
+ function normalizeBasePath(basePath) {
522
+ if (!basePath || basePath === "/") {
523
+ return "";
524
+ }
525
+ const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
526
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
527
+ }
528
+ function stripBasePath(pathname, basePath) {
529
+ if (!basePath) {
530
+ return pathname;
531
+ }
532
+ if (pathname === basePath) {
533
+ return "/";
534
+ }
535
+ if (pathname.startsWith(`${basePath}/`)) {
536
+ return pathname.slice(basePath.length) || "/";
537
+ }
538
+ return pathname;
539
+ }
540
+ function sanitizeInitialHtmlWorkerNodeOptions(rawNodeOptions) {
541
+ if (!rawNodeOptions || rawNodeOptions.trim() === "") {
542
+ return "";
543
+ }
544
+ return rawNodeOptions.replace(/(^|\s)--conditions\s+react-server(?=\s|$)/g, " ").replace(/(^|\s)-r\s+(\S*webframez-react\/register)(?=\s|$)/g, " ").replace(/\s+/g, " ").trim();
545
+ }
546
+ function createInitialHtmlWorker(pagesDir) {
547
+ let child = null;
548
+ let nextRequestId = 1;
549
+ let stderrBuffer = "";
550
+ const pending = /* @__PURE__ */ new Map();
551
+ const rejectPending = (error) => {
552
+ for (const entry of pending.values()) {
553
+ clearTimeout(entry.timeout);
554
+ entry.reject(error);
555
+ }
556
+ pending.clear();
557
+ };
558
+ const stopWorker = () => {
559
+ if (!child) {
560
+ return;
561
+ }
562
+ child.removeAllListeners();
563
+ if (!child.killed) {
564
+ child.kill();
565
+ }
566
+ child = null;
567
+ };
568
+ const startWorker = () => {
569
+ if (child && child.connected && !child.killed) {
570
+ return child;
571
+ }
572
+ stderrBuffer = "";
573
+ child = spawn(process.execPath, ["-e", INITIAL_HTML_WORKER_SCRIPT], {
574
+ cwd: process.cwd(),
575
+ env: {
576
+ ...process.env,
577
+ NODE_OPTIONS: sanitizeInitialHtmlWorkerNodeOptions(process.env.NODE_OPTIONS),
578
+ WEBFRAMEZ_REACT_PAGES_DIR: pagesDir
579
+ },
580
+ stdio: ["ignore", "ignore", "pipe", "ipc"]
581
+ });
582
+ child.on("message", (message) => {
583
+ if (!message || typeof message.id !== "number") {
584
+ return;
585
+ }
586
+ const entry = pending.get(message.id);
587
+ if (!entry) {
588
+ return;
589
+ }
590
+ pending.delete(message.id);
591
+ clearTimeout(entry.timeout);
592
+ if (message.ok) {
593
+ entry.resolve(message.html);
594
+ return;
595
+ }
596
+ entry.reject(new Error(message.error));
597
+ });
598
+ child.stderr?.on("data", (chunk) => {
599
+ stderrBuffer = `${stderrBuffer}${chunk.toString("utf8")}`.slice(-8192);
600
+ });
601
+ child.on("exit", (code, signal) => {
602
+ const suffix = stderrBuffer.trim() !== "" ? `
603
+ ${stderrBuffer.trim()}` : "";
604
+ rejectPending(
605
+ new Error(
606
+ `[webframez-react] Initial HTML worker exited (${signal ?? code ?? "unknown"})${suffix}`
607
+ )
608
+ );
609
+ child = null;
610
+ });
611
+ child.on("error", (error) => {
612
+ rejectPending(error instanceof Error ? error : new Error(String(error)));
613
+ child = null;
614
+ });
615
+ return child;
616
+ };
617
+ return {
618
+ render(payload) {
619
+ const activeChild = startWorker();
620
+ const requestId = nextRequestId++;
621
+ return new Promise((resolve, reject) => {
622
+ const timeout = setTimeout(() => {
623
+ rejectPending(new Error("[webframez-react] Initial HTML worker timed out"));
624
+ stopWorker();
625
+ }, 1e4);
626
+ pending.set(requestId, { resolve, reject, timeout });
627
+ const request = {
628
+ id: requestId,
629
+ type: "render",
630
+ payload
631
+ };
632
+ activeChild.send(request, (error) => {
633
+ if (!error) {
634
+ return;
635
+ }
636
+ const entry = pending.get(requestId);
637
+ if (!entry) {
638
+ return;
639
+ }
640
+ pending.delete(requestId);
641
+ clearTimeout(entry.timeout);
642
+ entry.reject(error instanceof Error ? error : new Error(String(error)));
643
+ });
644
+ });
645
+ },
646
+ dispose() {
647
+ rejectPending(new Error("[webframez-react] Initial HTML worker disposed"));
648
+ stopWorker();
649
+ }
650
+ };
543
651
  }
544
652
  function withRequestBasename(basename, fn) {
545
653
  const target = globalThis;
@@ -599,6 +707,13 @@ function createNodeRequestHandler(options) {
599
707
  const liveReloadClients = /* @__PURE__ */ new Set();
600
708
  const router = createFileRouter({ pagesDir });
601
709
  const moduleMap = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
710
+ const initialHtmlWorker = createInitialHtmlWorker(pagesDir);
711
+ const disposeInitialHtmlWorker = () => {
712
+ initialHtmlWorker.dispose();
713
+ };
714
+ process.once("exit", disposeInitialHtmlWorker);
715
+ process.once("SIGINT", disposeInitialHtmlWorker);
716
+ process.once("SIGTERM", disposeInitialHtmlWorker);
602
717
  return async function handleRequest(req, res) {
603
718
  if (!req.url) {
604
719
  res.statusCode = 400;
@@ -698,8 +813,7 @@ function createNodeRequestHandler(options) {
698
813
  );
699
814
  let rootHtml = "";
700
815
  try {
701
- rootHtml = await renderInitialHtmlInWorker({
702
- pagesDir,
816
+ rootHtml = await initialHtmlWorker.render({
703
817
  pathname: stripBasePath(url.pathname, basePath),
704
818
  searchParams: parseSearchParams(url.searchParams),
705
819
  cookies: requestCookies,
@@ -707,6 +821,32 @@ function createNodeRequestHandler(options) {
707
821
  });
708
822
  } catch (error) {
709
823
  console.error("[webframez-react] Failed to render initial HTML", error);
824
+ try {
825
+ initialHtmlWorker.dispose();
826
+ rootHtml = await initialHtmlWorker.render({
827
+ pathname: stripBasePath(url.pathname, basePath),
828
+ searchParams: parseSearchParams(url.searchParams),
829
+ cookies: requestCookies,
830
+ basename: basePath
831
+ });
832
+ } catch (retryError) {
833
+ console.error("[webframez-react] Retry for initial HTML failed", retryError);
834
+ res.statusCode = 500;
835
+ res.setHeader("Content-Type", "text/html");
836
+ res.end(
837
+ createHTMLShell({
838
+ title: "500 - Initial HTML render failed",
839
+ headTags: "",
840
+ clientScriptUrl,
841
+ rscEndpoint: rscPath,
842
+ rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
843
+ basename: basePath,
844
+ liveReloadPath: liveReloadPath || void 0,
845
+ liveReloadServerId: liveReloadPath ? devServerId : void 0
846
+ })
847
+ );
848
+ return;
849
+ }
710
850
  }
711
851
  res.statusCode = resolved.statusCode;
712
852
  res.setHeader("Content-Type", "text/html");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-react",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "TypeScript React RSC addition for @webtypen/webframez-core",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "author": {