@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/webframez-core.cjs
CHANGED
|
@@ -481,57 +481,19 @@ function parseSearchParams(query) {
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
// src/http.ts
|
|
484
|
-
function
|
|
485
|
-
|
|
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
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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(),
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
|
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");
|
package/dist/webframez-core.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");
|