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