@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.
@@ -481,57 +481,16 @@ function parseSearchParams(query) {
481
481
  }
482
482
 
483
483
  // src/http.ts
484
- function normalizeBasePath(basePath) {
485
- if (!basePath || basePath === "/") {
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;
502
- }
503
- function runNodeCommand(args) {
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 = `
484
+ var INITIAL_HTML_WORKER_SCRIPT = `
530
485
  const path = require("node:path");
531
486
  const Module = require("node:module");
532
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
533
- globalThis.__RSC_BASENAME = input.basename || "";
534
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
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"));
535
494
  const originalResolveFilename = Module._resolveFilename;
536
495
  const forcedResolutions = new Map([
537
496
  ["react", appRequire.resolve("react")],
@@ -539,35 +498,174 @@ const forcedResolutions = new Map([
539
498
  ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
540
499
  ["react-dom/client", appRequire.resolve("react-dom/client")]
541
500
  ]);
501
+
542
502
  Module._resolveFilename = function(request, parent, isMain, options) {
543
503
  if (forcedResolutions.has(request)) {
544
504
  return forcedResolutions.get(request);
545
505
  }
546
506
  return originalResolveFilename.call(this, request, parent, isMain, options);
547
507
  };
508
+
548
509
  const { createFileRouter } = require("@webtypen/webframez-react/router");
549
510
  const reactDomPkg = require.resolve("react-dom/package.json", {
550
- paths: [process.cwd(), input.pagesDir]
511
+ paths: [process.cwd(), pagesDir]
551
512
  });
552
513
  const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
514
+ const router = createFileRouter({ pagesDir });
553
515
 
554
- (async () => {
555
- const router = createFileRouter({ pagesDir: input.pagesDir });
556
- const resolved = await router.resolve({
557
- pathname: input.pathname,
558
- searchParams: input.searchParams || {},
559
- cookies: input.cookies || {},
560
- });
561
- const html = reactDomServer.renderToString(resolved.model);
562
- process.stdout.write(JSON.stringify({ html }));
563
- })().catch((error) => {
564
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
565
- process.exit(1);
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
+ }
566
544
  });
567
545
  `;
568
- const { stdout } = await runNodeCommand(["-e", script, payload]);
569
- const parsed = JSON.parse(stdout || "{}");
570
- return parsed.html || "";
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 createInitialHtmlWorker(pagesDir) {
566
+ let child = null;
567
+ let nextRequestId = 1;
568
+ let stderrBuffer = "";
569
+ const pending = /* @__PURE__ */ new Map();
570
+ const rejectPending = (error) => {
571
+ for (const entry of pending.values()) {
572
+ clearTimeout(entry.timeout);
573
+ entry.reject(error);
574
+ }
575
+ pending.clear();
576
+ };
577
+ const stopWorker = () => {
578
+ if (!child) {
579
+ return;
580
+ }
581
+ child.removeAllListeners();
582
+ if (!child.killed) {
583
+ child.kill();
584
+ }
585
+ child = null;
586
+ };
587
+ const startWorker = () => {
588
+ if (child && child.connected && !child.killed) {
589
+ return child;
590
+ }
591
+ stderrBuffer = "";
592
+ child = (0, import_node_child_process.spawn)(process.execPath, ["-e", INITIAL_HTML_WORKER_SCRIPT], {
593
+ cwd: process.cwd(),
594
+ env: {
595
+ ...process.env,
596
+ WEBFRAMEZ_REACT_PAGES_DIR: pagesDir
597
+ },
598
+ stdio: ["ignore", "ignore", "pipe", "ipc"]
599
+ });
600
+ child.on("message", (message) => {
601
+ if (!message || typeof message.id !== "number") {
602
+ return;
603
+ }
604
+ const entry = pending.get(message.id);
605
+ if (!entry) {
606
+ return;
607
+ }
608
+ pending.delete(message.id);
609
+ clearTimeout(entry.timeout);
610
+ if (message.ok) {
611
+ entry.resolve(message.html);
612
+ return;
613
+ }
614
+ entry.reject(new Error(message.error));
615
+ });
616
+ child.stderr?.on("data", (chunk) => {
617
+ stderrBuffer = `${stderrBuffer}${chunk.toString("utf8")}`.slice(-8192);
618
+ });
619
+ child.on("exit", (code, signal) => {
620
+ const suffix = stderrBuffer.trim() !== "" ? `
621
+ ${stderrBuffer.trim()}` : "";
622
+ rejectPending(
623
+ new Error(
624
+ `[webframez-react] Initial HTML worker exited (${signal ?? code ?? "unknown"})${suffix}`
625
+ )
626
+ );
627
+ child = null;
628
+ });
629
+ child.on("error", (error) => {
630
+ rejectPending(error instanceof Error ? error : new Error(String(error)));
631
+ child = null;
632
+ });
633
+ return child;
634
+ };
635
+ return {
636
+ render(payload) {
637
+ const activeChild = startWorker();
638
+ const requestId = nextRequestId++;
639
+ return new Promise((resolve, reject) => {
640
+ const timeout = setTimeout(() => {
641
+ rejectPending(new Error("[webframez-react] Initial HTML worker timed out"));
642
+ stopWorker();
643
+ }, 1e4);
644
+ pending.set(requestId, { resolve, reject, timeout });
645
+ const request = {
646
+ id: requestId,
647
+ type: "render",
648
+ payload
649
+ };
650
+ activeChild.send(request, (error) => {
651
+ if (!error) {
652
+ return;
653
+ }
654
+ const entry = pending.get(requestId);
655
+ if (!entry) {
656
+ return;
657
+ }
658
+ pending.delete(requestId);
659
+ clearTimeout(entry.timeout);
660
+ entry.reject(error instanceof Error ? error : new Error(String(error)));
661
+ });
662
+ });
663
+ },
664
+ dispose() {
665
+ rejectPending(new Error("[webframez-react] Initial HTML worker disposed"));
666
+ stopWorker();
667
+ }
668
+ };
571
669
  }
572
670
  function withRequestBasename(basename, fn) {
573
671
  const target = globalThis;
@@ -627,6 +725,13 @@ function createNodeRequestHandler(options) {
627
725
  const liveReloadClients = /* @__PURE__ */ new Set();
628
726
  const router = createFileRouter({ pagesDir });
629
727
  const moduleMap = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf-8"));
728
+ const initialHtmlWorker = createInitialHtmlWorker(pagesDir);
729
+ const disposeInitialHtmlWorker = () => {
730
+ initialHtmlWorker.dispose();
731
+ };
732
+ process.once("exit", disposeInitialHtmlWorker);
733
+ process.once("SIGINT", disposeInitialHtmlWorker);
734
+ process.once("SIGTERM", disposeInitialHtmlWorker);
630
735
  return async function handleRequest(req, res) {
631
736
  if (!req.url) {
632
737
  res.statusCode = 400;
@@ -726,8 +831,7 @@ function createNodeRequestHandler(options) {
726
831
  );
727
832
  let rootHtml = "";
728
833
  try {
729
- rootHtml = await renderInitialHtmlInWorker({
730
- pagesDir,
834
+ rootHtml = await initialHtmlWorker.render({
731
835
  pathname: stripBasePath(url.pathname, basePath),
732
836
  searchParams: parseSearchParams(url.searchParams),
733
837
  cookies: requestCookies,
@@ -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 { execFile } from "node:child_process";
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,16 @@ function parseSearchParams(query) {
453
453
  }
454
454
 
455
455
  // src/http.ts
456
- function normalizeBasePath(basePath) {
457
- if (!basePath || basePath === "/") {
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
- });
498
- }
499
- async function renderInitialHtmlInWorker(options) {
500
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
501
- const script = `
456
+ var INITIAL_HTML_WORKER_SCRIPT = `
502
457
  const path = require("node:path");
503
458
  const Module = require("node:module");
504
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
505
- globalThis.__RSC_BASENAME = input.basename || "";
506
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
459
+
460
+ const pagesDir = process.env.WEBFRAMEZ_REACT_PAGES_DIR || "";
461
+ if (!pagesDir) {
462
+ throw new Error("Missing WEBFRAMEZ_REACT_PAGES_DIR");
463
+ }
464
+
465
+ const appRequire = Module.createRequire(path.join(pagesDir, "__webframez_react_worker__.js"));
507
466
  const originalResolveFilename = Module._resolveFilename;
508
467
  const forcedResolutions = new Map([
509
468
  ["react", appRequire.resolve("react")],
@@ -511,35 +470,174 @@ const forcedResolutions = new Map([
511
470
  ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
512
471
  ["react-dom/client", appRequire.resolve("react-dom/client")]
513
472
  ]);
473
+
514
474
  Module._resolveFilename = function(request, parent, isMain, options) {
515
475
  if (forcedResolutions.has(request)) {
516
476
  return forcedResolutions.get(request);
517
477
  }
518
478
  return originalResolveFilename.call(this, request, parent, isMain, options);
519
479
  };
480
+
520
481
  const { createFileRouter } = require("@webtypen/webframez-react/router");
521
482
  const reactDomPkg = require.resolve("react-dom/package.json", {
522
- paths: [process.cwd(), input.pagesDir]
483
+ paths: [process.cwd(), pagesDir]
523
484
  });
524
485
  const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
486
+ const router = createFileRouter({ pagesDir });
525
487
 
526
- (async () => {
527
- const router = createFileRouter({ pagesDir: input.pagesDir });
528
- const resolved = await router.resolve({
529
- pathname: input.pathname,
530
- searchParams: input.searchParams || {},
531
- cookies: input.cookies || {},
532
- });
533
- const html = reactDomServer.renderToString(resolved.model);
534
- process.stdout.write(JSON.stringify({ html }));
535
- })().catch((error) => {
536
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
537
- process.exit(1);
488
+ process.on("message", async (message) => {
489
+ if (!message || message.type !== "render") {
490
+ return;
491
+ }
492
+
493
+ const previousBasename = globalThis.__RSC_BASENAME;
494
+ globalThis.__RSC_BASENAME = message.payload.basename || "";
495
+
496
+ try {
497
+ const resolved = await router.resolve({
498
+ pathname: message.payload.pathname,
499
+ searchParams: message.payload.searchParams || {},
500
+ cookies: message.payload.cookies || {},
501
+ });
502
+ const html = reactDomServer.renderToString(resolved.model);
503
+ if (typeof process.send === "function") {
504
+ process.send({ id: message.id, ok: true, html });
505
+ }
506
+ } catch (error) {
507
+ const formatted = error && (error.stack || String(error))
508
+ ? (error.stack || String(error))
509
+ : "Unknown SSR worker error";
510
+ if (typeof process.send === "function") {
511
+ process.send({ id: message.id, ok: false, error: formatted });
512
+ }
513
+ } finally {
514
+ globalThis.__RSC_BASENAME = previousBasename;
515
+ }
538
516
  });
539
517
  `;
540
- const { stdout } = await runNodeCommand(["-e", script, payload]);
541
- const parsed = JSON.parse(stdout || "{}");
542
- return parsed.html || "";
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 createInitialHtmlWorker(pagesDir) {
538
+ let child = null;
539
+ let nextRequestId = 1;
540
+ let stderrBuffer = "";
541
+ const pending = /* @__PURE__ */ new Map();
542
+ const rejectPending = (error) => {
543
+ for (const entry of pending.values()) {
544
+ clearTimeout(entry.timeout);
545
+ entry.reject(error);
546
+ }
547
+ pending.clear();
548
+ };
549
+ const stopWorker = () => {
550
+ if (!child) {
551
+ return;
552
+ }
553
+ child.removeAllListeners();
554
+ if (!child.killed) {
555
+ child.kill();
556
+ }
557
+ child = null;
558
+ };
559
+ const startWorker = () => {
560
+ if (child && child.connected && !child.killed) {
561
+ return child;
562
+ }
563
+ stderrBuffer = "";
564
+ child = spawn(process.execPath, ["-e", INITIAL_HTML_WORKER_SCRIPT], {
565
+ cwd: process.cwd(),
566
+ env: {
567
+ ...process.env,
568
+ WEBFRAMEZ_REACT_PAGES_DIR: pagesDir
569
+ },
570
+ stdio: ["ignore", "ignore", "pipe", "ipc"]
571
+ });
572
+ child.on("message", (message) => {
573
+ if (!message || typeof message.id !== "number") {
574
+ return;
575
+ }
576
+ const entry = pending.get(message.id);
577
+ if (!entry) {
578
+ return;
579
+ }
580
+ pending.delete(message.id);
581
+ clearTimeout(entry.timeout);
582
+ if (message.ok) {
583
+ entry.resolve(message.html);
584
+ return;
585
+ }
586
+ entry.reject(new Error(message.error));
587
+ });
588
+ child.stderr?.on("data", (chunk) => {
589
+ stderrBuffer = `${stderrBuffer}${chunk.toString("utf8")}`.slice(-8192);
590
+ });
591
+ child.on("exit", (code, signal) => {
592
+ const suffix = stderrBuffer.trim() !== "" ? `
593
+ ${stderrBuffer.trim()}` : "";
594
+ rejectPending(
595
+ new Error(
596
+ `[webframez-react] Initial HTML worker exited (${signal ?? code ?? "unknown"})${suffix}`
597
+ )
598
+ );
599
+ child = null;
600
+ });
601
+ child.on("error", (error) => {
602
+ rejectPending(error instanceof Error ? error : new Error(String(error)));
603
+ child = null;
604
+ });
605
+ return child;
606
+ };
607
+ return {
608
+ render(payload) {
609
+ const activeChild = startWorker();
610
+ const requestId = nextRequestId++;
611
+ return new Promise((resolve, reject) => {
612
+ const timeout = setTimeout(() => {
613
+ rejectPending(new Error("[webframez-react] Initial HTML worker timed out"));
614
+ stopWorker();
615
+ }, 1e4);
616
+ pending.set(requestId, { resolve, reject, timeout });
617
+ const request = {
618
+ id: requestId,
619
+ type: "render",
620
+ payload
621
+ };
622
+ activeChild.send(request, (error) => {
623
+ if (!error) {
624
+ return;
625
+ }
626
+ const entry = pending.get(requestId);
627
+ if (!entry) {
628
+ return;
629
+ }
630
+ pending.delete(requestId);
631
+ clearTimeout(entry.timeout);
632
+ entry.reject(error instanceof Error ? error : new Error(String(error)));
633
+ });
634
+ });
635
+ },
636
+ dispose() {
637
+ rejectPending(new Error("[webframez-react] Initial HTML worker disposed"));
638
+ stopWorker();
639
+ }
640
+ };
543
641
  }
544
642
  function withRequestBasename(basename, fn) {
545
643
  const target = globalThis;
@@ -599,6 +697,13 @@ function createNodeRequestHandler(options) {
599
697
  const liveReloadClients = /* @__PURE__ */ new Set();
600
698
  const router = createFileRouter({ pagesDir });
601
699
  const moduleMap = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
700
+ const initialHtmlWorker = createInitialHtmlWorker(pagesDir);
701
+ const disposeInitialHtmlWorker = () => {
702
+ initialHtmlWorker.dispose();
703
+ };
704
+ process.once("exit", disposeInitialHtmlWorker);
705
+ process.once("SIGINT", disposeInitialHtmlWorker);
706
+ process.once("SIGTERM", disposeInitialHtmlWorker);
602
707
  return async function handleRequest(req, res) {
603
708
  if (!req.url) {
604
709
  res.statusCode = 400;
@@ -698,8 +803,7 @@ function createNodeRequestHandler(options) {
698
803
  );
699
804
  let rootHtml = "";
700
805
  try {
701
- rootHtml = await renderInitialHtmlInWorker({
702
- pagesDir,
806
+ rootHtml = await initialHtmlWorker.render({
703
807
  pathname: stripBasePath(url.pathname, basePath),
704
808
  searchParams: parseSearchParams(url.searchParams),
705
809
  cookies: requestCookies,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-react",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "TypeScript React RSC addition for @webtypen/webframez-core",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "author": {