@webtypen/webframez-react 0.0.4 → 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,57 +515,16 @@ function parseSearchParams(query) {
515
515
  var import_node_fs2 = __toESM(require("node:fs"), 1);
516
516
  var import_node_path2 = __toESM(require("node:path"), 1);
517
517
  var import_node_child_process = require("node:child_process");
518
- function normalizeBasePath(basePath) {
519
- if (!basePath || basePath === "/") {
520
- return "";
521
- }
522
- const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
523
- return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
524
- }
525
- function stripBasePath(pathname, basePath) {
526
- if (!basePath) {
527
- return pathname;
528
- }
529
- if (pathname === basePath) {
530
- return "/";
531
- }
532
- if (pathname.startsWith(`${basePath}/`)) {
533
- return pathname.slice(basePath.length) || "/";
534
- }
535
- return pathname;
536
- }
537
- function runNodeCommand(args) {
538
- return new Promise((resolve, reject) => {
539
- const execArgs = [
540
- "--conditions",
541
- "react-server",
542
- "-r",
543
- "@webtypen/webframez-react/register",
544
- ...args
545
- ];
546
- (0, import_node_child_process.execFile)(
547
- process.execPath,
548
- execArgs,
549
- { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 },
550
- (error, stdout, stderr) => {
551
- if (error) {
552
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
553
- reject(new Error(out || error.message));
554
- return;
555
- }
556
- resolve({ stdout, stderr });
557
- }
558
- );
559
- });
560
- }
561
- async function renderInitialHtmlInWorker(options) {
562
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
563
- const script = `
518
+ var INITIAL_HTML_WORKER_SCRIPT = `
564
519
  const path = require("node:path");
565
520
  const Module = require("node:module");
566
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
567
- globalThis.__RSC_BASENAME = input.basename || "";
568
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
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"));
569
528
  const originalResolveFilename = Module._resolveFilename;
570
529
  const forcedResolutions = new Map([
571
530
  ["react", appRequire.resolve("react")],
@@ -573,35 +532,174 @@ const forcedResolutions = new Map([
573
532
  ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
574
533
  ["react-dom/client", appRequire.resolve("react-dom/client")]
575
534
  ]);
535
+
576
536
  Module._resolveFilename = function(request, parent, isMain, options) {
577
537
  if (forcedResolutions.has(request)) {
578
538
  return forcedResolutions.get(request);
579
539
  }
580
540
  return originalResolveFilename.call(this, request, parent, isMain, options);
581
541
  };
542
+
582
543
  const { createFileRouter } = require("@webtypen/webframez-react/router");
583
544
  const reactDomPkg = require.resolve("react-dom/package.json", {
584
- paths: [process.cwd(), input.pagesDir]
545
+ paths: [process.cwd(), pagesDir]
585
546
  });
586
547
  const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
548
+ const router = createFileRouter({ pagesDir });
587
549
 
588
- (async () => {
589
- const router = createFileRouter({ pagesDir: input.pagesDir });
590
- const resolved = await router.resolve({
591
- pathname: input.pathname,
592
- searchParams: input.searchParams || {},
593
- cookies: input.cookies || {},
594
- });
595
- const html = reactDomServer.renderToString(resolved.model);
596
- process.stdout.write(JSON.stringify({ html }));
597
- })().catch((error) => {
598
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
599
- process.exit(1);
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
+ }
600
578
  });
601
579
  `;
602
- const { stdout } = await runNodeCommand(["-e", script, payload]);
603
- const parsed = JSON.parse(stdout || "{}");
604
- return parsed.html || "";
580
+ function normalizeBasePath(basePath) {
581
+ if (!basePath || basePath === "/") {
582
+ return "";
583
+ }
584
+ const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
585
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
586
+ }
587
+ function stripBasePath(pathname, basePath) {
588
+ if (!basePath) {
589
+ return pathname;
590
+ }
591
+ if (pathname === basePath) {
592
+ return "/";
593
+ }
594
+ if (pathname.startsWith(`${basePath}/`)) {
595
+ return pathname.slice(basePath.length) || "/";
596
+ }
597
+ return pathname;
598
+ }
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) {
640
+ return;
641
+ }
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));
649
+ });
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
+ };
605
703
  }
606
704
  function withRequestBasename(basename, fn) {
607
705
  const target = globalThis;
@@ -661,6 +759,13 @@ function createNodeRequestHandler(options) {
661
759
  const liveReloadClients = /* @__PURE__ */ new Set();
662
760
  const router = createFileRouter({ pagesDir });
663
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);
664
769
  return async function handleRequest(req, res) {
665
770
  if (!req.url) {
666
771
  res.statusCode = 400;
@@ -760,8 +865,7 @@ function createNodeRequestHandler(options) {
760
865
  );
761
866
  let rootHtml = "";
762
867
  try {
763
- rootHtml = await renderInitialHtmlInWorker({
764
- pagesDir,
868
+ rootHtml = await initialHtmlWorker.render({
765
869
  pathname: stripBasePath(url.pathname, basePath),
766
870
  searchParams: parseSearchParams(url.searchParams),
767
871
  cookies: requestCookies,
package/dist/index.js CHANGED
@@ -478,58 +478,17 @@ function parseSearchParams(query) {
478
478
  // src/http.ts
479
479
  import fs2 from "node:fs";
480
480
  import path2 from "node:path";
481
- import { execFile } from "node:child_process";
482
- function normalizeBasePath(basePath) {
483
- if (!basePath || basePath === "/") {
484
- return "";
485
- }
486
- const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
487
- return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
488
- }
489
- function stripBasePath(pathname, basePath) {
490
- if (!basePath) {
491
- return pathname;
492
- }
493
- if (pathname === basePath) {
494
- return "/";
495
- }
496
- if (pathname.startsWith(`${basePath}/`)) {
497
- return pathname.slice(basePath.length) || "/";
498
- }
499
- return pathname;
500
- }
501
- function runNodeCommand(args) {
502
- return new Promise((resolve, reject) => {
503
- const execArgs = [
504
- "--conditions",
505
- "react-server",
506
- "-r",
507
- "@webtypen/webframez-react/register",
508
- ...args
509
- ];
510
- execFile(
511
- process.execPath,
512
- execArgs,
513
- { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 },
514
- (error, stdout, stderr) => {
515
- if (error) {
516
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
517
- reject(new Error(out || error.message));
518
- return;
519
- }
520
- resolve({ stdout, stderr });
521
- }
522
- );
523
- });
524
- }
525
- async function renderInitialHtmlInWorker(options) {
526
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
527
- const script = `
481
+ import { spawn } from "node:child_process";
482
+ var INITIAL_HTML_WORKER_SCRIPT = `
528
483
  const path = require("node:path");
529
484
  const Module = require("node:module");
530
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
531
- globalThis.__RSC_BASENAME = input.basename || "";
532
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
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"));
533
492
  const originalResolveFilename = Module._resolveFilename;
534
493
  const forcedResolutions = new Map([
535
494
  ["react", appRequire.resolve("react")],
@@ -537,35 +496,174 @@ const forcedResolutions = new Map([
537
496
  ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
538
497
  ["react-dom/client", appRequire.resolve("react-dom/client")]
539
498
  ]);
499
+
540
500
  Module._resolveFilename = function(request, parent, isMain, options) {
541
501
  if (forcedResolutions.has(request)) {
542
502
  return forcedResolutions.get(request);
543
503
  }
544
504
  return originalResolveFilename.call(this, request, parent, isMain, options);
545
505
  };
506
+
546
507
  const { createFileRouter } = require("@webtypen/webframez-react/router");
547
508
  const reactDomPkg = require.resolve("react-dom/package.json", {
548
- paths: [process.cwd(), input.pagesDir]
509
+ paths: [process.cwd(), pagesDir]
549
510
  });
550
511
  const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
512
+ const router = createFileRouter({ pagesDir });
551
513
 
552
- (async () => {
553
- const router = createFileRouter({ pagesDir: input.pagesDir });
554
- const resolved = await router.resolve({
555
- pathname: input.pathname,
556
- searchParams: input.searchParams || {},
557
- cookies: input.cookies || {},
558
- });
559
- const html = reactDomServer.renderToString(resolved.model);
560
- process.stdout.write(JSON.stringify({ html }));
561
- })().catch((error) => {
562
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
563
- process.exit(1);
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
+ }
564
542
  });
565
543
  `;
566
- const { stdout } = await runNodeCommand(["-e", script, payload]);
567
- const parsed = JSON.parse(stdout || "{}");
568
- return parsed.html || "";
544
+ function normalizeBasePath(basePath) {
545
+ if (!basePath || basePath === "/") {
546
+ return "";
547
+ }
548
+ const withLeadingSlash = basePath.startsWith("/") ? basePath : `/${basePath}`;
549
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
550
+ }
551
+ function stripBasePath(pathname, basePath) {
552
+ if (!basePath) {
553
+ return pathname;
554
+ }
555
+ if (pathname === basePath) {
556
+ return "/";
557
+ }
558
+ if (pathname.startsWith(`${basePath}/`)) {
559
+ return pathname.slice(basePath.length) || "/";
560
+ }
561
+ return pathname;
562
+ }
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) {
604
+ return;
605
+ }
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));
613
+ });
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
+ };
569
667
  }
570
668
  function withRequestBasename(basename, fn) {
571
669
  const target = globalThis;
@@ -625,6 +723,13 @@ function createNodeRequestHandler(options) {
625
723
  const liveReloadClients = /* @__PURE__ */ new Set();
626
724
  const router = createFileRouter({ pagesDir });
627
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);
628
733
  return async function handleRequest(req, res) {
629
734
  if (!req.url) {
630
735
  res.statusCode = 400;
@@ -724,8 +829,7 @@ function createNodeRequestHandler(options) {
724
829
  );
725
830
  let rootHtml = "";
726
831
  try {
727
- rootHtml = await renderInitialHtmlInWorker({
728
- pagesDir,
832
+ rootHtml = await initialHtmlWorker.render({
729
833
  pathname: stripBasePath(url.pathname, basePath),
730
834
  searchParams: parseSearchParams(url.searchParams),
731
835
  cookies: requestCookies,