@webtypen/webframez-react 0.0.3 → 0.0.5

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,6 +515,68 @@ 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
+ var INITIAL_HTML_WORKER_SCRIPT = `
519
+ const path = require("node:path");
520
+ const Module = require("node:module");
521
+
522
+ const pagesDir = process.env.WEBFRAMEZ_REACT_PAGES_DIR || "";
523
+ if (!pagesDir) {
524
+ throw new Error("Missing WEBFRAMEZ_REACT_PAGES_DIR");
525
+ }
526
+
527
+ const appRequire = Module.createRequire(path.join(pagesDir, "__webframez_react_worker__.js"));
528
+ const originalResolveFilename = Module._resolveFilename;
529
+ const forcedResolutions = new Map([
530
+ ["react", appRequire.resolve("react")],
531
+ ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
532
+ ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
533
+ ["react-dom/client", appRequire.resolve("react-dom/client")]
534
+ ]);
535
+
536
+ Module._resolveFilename = function(request, parent, isMain, options) {
537
+ if (forcedResolutions.has(request)) {
538
+ return forcedResolutions.get(request);
539
+ }
540
+ return originalResolveFilename.call(this, request, parent, isMain, options);
541
+ };
542
+
543
+ const { createFileRouter } = require("@webtypen/webframez-react/router");
544
+ const reactDomPkg = require.resolve("react-dom/package.json", {
545
+ paths: [process.cwd(), pagesDir]
546
+ });
547
+ const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
548
+ const router = createFileRouter({ pagesDir });
549
+
550
+ process.on("message", async (message) => {
551
+ if (!message || message.type !== "render") {
552
+ return;
553
+ }
554
+
555
+ const previousBasename = globalThis.__RSC_BASENAME;
556
+ globalThis.__RSC_BASENAME = message.payload.basename || "";
557
+
558
+ try {
559
+ const resolved = await router.resolve({
560
+ pathname: message.payload.pathname,
561
+ searchParams: message.payload.searchParams || {},
562
+ cookies: message.payload.cookies || {},
563
+ });
564
+ const html = reactDomServer.renderToString(resolved.model);
565
+ if (typeof process.send === "function") {
566
+ process.send({ id: message.id, ok: true, html });
567
+ }
568
+ } catch (error) {
569
+ const formatted = error && (error.stack || String(error))
570
+ ? (error.stack || String(error))
571
+ : "Unknown SSR worker error";
572
+ if (typeof process.send === "function") {
573
+ process.send({ id: message.id, ok: false, error: formatted });
574
+ }
575
+ } finally {
576
+ globalThis.__RSC_BASENAME = previousBasename;
577
+ }
578
+ });
579
+ `;
518
580
  function normalizeBasePath(basePath) {
519
581
  if (!basePath || basePath === "/") {
520
582
  return "";
@@ -534,62 +596,110 @@ function stripBasePath(pathname, basePath) {
534
596
  }
535
597
  return pathname;
536
598
  }
537
- function runNodeCommand(args) {
538
- return new Promise((resolve, reject) => {
539
- (0, import_node_child_process.execFile)(process.execPath, args, { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 }, (error, stdout, stderr) => {
540
- if (error) {
541
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
542
- reject(new Error(out || error.message));
599
+ function createInitialHtmlWorker(pagesDir) {
600
+ let child = null;
601
+ let nextRequestId = 1;
602
+ let stderrBuffer = "";
603
+ const pending = /* @__PURE__ */ new Map();
604
+ const rejectPending = (error) => {
605
+ for (const entry of pending.values()) {
606
+ clearTimeout(entry.timeout);
607
+ entry.reject(error);
608
+ }
609
+ pending.clear();
610
+ };
611
+ const stopWorker = () => {
612
+ if (!child) {
613
+ return;
614
+ }
615
+ child.removeAllListeners();
616
+ if (!child.killed) {
617
+ child.kill();
618
+ }
619
+ child = null;
620
+ };
621
+ const startWorker = () => {
622
+ if (child && child.connected && !child.killed) {
623
+ return child;
624
+ }
625
+ stderrBuffer = "";
626
+ child = (0, import_node_child_process.spawn)(process.execPath, ["-e", INITIAL_HTML_WORKER_SCRIPT], {
627
+ cwd: process.cwd(),
628
+ env: {
629
+ ...process.env,
630
+ WEBFRAMEZ_REACT_PAGES_DIR: pagesDir
631
+ },
632
+ stdio: ["ignore", "ignore", "pipe", "ipc"]
633
+ });
634
+ child.on("message", (message) => {
635
+ if (!message || typeof message.id !== "number") {
636
+ return;
637
+ }
638
+ const entry = pending.get(message.id);
639
+ if (!entry) {
543
640
  return;
544
641
  }
545
- resolve({ stdout, stderr });
642
+ pending.delete(message.id);
643
+ clearTimeout(entry.timeout);
644
+ if (message.ok) {
645
+ entry.resolve(message.html);
646
+ return;
647
+ }
648
+ entry.reject(new Error(message.error));
546
649
  });
547
- });
548
- }
549
- async function renderInitialHtmlInWorker(options) {
550
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
551
- const script = `
552
- const path = require("node:path");
553
- const Module = require("node:module");
554
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
555
- globalThis.__RSC_BASENAME = input.basename || "";
556
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
557
- const originalResolveFilename = Module._resolveFilename;
558
- const forcedResolutions = new Map([
559
- ["react", appRequire.resolve("react")],
560
- ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
561
- ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
562
- ["react-dom/client", appRequire.resolve("react-dom/client")]
563
- ]);
564
- Module._resolveFilename = function(request, parent, isMain, options) {
565
- if (forcedResolutions.has(request)) {
566
- return forcedResolutions.get(request);
567
- }
568
- return originalResolveFilename.call(this, request, parent, isMain, options);
569
- };
570
- const { createFileRouter } = require("webframez-react/router");
571
- const reactDomPkg = require.resolve("react-dom/package.json", {
572
- paths: [process.cwd(), input.pagesDir]
573
- });
574
- const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
575
-
576
- (async () => {
577
- const router = createFileRouter({ pagesDir: input.pagesDir });
578
- const resolved = await router.resolve({
579
- pathname: input.pathname,
580
- searchParams: input.searchParams || {},
581
- cookies: input.cookies || {},
582
- });
583
- const html = reactDomServer.renderToString(resolved.model);
584
- process.stdout.write(JSON.stringify({ html }));
585
- })().catch((error) => {
586
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
587
- process.exit(1);
588
- });
589
- `;
590
- const { stdout } = await runNodeCommand(["-e", script, payload]);
591
- const parsed = JSON.parse(stdout || "{}");
592
- return parsed.html || "";
650
+ child.stderr?.on("data", (chunk) => {
651
+ stderrBuffer = `${stderrBuffer}${chunk.toString("utf8")}`.slice(-8192);
652
+ });
653
+ child.on("exit", (code, signal) => {
654
+ const suffix = stderrBuffer.trim() !== "" ? `
655
+ ${stderrBuffer.trim()}` : "";
656
+ rejectPending(
657
+ new Error(
658
+ `[webframez-react] Initial HTML worker exited (${signal ?? code ?? "unknown"})${suffix}`
659
+ )
660
+ );
661
+ child = null;
662
+ });
663
+ child.on("error", (error) => {
664
+ rejectPending(error instanceof Error ? error : new Error(String(error)));
665
+ child = null;
666
+ });
667
+ return child;
668
+ };
669
+ return {
670
+ render(payload) {
671
+ const activeChild = startWorker();
672
+ const requestId = nextRequestId++;
673
+ return new Promise((resolve, reject) => {
674
+ const timeout = setTimeout(() => {
675
+ rejectPending(new Error("[webframez-react] Initial HTML worker timed out"));
676
+ stopWorker();
677
+ }, 1e4);
678
+ pending.set(requestId, { resolve, reject, timeout });
679
+ const request = {
680
+ id: requestId,
681
+ type: "render",
682
+ payload
683
+ };
684
+ activeChild.send(request, (error) => {
685
+ if (!error) {
686
+ return;
687
+ }
688
+ const entry = pending.get(requestId);
689
+ if (!entry) {
690
+ return;
691
+ }
692
+ pending.delete(requestId);
693
+ clearTimeout(entry.timeout);
694
+ entry.reject(error instanceof Error ? error : new Error(String(error)));
695
+ });
696
+ });
697
+ },
698
+ dispose() {
699
+ rejectPending(new Error("[webframez-react] Initial HTML worker disposed"));
700
+ stopWorker();
701
+ }
702
+ };
593
703
  }
594
704
  function withRequestBasename(basename, fn) {
595
705
  const target = globalThis;
@@ -649,6 +759,13 @@ function createNodeRequestHandler(options) {
649
759
  const liveReloadClients = /* @__PURE__ */ new Set();
650
760
  const router = createFileRouter({ pagesDir });
651
761
  const moduleMap = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf-8"));
762
+ const initialHtmlWorker = createInitialHtmlWorker(pagesDir);
763
+ const disposeInitialHtmlWorker = () => {
764
+ initialHtmlWorker.dispose();
765
+ };
766
+ process.once("exit", disposeInitialHtmlWorker);
767
+ process.once("SIGINT", disposeInitialHtmlWorker);
768
+ process.once("SIGTERM", disposeInitialHtmlWorker);
652
769
  return async function handleRequest(req, res) {
653
770
  if (!req.url) {
654
771
  res.statusCode = 400;
@@ -748,8 +865,7 @@ function createNodeRequestHandler(options) {
748
865
  );
749
866
  let rootHtml = "";
750
867
  try {
751
- rootHtml = await renderInitialHtmlInWorker({
752
- pagesDir,
868
+ rootHtml = await initialHtmlWorker.render({
753
869
  pathname: stripBasePath(url.pathname, basePath),
754
870
  searchParams: parseSearchParams(url.searchParams),
755
871
  cookies: requestCookies,
package/dist/index.js CHANGED
@@ -478,7 +478,69 @@ 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";
481
+ import { spawn } from "node:child_process";
482
+ var INITIAL_HTML_WORKER_SCRIPT = `
483
+ const path = require("node:path");
484
+ const Module = require("node:module");
485
+
486
+ const pagesDir = process.env.WEBFRAMEZ_REACT_PAGES_DIR || "";
487
+ if (!pagesDir) {
488
+ throw new Error("Missing WEBFRAMEZ_REACT_PAGES_DIR");
489
+ }
490
+
491
+ const appRequire = Module.createRequire(path.join(pagesDir, "__webframez_react_worker__.js"));
492
+ const originalResolveFilename = Module._resolveFilename;
493
+ const forcedResolutions = new Map([
494
+ ["react", appRequire.resolve("react")],
495
+ ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
496
+ ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
497
+ ["react-dom/client", appRequire.resolve("react-dom/client")]
498
+ ]);
499
+
500
+ Module._resolveFilename = function(request, parent, isMain, options) {
501
+ if (forcedResolutions.has(request)) {
502
+ return forcedResolutions.get(request);
503
+ }
504
+ return originalResolveFilename.call(this, request, parent, isMain, options);
505
+ };
506
+
507
+ const { createFileRouter } = require("@webtypen/webframez-react/router");
508
+ const reactDomPkg = require.resolve("react-dom/package.json", {
509
+ paths: [process.cwd(), pagesDir]
510
+ });
511
+ const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
512
+ const router = createFileRouter({ pagesDir });
513
+
514
+ process.on("message", async (message) => {
515
+ if (!message || message.type !== "render") {
516
+ return;
517
+ }
518
+
519
+ const previousBasename = globalThis.__RSC_BASENAME;
520
+ globalThis.__RSC_BASENAME = message.payload.basename || "";
521
+
522
+ try {
523
+ const resolved = await router.resolve({
524
+ pathname: message.payload.pathname,
525
+ searchParams: message.payload.searchParams || {},
526
+ cookies: message.payload.cookies || {},
527
+ });
528
+ const html = reactDomServer.renderToString(resolved.model);
529
+ if (typeof process.send === "function") {
530
+ process.send({ id: message.id, ok: true, html });
531
+ }
532
+ } catch (error) {
533
+ const formatted = error && (error.stack || String(error))
534
+ ? (error.stack || String(error))
535
+ : "Unknown SSR worker error";
536
+ if (typeof process.send === "function") {
537
+ process.send({ id: message.id, ok: false, error: formatted });
538
+ }
539
+ } finally {
540
+ globalThis.__RSC_BASENAME = previousBasename;
541
+ }
542
+ });
543
+ `;
482
544
  function normalizeBasePath(basePath) {
483
545
  if (!basePath || basePath === "/") {
484
546
  return "";
@@ -498,62 +560,110 @@ function stripBasePath(pathname, basePath) {
498
560
  }
499
561
  return pathname;
500
562
  }
501
- function runNodeCommand(args) {
502
- return new Promise((resolve, reject) => {
503
- execFile(process.execPath, args, { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 }, (error, stdout, stderr) => {
504
- if (error) {
505
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
506
- reject(new Error(out || error.message));
563
+ function createInitialHtmlWorker(pagesDir) {
564
+ let child = null;
565
+ let nextRequestId = 1;
566
+ let stderrBuffer = "";
567
+ const pending = /* @__PURE__ */ new Map();
568
+ const rejectPending = (error) => {
569
+ for (const entry of pending.values()) {
570
+ clearTimeout(entry.timeout);
571
+ entry.reject(error);
572
+ }
573
+ pending.clear();
574
+ };
575
+ const stopWorker = () => {
576
+ if (!child) {
577
+ return;
578
+ }
579
+ child.removeAllListeners();
580
+ if (!child.killed) {
581
+ child.kill();
582
+ }
583
+ child = null;
584
+ };
585
+ const startWorker = () => {
586
+ if (child && child.connected && !child.killed) {
587
+ return child;
588
+ }
589
+ stderrBuffer = "";
590
+ child = spawn(process.execPath, ["-e", INITIAL_HTML_WORKER_SCRIPT], {
591
+ cwd: process.cwd(),
592
+ env: {
593
+ ...process.env,
594
+ WEBFRAMEZ_REACT_PAGES_DIR: pagesDir
595
+ },
596
+ stdio: ["ignore", "ignore", "pipe", "ipc"]
597
+ });
598
+ child.on("message", (message) => {
599
+ if (!message || typeof message.id !== "number") {
600
+ return;
601
+ }
602
+ const entry = pending.get(message.id);
603
+ if (!entry) {
507
604
  return;
508
605
  }
509
- resolve({ stdout, stderr });
606
+ pending.delete(message.id);
607
+ clearTimeout(entry.timeout);
608
+ if (message.ok) {
609
+ entry.resolve(message.html);
610
+ return;
611
+ }
612
+ entry.reject(new Error(message.error));
510
613
  });
511
- });
512
- }
513
- async function renderInitialHtmlInWorker(options) {
514
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
515
- const script = `
516
- const path = require("node:path");
517
- const Module = require("node:module");
518
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
519
- globalThis.__RSC_BASENAME = input.basename || "";
520
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
521
- const originalResolveFilename = Module._resolveFilename;
522
- const forcedResolutions = new Map([
523
- ["react", appRequire.resolve("react")],
524
- ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
525
- ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
526
- ["react-dom/client", appRequire.resolve("react-dom/client")]
527
- ]);
528
- Module._resolveFilename = function(request, parent, isMain, options) {
529
- if (forcedResolutions.has(request)) {
530
- return forcedResolutions.get(request);
531
- }
532
- return originalResolveFilename.call(this, request, parent, isMain, options);
533
- };
534
- const { createFileRouter } = require("webframez-react/router");
535
- const reactDomPkg = require.resolve("react-dom/package.json", {
536
- paths: [process.cwd(), input.pagesDir]
537
- });
538
- const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
539
-
540
- (async () => {
541
- const router = createFileRouter({ pagesDir: input.pagesDir });
542
- const resolved = await router.resolve({
543
- pathname: input.pathname,
544
- searchParams: input.searchParams || {},
545
- cookies: input.cookies || {},
546
- });
547
- const html = reactDomServer.renderToString(resolved.model);
548
- process.stdout.write(JSON.stringify({ html }));
549
- })().catch((error) => {
550
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
551
- process.exit(1);
552
- });
553
- `;
554
- const { stdout } = await runNodeCommand(["-e", script, payload]);
555
- const parsed = JSON.parse(stdout || "{}");
556
- return parsed.html || "";
614
+ child.stderr?.on("data", (chunk) => {
615
+ stderrBuffer = `${stderrBuffer}${chunk.toString("utf8")}`.slice(-8192);
616
+ });
617
+ child.on("exit", (code, signal) => {
618
+ const suffix = stderrBuffer.trim() !== "" ? `
619
+ ${stderrBuffer.trim()}` : "";
620
+ rejectPending(
621
+ new Error(
622
+ `[webframez-react] Initial HTML worker exited (${signal ?? code ?? "unknown"})${suffix}`
623
+ )
624
+ );
625
+ child = null;
626
+ });
627
+ child.on("error", (error) => {
628
+ rejectPending(error instanceof Error ? error : new Error(String(error)));
629
+ child = null;
630
+ });
631
+ return child;
632
+ };
633
+ return {
634
+ render(payload) {
635
+ const activeChild = startWorker();
636
+ const requestId = nextRequestId++;
637
+ return new Promise((resolve, reject) => {
638
+ const timeout = setTimeout(() => {
639
+ rejectPending(new Error("[webframez-react] Initial HTML worker timed out"));
640
+ stopWorker();
641
+ }, 1e4);
642
+ pending.set(requestId, { resolve, reject, timeout });
643
+ const request = {
644
+ id: requestId,
645
+ type: "render",
646
+ payload
647
+ };
648
+ activeChild.send(request, (error) => {
649
+ if (!error) {
650
+ return;
651
+ }
652
+ const entry = pending.get(requestId);
653
+ if (!entry) {
654
+ return;
655
+ }
656
+ pending.delete(requestId);
657
+ clearTimeout(entry.timeout);
658
+ entry.reject(error instanceof Error ? error : new Error(String(error)));
659
+ });
660
+ });
661
+ },
662
+ dispose() {
663
+ rejectPending(new Error("[webframez-react] Initial HTML worker disposed"));
664
+ stopWorker();
665
+ }
666
+ };
557
667
  }
558
668
  function withRequestBasename(basename, fn) {
559
669
  const target = globalThis;
@@ -613,6 +723,13 @@ function createNodeRequestHandler(options) {
613
723
  const liveReloadClients = /* @__PURE__ */ new Set();
614
724
  const router = createFileRouter({ pagesDir });
615
725
  const moduleMap = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
726
+ const initialHtmlWorker = createInitialHtmlWorker(pagesDir);
727
+ const disposeInitialHtmlWorker = () => {
728
+ initialHtmlWorker.dispose();
729
+ };
730
+ process.once("exit", disposeInitialHtmlWorker);
731
+ process.once("SIGINT", disposeInitialHtmlWorker);
732
+ process.once("SIGTERM", disposeInitialHtmlWorker);
616
733
  return async function handleRequest(req, res) {
617
734
  if (!req.url) {
618
735
  res.statusCode = 400;
@@ -712,8 +829,7 @@ function createNodeRequestHandler(options) {
712
829
  );
713
830
  let rootHtml = "";
714
831
  try {
715
- rootHtml = await renderInitialHtmlInWorker({
716
- pagesDir,
832
+ rootHtml = await initialHtmlWorker.render({
717
833
  pathname: stripBasePath(url.pathname, basePath),
718
834
  searchParams: parseSearchParams(url.searchParams),
719
835
  cookies: requestCookies,