@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/http.cjs +206 -66
- package/dist/http.js +207 -67
- package/dist/index.cjs +206 -66
- package/dist/index.js +207 -67
- package/dist/webframez-core.cjs +206 -66
- package/dist/webframez-core.js +207 -67
- package/package.json +1 -1
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
|
|
519
|
-
|
|
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
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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(),
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
|
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 {
|
|
482
|
-
function
|
|
483
|
-
|
|
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
|
-
|
|
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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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(),
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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
|
|
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");
|