@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.
package/dist/index.cjs CHANGED
@@ -515,57 +515,19 @@ function parseSearchParams(query) {
515
515
  var import_node_fs2 = __toESM(require("node:fs"), 1);
516
516
  var import_node_path2 = __toESM(require("node:path"), 1);
517
517
  var import_node_child_process = require("node:child_process");
518
- function normalizeBasePath(basePath) {
519
- if (!basePath || basePath === "/") {
520
- return "";
521
- }
522
- const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
523
- return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
524
- }
525
- function stripBasePath(pathname, basePath) {
526
- if (!basePath) {
527
- return pathname;
528
- }
529
- if (pathname === basePath) {
530
- return "/";
531
- }
532
- if (pathname.startsWith(`${basePath}/`)) {
533
- return pathname.slice(basePath.length) || "/";
534
- }
535
- return pathname;
518
+ function createInitialHtmlErrorMarkup(message) {
519
+ 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>`;
536
520
  }
537
- function runNodeCommand(args) {
538
- return new Promise((resolve, reject) => {
539
- const execArgs = [
540
- "--conditions",
541
- "react-server",
542
- "-r",
543
- "@webtypen/webframez-react/register",
544
- ...args
545
- ];
546
- (0, import_node_child_process.execFile)(
547
- process.execPath,
548
- execArgs,
549
- { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 },
550
- (error, stdout, stderr) => {
551
- if (error) {
552
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
553
- reject(new Error(out || error.message));
554
- return;
555
- }
556
- resolve({ stdout, stderr });
557
- }
558
- );
559
- });
560
- }
561
- async function renderInitialHtmlInWorker(options) {
562
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
563
- const script = `
521
+ var INITIAL_HTML_WORKER_SCRIPT = `
564
522
  const path = require("node:path");
565
523
  const Module = require("node:module");
566
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
567
- globalThis.__RSC_BASENAME = input.basename || "";
568
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
524
+
525
+ const pagesDir = process.env.WEBFRAMEZ_REACT_PAGES_DIR || "";
526
+ if (!pagesDir) {
527
+ throw new Error("Missing WEBFRAMEZ_REACT_PAGES_DIR");
528
+ }
529
+
530
+ const appRequire = Module.createRequire(path.join(pagesDir, "__webframez_react_worker__.js"));
569
531
  const originalResolveFilename = Module._resolveFilename;
570
532
  const forcedResolutions = new Map([
571
533
  ["react", appRequire.resolve("react")],
@@ -573,35 +535,181 @@ const forcedResolutions = new Map([
573
535
  ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
574
536
  ["react-dom/client", appRequire.resolve("react-dom/client")]
575
537
  ]);
538
+
576
539
  Module._resolveFilename = function(request, parent, isMain, options) {
577
540
  if (forcedResolutions.has(request)) {
578
541
  return forcedResolutions.get(request);
579
542
  }
580
543
  return originalResolveFilename.call(this, request, parent, isMain, options);
581
544
  };
545
+
582
546
  const { createFileRouter } = require("@webtypen/webframez-react/router");
583
547
  const reactDomPkg = require.resolve("react-dom/package.json", {
584
- paths: [process.cwd(), input.pagesDir]
548
+ paths: [process.cwd(), pagesDir]
585
549
  });
586
550
  const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
551
+ const router = createFileRouter({ pagesDir });
587
552
 
588
- (async () => {
589
- const router = createFileRouter({ pagesDir: input.pagesDir });
590
- const resolved = await router.resolve({
591
- pathname: input.pathname,
592
- searchParams: input.searchParams || {},
593
- cookies: input.cookies || {},
594
- });
595
- const html = reactDomServer.renderToString(resolved.model);
596
- process.stdout.write(JSON.stringify({ html }));
597
- })().catch((error) => {
598
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
599
- process.exit(1);
553
+ process.on("message", async (message) => {
554
+ if (!message || message.type !== "render") {
555
+ return;
556
+ }
557
+
558
+ const previousBasename = globalThis.__RSC_BASENAME;
559
+ globalThis.__RSC_BASENAME = message.payload.basename || "";
560
+
561
+ try {
562
+ const resolved = await router.resolve({
563
+ pathname: message.payload.pathname,
564
+ searchParams: message.payload.searchParams || {},
565
+ cookies: message.payload.cookies || {},
566
+ });
567
+ const html = reactDomServer.renderToString(resolved.model);
568
+ if (typeof process.send === "function") {
569
+ process.send({ id: message.id, ok: true, html });
570
+ }
571
+ } catch (error) {
572
+ const formatted = error && (error.stack || String(error))
573
+ ? (error.stack || String(error))
574
+ : "Unknown SSR worker error";
575
+ if (typeof process.send === "function") {
576
+ process.send({ id: message.id, ok: false, error: formatted });
577
+ }
578
+ } finally {
579
+ globalThis.__RSC_BASENAME = previousBasename;
580
+ }
600
581
  });
601
582
  `;
602
- const { stdout } = await runNodeCommand(["-e", script, payload]);
603
- const parsed = JSON.parse(stdout || "{}");
604
- return parsed.html || "";
583
+ function normalizeBasePath(basePath) {
584
+ if (!basePath || basePath === "/") {
585
+ return "";
586
+ }
587
+ const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
588
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
589
+ }
590
+ function stripBasePath(pathname, basePath) {
591
+ if (!basePath) {
592
+ return pathname;
593
+ }
594
+ if (pathname === basePath) {
595
+ return "/";
596
+ }
597
+ if (pathname.startsWith(`${basePath}/`)) {
598
+ return pathname.slice(basePath.length) || "/";
599
+ }
600
+ return pathname;
601
+ }
602
+ function sanitizeInitialHtmlWorkerNodeOptions(rawNodeOptions) {
603
+ if (!rawNodeOptions || rawNodeOptions.trim() === "") {
604
+ return "";
605
+ }
606
+ return rawNodeOptions.replace(/(^|\s)--conditions\s+react-server(?=\s|$)/g, " ").replace(/(^|\s)-r\s+(\S*webframez-react\/register)(?=\s|$)/g, " ").replace(/\s+/g, " ").trim();
607
+ }
608
+ function createInitialHtmlWorker(pagesDir) {
609
+ let child = null;
610
+ let nextRequestId = 1;
611
+ let stderrBuffer = "";
612
+ const pending = /* @__PURE__ */ new Map();
613
+ const rejectPending = (error) => {
614
+ for (const entry of pending.values()) {
615
+ clearTimeout(entry.timeout);
616
+ entry.reject(error);
617
+ }
618
+ pending.clear();
619
+ };
620
+ const stopWorker = () => {
621
+ if (!child) {
622
+ return;
623
+ }
624
+ child.removeAllListeners();
625
+ if (!child.killed) {
626
+ child.kill();
627
+ }
628
+ child = null;
629
+ };
630
+ const startWorker = () => {
631
+ if (child && child.connected && !child.killed) {
632
+ return child;
633
+ }
634
+ stderrBuffer = "";
635
+ child = (0, import_node_child_process.spawn)(process.execPath, ["-e", INITIAL_HTML_WORKER_SCRIPT], {
636
+ cwd: process.cwd(),
637
+ env: {
638
+ ...process.env,
639
+ NODE_OPTIONS: sanitizeInitialHtmlWorkerNodeOptions(process.env.NODE_OPTIONS),
640
+ WEBFRAMEZ_REACT_PAGES_DIR: pagesDir
641
+ },
642
+ stdio: ["ignore", "ignore", "pipe", "ipc"]
643
+ });
644
+ child.on("message", (message) => {
645
+ if (!message || typeof message.id !== "number") {
646
+ return;
647
+ }
648
+ const entry = pending.get(message.id);
649
+ if (!entry) {
650
+ return;
651
+ }
652
+ pending.delete(message.id);
653
+ clearTimeout(entry.timeout);
654
+ if (message.ok) {
655
+ entry.resolve(message.html);
656
+ return;
657
+ }
658
+ entry.reject(new Error(message.error));
659
+ });
660
+ child.stderr?.on("data", (chunk) => {
661
+ stderrBuffer = `${stderrBuffer}${chunk.toString("utf8")}`.slice(-8192);
662
+ });
663
+ child.on("exit", (code, signal) => {
664
+ const suffix = stderrBuffer.trim() !== "" ? `
665
+ ${stderrBuffer.trim()}` : "";
666
+ rejectPending(
667
+ new Error(
668
+ `[webframez-react] Initial HTML worker exited (${signal ?? code ?? "unknown"})${suffix}`
669
+ )
670
+ );
671
+ child = null;
672
+ });
673
+ child.on("error", (error) => {
674
+ rejectPending(error instanceof Error ? error : new Error(String(error)));
675
+ child = null;
676
+ });
677
+ return child;
678
+ };
679
+ return {
680
+ render(payload) {
681
+ const activeChild = startWorker();
682
+ const requestId = nextRequestId++;
683
+ return new Promise((resolve, reject) => {
684
+ const timeout = setTimeout(() => {
685
+ rejectPending(new Error("[webframez-react] Initial HTML worker timed out"));
686
+ stopWorker();
687
+ }, 1e4);
688
+ pending.set(requestId, { resolve, reject, timeout });
689
+ const request = {
690
+ id: requestId,
691
+ type: "render",
692
+ payload
693
+ };
694
+ activeChild.send(request, (error) => {
695
+ if (!error) {
696
+ return;
697
+ }
698
+ const entry = pending.get(requestId);
699
+ if (!entry) {
700
+ return;
701
+ }
702
+ pending.delete(requestId);
703
+ clearTimeout(entry.timeout);
704
+ entry.reject(error instanceof Error ? error : new Error(String(error)));
705
+ });
706
+ });
707
+ },
708
+ dispose() {
709
+ rejectPending(new Error("[webframez-react] Initial HTML worker disposed"));
710
+ stopWorker();
711
+ }
712
+ };
605
713
  }
606
714
  function withRequestBasename(basename, fn) {
607
715
  const target = globalThis;
@@ -661,6 +769,13 @@ function createNodeRequestHandler(options) {
661
769
  const liveReloadClients = /* @__PURE__ */ new Set();
662
770
  const router = createFileRouter({ pagesDir });
663
771
  const moduleMap = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf-8"));
772
+ const initialHtmlWorker = createInitialHtmlWorker(pagesDir);
773
+ const disposeInitialHtmlWorker = () => {
774
+ initialHtmlWorker.dispose();
775
+ };
776
+ process.once("exit", disposeInitialHtmlWorker);
777
+ process.once("SIGINT", disposeInitialHtmlWorker);
778
+ process.once("SIGTERM", disposeInitialHtmlWorker);
664
779
  return async function handleRequest(req, res) {
665
780
  if (!req.url) {
666
781
  res.statusCode = 400;
@@ -760,8 +875,7 @@ function createNodeRequestHandler(options) {
760
875
  );
761
876
  let rootHtml = "";
762
877
  try {
763
- rootHtml = await renderInitialHtmlInWorker({
764
- pagesDir,
878
+ rootHtml = await initialHtmlWorker.render({
765
879
  pathname: stripBasePath(url.pathname, basePath),
766
880
  searchParams: parseSearchParams(url.searchParams),
767
881
  cookies: requestCookies,
@@ -769,6 +883,32 @@ function createNodeRequestHandler(options) {
769
883
  });
770
884
  } catch (error) {
771
885
  console.error("[webframez-react] Failed to render initial HTML", error);
886
+ try {
887
+ initialHtmlWorker.dispose();
888
+ rootHtml = await initialHtmlWorker.render({
889
+ pathname: stripBasePath(url.pathname, basePath),
890
+ searchParams: parseSearchParams(url.searchParams),
891
+ cookies: requestCookies,
892
+ basename: basePath
893
+ });
894
+ } catch (retryError) {
895
+ console.error("[webframez-react] Retry for initial HTML failed", retryError);
896
+ res.statusCode = 500;
897
+ res.setHeader("Content-Type", "text/html");
898
+ res.end(
899
+ createHTMLShell({
900
+ title: "500 - Initial HTML render failed",
901
+ headTags: "",
902
+ clientScriptUrl,
903
+ rscEndpoint: rscPath,
904
+ rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
905
+ basename: basePath,
906
+ liveReloadPath: liveReloadPath || void 0,
907
+ liveReloadServerId: liveReloadPath ? devServerId : void 0
908
+ })
909
+ );
910
+ return;
911
+ }
772
912
  }
773
913
  res.statusCode = resolved.statusCode;
774
914
  res.setHeader("Content-Type", "text/html");
package/dist/index.js CHANGED
@@ -478,58 +478,20 @@ function parseSearchParams(query) {
478
478
  // src/http.ts
479
479
  import fs2 from "node:fs";
480
480
  import path2 from "node:path";
481
- import { execFile } from "node:child_process";
482
- function normalizeBasePath(basePath) {
483
- if (!basePath || basePath === "/") {
484
- return "";
485
- }
486
- const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
487
- return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
488
- }
489
- function stripBasePath(pathname, basePath) {
490
- if (!basePath) {
491
- return pathname;
492
- }
493
- if (pathname === basePath) {
494
- return "/";
495
- }
496
- if (pathname.startsWith(`${basePath}/`)) {
497
- return pathname.slice(basePath.length) || "/";
498
- }
499
- return pathname;
481
+ import { spawn } from "node:child_process";
482
+ function createInitialHtmlErrorMarkup(message) {
483
+ 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>`;
500
484
  }
501
- function runNodeCommand(args) {
502
- return new Promise((resolve, reject) => {
503
- const execArgs = [
504
- "--conditions",
505
- "react-server",
506
- "-r",
507
- "@webtypen/webframez-react/register",
508
- ...args
509
- ];
510
- execFile(
511
- process.execPath,
512
- execArgs,
513
- { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 },
514
- (error, stdout, stderr) => {
515
- if (error) {
516
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
517
- reject(new Error(out || error.message));
518
- return;
519
- }
520
- resolve({ stdout, stderr });
521
- }
522
- );
523
- });
524
- }
525
- async function renderInitialHtmlInWorker(options) {
526
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
527
- const script = `
485
+ var INITIAL_HTML_WORKER_SCRIPT = `
528
486
  const path = require("node:path");
529
487
  const Module = require("node:module");
530
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
531
- globalThis.__RSC_BASENAME = input.basename || "";
532
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
488
+
489
+ const pagesDir = process.env.WEBFRAMEZ_REACT_PAGES_DIR || "";
490
+ if (!pagesDir) {
491
+ throw new Error("Missing WEBFRAMEZ_REACT_PAGES_DIR");
492
+ }
493
+
494
+ const appRequire = Module.createRequire(path.join(pagesDir, "__webframez_react_worker__.js"));
533
495
  const originalResolveFilename = Module._resolveFilename;
534
496
  const forcedResolutions = new Map([
535
497
  ["react", appRequire.resolve("react")],
@@ -537,35 +499,181 @@ const forcedResolutions = new Map([
537
499
  ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
538
500
  ["react-dom/client", appRequire.resolve("react-dom/client")]
539
501
  ]);
502
+
540
503
  Module._resolveFilename = function(request, parent, isMain, options) {
541
504
  if (forcedResolutions.has(request)) {
542
505
  return forcedResolutions.get(request);
543
506
  }
544
507
  return originalResolveFilename.call(this, request, parent, isMain, options);
545
508
  };
509
+
546
510
  const { createFileRouter } = require("@webtypen/webframez-react/router");
547
511
  const reactDomPkg = require.resolve("react-dom/package.json", {
548
- paths: [process.cwd(), input.pagesDir]
512
+ paths: [process.cwd(), pagesDir]
549
513
  });
550
514
  const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
515
+ const router = createFileRouter({ pagesDir });
551
516
 
552
- (async () => {
553
- const router = createFileRouter({ pagesDir: input.pagesDir });
554
- const resolved = await router.resolve({
555
- pathname: input.pathname,
556
- searchParams: input.searchParams || {},
557
- cookies: input.cookies || {},
558
- });
559
- const html = reactDomServer.renderToString(resolved.model);
560
- process.stdout.write(JSON.stringify({ html }));
561
- })().catch((error) => {
562
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
563
- process.exit(1);
517
+ process.on("message", async (message) => {
518
+ if (!message || message.type !== "render") {
519
+ return;
520
+ }
521
+
522
+ const previousBasename = globalThis.__RSC_BASENAME;
523
+ globalThis.__RSC_BASENAME = message.payload.basename || "";
524
+
525
+ try {
526
+ const resolved = await router.resolve({
527
+ pathname: message.payload.pathname,
528
+ searchParams: message.payload.searchParams || {},
529
+ cookies: message.payload.cookies || {},
530
+ });
531
+ const html = reactDomServer.renderToString(resolved.model);
532
+ if (typeof process.send === "function") {
533
+ process.send({ id: message.id, ok: true, html });
534
+ }
535
+ } catch (error) {
536
+ const formatted = error && (error.stack || String(error))
537
+ ? (error.stack || String(error))
538
+ : "Unknown SSR worker error";
539
+ if (typeof process.send === "function") {
540
+ process.send({ id: message.id, ok: false, error: formatted });
541
+ }
542
+ } finally {
543
+ globalThis.__RSC_BASENAME = previousBasename;
544
+ }
564
545
  });
565
546
  `;
566
- const { stdout } = await runNodeCommand(["-e", script, payload]);
567
- const parsed = JSON.parse(stdout || "{}");
568
- return parsed.html || "";
547
+ function normalizeBasePath(basePath) {
548
+ if (!basePath || basePath === "/") {
549
+ return "";
550
+ }
551
+ const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
552
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
553
+ }
554
+ function stripBasePath(pathname, basePath) {
555
+ if (!basePath) {
556
+ return pathname;
557
+ }
558
+ if (pathname === basePath) {
559
+ return "/";
560
+ }
561
+ if (pathname.startsWith(`${basePath}/`)) {
562
+ return pathname.slice(basePath.length) || "/";
563
+ }
564
+ return pathname;
565
+ }
566
+ function sanitizeInitialHtmlWorkerNodeOptions(rawNodeOptions) {
567
+ if (!rawNodeOptions || rawNodeOptions.trim() === "") {
568
+ return "";
569
+ }
570
+ return rawNodeOptions.replace(/(^|\s)--conditions\s+react-server(?=\s|$)/g, " ").replace(/(^|\s)-r\s+(\S*webframez-react\/register)(?=\s|$)/g, " ").replace(/\s+/g, " ").trim();
571
+ }
572
+ function createInitialHtmlWorker(pagesDir) {
573
+ let child = null;
574
+ let nextRequestId = 1;
575
+ let stderrBuffer = "";
576
+ const pending = /* @__PURE__ */ new Map();
577
+ const rejectPending = (error) => {
578
+ for (const entry of pending.values()) {
579
+ clearTimeout(entry.timeout);
580
+ entry.reject(error);
581
+ }
582
+ pending.clear();
583
+ };
584
+ const stopWorker = () => {
585
+ if (!child) {
586
+ return;
587
+ }
588
+ child.removeAllListeners();
589
+ if (!child.killed) {
590
+ child.kill();
591
+ }
592
+ child = null;
593
+ };
594
+ const startWorker = () => {
595
+ if (child && child.connected && !child.killed) {
596
+ return child;
597
+ }
598
+ stderrBuffer = "";
599
+ child = spawn(process.execPath, ["-e", INITIAL_HTML_WORKER_SCRIPT], {
600
+ cwd: process.cwd(),
601
+ env: {
602
+ ...process.env,
603
+ NODE_OPTIONS: sanitizeInitialHtmlWorkerNodeOptions(process.env.NODE_OPTIONS),
604
+ WEBFRAMEZ_REACT_PAGES_DIR: pagesDir
605
+ },
606
+ stdio: ["ignore", "ignore", "pipe", "ipc"]
607
+ });
608
+ child.on("message", (message) => {
609
+ if (!message || typeof message.id !== "number") {
610
+ return;
611
+ }
612
+ const entry = pending.get(message.id);
613
+ if (!entry) {
614
+ return;
615
+ }
616
+ pending.delete(message.id);
617
+ clearTimeout(entry.timeout);
618
+ if (message.ok) {
619
+ entry.resolve(message.html);
620
+ return;
621
+ }
622
+ entry.reject(new Error(message.error));
623
+ });
624
+ child.stderr?.on("data", (chunk) => {
625
+ stderrBuffer = `${stderrBuffer}${chunk.toString("utf8")}`.slice(-8192);
626
+ });
627
+ child.on("exit", (code, signal) => {
628
+ const suffix = stderrBuffer.trim() !== "" ? `
629
+ ${stderrBuffer.trim()}` : "";
630
+ rejectPending(
631
+ new Error(
632
+ `[webframez-react] Initial HTML worker exited (${signal ?? code ?? "unknown"})${suffix}`
633
+ )
634
+ );
635
+ child = null;
636
+ });
637
+ child.on("error", (error) => {
638
+ rejectPending(error instanceof Error ? error : new Error(String(error)));
639
+ child = null;
640
+ });
641
+ return child;
642
+ };
643
+ return {
644
+ render(payload) {
645
+ const activeChild = startWorker();
646
+ const requestId = nextRequestId++;
647
+ return new Promise((resolve, reject) => {
648
+ const timeout = setTimeout(() => {
649
+ rejectPending(new Error("[webframez-react] Initial HTML worker timed out"));
650
+ stopWorker();
651
+ }, 1e4);
652
+ pending.set(requestId, { resolve, reject, timeout });
653
+ const request = {
654
+ id: requestId,
655
+ type: "render",
656
+ payload
657
+ };
658
+ activeChild.send(request, (error) => {
659
+ if (!error) {
660
+ return;
661
+ }
662
+ const entry = pending.get(requestId);
663
+ if (!entry) {
664
+ return;
665
+ }
666
+ pending.delete(requestId);
667
+ clearTimeout(entry.timeout);
668
+ entry.reject(error instanceof Error ? error : new Error(String(error)));
669
+ });
670
+ });
671
+ },
672
+ dispose() {
673
+ rejectPending(new Error("[webframez-react] Initial HTML worker disposed"));
674
+ stopWorker();
675
+ }
676
+ };
569
677
  }
570
678
  function withRequestBasename(basename, fn) {
571
679
  const target = globalThis;
@@ -625,6 +733,13 @@ function createNodeRequestHandler(options) {
625
733
  const liveReloadClients = /* @__PURE__ */ new Set();
626
734
  const router = createFileRouter({ pagesDir });
627
735
  const moduleMap = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
736
+ const initialHtmlWorker = createInitialHtmlWorker(pagesDir);
737
+ const disposeInitialHtmlWorker = () => {
738
+ initialHtmlWorker.dispose();
739
+ };
740
+ process.once("exit", disposeInitialHtmlWorker);
741
+ process.once("SIGINT", disposeInitialHtmlWorker);
742
+ process.once("SIGTERM", disposeInitialHtmlWorker);
628
743
  return async function handleRequest(req, res) {
629
744
  if (!req.url) {
630
745
  res.statusCode = 400;
@@ -724,8 +839,7 @@ function createNodeRequestHandler(options) {
724
839
  );
725
840
  let rootHtml = "";
726
841
  try {
727
- rootHtml = await renderInitialHtmlInWorker({
728
- pagesDir,
842
+ rootHtml = await initialHtmlWorker.render({
729
843
  pathname: stripBasePath(url.pathname, basePath),
730
844
  searchParams: parseSearchParams(url.searchParams),
731
845
  cookies: requestCookies,
@@ -733,6 +847,32 @@ function createNodeRequestHandler(options) {
733
847
  });
734
848
  } catch (error) {
735
849
  console.error("[webframez-react] Failed to render initial HTML", error);
850
+ try {
851
+ initialHtmlWorker.dispose();
852
+ rootHtml = await initialHtmlWorker.render({
853
+ pathname: stripBasePath(url.pathname, basePath),
854
+ searchParams: parseSearchParams(url.searchParams),
855
+ cookies: requestCookies,
856
+ basename: basePath
857
+ });
858
+ } catch (retryError) {
859
+ console.error("[webframez-react] Retry for initial HTML failed", retryError);
860
+ res.statusCode = 500;
861
+ res.setHeader("Content-Type", "text/html");
862
+ res.end(
863
+ createHTMLShell({
864
+ title: "500 - Initial HTML render failed",
865
+ headTags: "",
866
+ clientScriptUrl,
867
+ rscEndpoint: rscPath,
868
+ rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
869
+ basename: basePath,
870
+ liveReloadPath: liveReloadPath || void 0,
871
+ liveReloadServerId: liveReloadPath ? devServerId : void 0
872
+ })
873
+ );
874
+ return;
875
+ }
736
876
  }
737
877
  res.statusCode = resolved.statusCode;
738
878
  res.setHeader("Content-Type", "text/html");