@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.
@@ -481,6 +481,68 @@ function parseSearchParams(query) {
481
481
  }
482
482
 
483
483
  // src/http.ts
484
+ var INITIAL_HTML_WORKER_SCRIPT = `
485
+ const path = require("node:path");
486
+ const Module = require("node:module");
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"));
494
+ const originalResolveFilename = Module._resolveFilename;
495
+ const forcedResolutions = new Map([
496
+ ["react", appRequire.resolve("react")],
497
+ ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
498
+ ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
499
+ ["react-dom/client", appRequire.resolve("react-dom/client")]
500
+ ]);
501
+
502
+ Module._resolveFilename = function(request, parent, isMain, options) {
503
+ if (forcedResolutions.has(request)) {
504
+ return forcedResolutions.get(request);
505
+ }
506
+ return originalResolveFilename.call(this, request, parent, isMain, options);
507
+ };
508
+
509
+ const { createFileRouter } = require("@webtypen/webframez-react/router");
510
+ const reactDomPkg = require.resolve("react-dom/package.json", {
511
+ paths: [process.cwd(), pagesDir]
512
+ });
513
+ const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
514
+ const router = createFileRouter({ pagesDir });
515
+
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
+ }
544
+ });
545
+ `;
484
546
  function normalizeBasePath(basePath) {
485
547
  if (!basePath || basePath === "/") {
486
548
  return "";
@@ -500,62 +562,110 @@ function stripBasePath(pathname, basePath) {
500
562
  }
501
563
  return pathname;
502
564
  }
503
- function runNodeCommand(args) {
504
- return new Promise((resolve, reject) => {
505
- (0, import_node_child_process.execFile)(process.execPath, args, { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 }, (error, stdout, stderr) => {
506
- if (error) {
507
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
508
- reject(new Error(out || error.message));
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) {
509
606
  return;
510
607
  }
511
- resolve({ stdout, stderr });
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));
512
615
  });
513
- });
514
- }
515
- async function renderInitialHtmlInWorker(options) {
516
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
517
- const script = `
518
- const path = require("node:path");
519
- const Module = require("node:module");
520
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
521
- globalThis.__RSC_BASENAME = input.basename || "";
522
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
523
- const originalResolveFilename = Module._resolveFilename;
524
- const forcedResolutions = new Map([
525
- ["react", appRequire.resolve("react")],
526
- ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
527
- ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
528
- ["react-dom/client", appRequire.resolve("react-dom/client")]
529
- ]);
530
- Module._resolveFilename = function(request, parent, isMain, options) {
531
- if (forcedResolutions.has(request)) {
532
- return forcedResolutions.get(request);
533
- }
534
- return originalResolveFilename.call(this, request, parent, isMain, options);
535
- };
536
- const { createFileRouter } = require("webframez-react/router");
537
- const reactDomPkg = require.resolve("react-dom/package.json", {
538
- paths: [process.cwd(), input.pagesDir]
539
- });
540
- const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
541
-
542
- (async () => {
543
- const router = createFileRouter({ pagesDir: input.pagesDir });
544
- const resolved = await router.resolve({
545
- pathname: input.pathname,
546
- searchParams: input.searchParams || {},
547
- cookies: input.cookies || {},
548
- });
549
- const html = reactDomServer.renderToString(resolved.model);
550
- process.stdout.write(JSON.stringify({ html }));
551
- })().catch((error) => {
552
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
553
- process.exit(1);
554
- });
555
- `;
556
- const { stdout } = await runNodeCommand(["-e", script, payload]);
557
- const parsed = JSON.parse(stdout || "{}");
558
- return parsed.html || "";
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
+ };
559
669
  }
560
670
  function withRequestBasename(basename, fn) {
561
671
  const target = globalThis;
@@ -615,6 +725,13 @@ function createNodeRequestHandler(options) {
615
725
  const liveReloadClients = /* @__PURE__ */ new Set();
616
726
  const router = createFileRouter({ pagesDir });
617
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);
618
735
  return async function handleRequest(req, res) {
619
736
  if (!req.url) {
620
737
  res.statusCode = 400;
@@ -714,8 +831,7 @@ function createNodeRequestHandler(options) {
714
831
  );
715
832
  let rootHtml = "";
716
833
  try {
717
- rootHtml = await renderInitialHtmlInWorker({
718
- pagesDir,
834
+ rootHtml = await initialHtmlWorker.render({
719
835
  pathname: stripBasePath(url.pathname, basePath),
720
836
  searchParams: parseSearchParams(url.searchParams),
721
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,6 +453,68 @@ function parseSearchParams(query) {
453
453
  }
454
454
 
455
455
  // src/http.ts
456
+ var INITIAL_HTML_WORKER_SCRIPT = `
457
+ const path = require("node:path");
458
+ const Module = require("node:module");
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"));
466
+ const originalResolveFilename = Module._resolveFilename;
467
+ const forcedResolutions = new Map([
468
+ ["react", appRequire.resolve("react")],
469
+ ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
470
+ ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
471
+ ["react-dom/client", appRequire.resolve("react-dom/client")]
472
+ ]);
473
+
474
+ Module._resolveFilename = function(request, parent, isMain, options) {
475
+ if (forcedResolutions.has(request)) {
476
+ return forcedResolutions.get(request);
477
+ }
478
+ return originalResolveFilename.call(this, request, parent, isMain, options);
479
+ };
480
+
481
+ const { createFileRouter } = require("@webtypen/webframez-react/router");
482
+ const reactDomPkg = require.resolve("react-dom/package.json", {
483
+ paths: [process.cwd(), pagesDir]
484
+ });
485
+ const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
486
+ const router = createFileRouter({ pagesDir });
487
+
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
+ }
516
+ });
517
+ `;
456
518
  function normalizeBasePath(basePath) {
457
519
  if (!basePath || basePath === "/") {
458
520
  return "";
@@ -472,62 +534,110 @@ function stripBasePath(pathname, basePath) {
472
534
  }
473
535
  return pathname;
474
536
  }
475
- function runNodeCommand(args) {
476
- return new Promise((resolve, reject) => {
477
- execFile(process.execPath, args, { timeout: 1e4, maxBuffer: 1024 * 1024 * 5 }, (error, stdout, stderr) => {
478
- if (error) {
479
- const out = stderr && stderr.trim() !== "" ? stderr : stdout;
480
- reject(new Error(out || error.message));
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) {
481
578
  return;
482
579
  }
483
- resolve({ stdout, stderr });
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));
484
587
  });
485
- });
486
- }
487
- async function renderInitialHtmlInWorker(options) {
488
- const payload = Buffer.from(JSON.stringify(options), "utf8").toString("base64url");
489
- const script = `
490
- const path = require("node:path");
491
- const Module = require("node:module");
492
- const input = JSON.parse(Buffer.from(process.argv[1], "base64url").toString("utf8"));
493
- globalThis.__RSC_BASENAME = input.basename || "";
494
- const appRequire = Module.createRequire(path.join(input.pagesDir, "__webframez_react_worker__.js"));
495
- const originalResolveFilename = Module._resolveFilename;
496
- const forcedResolutions = new Map([
497
- ["react", appRequire.resolve("react")],
498
- ["react/jsx-runtime", appRequire.resolve("react/jsx-runtime")],
499
- ["react/jsx-dev-runtime", appRequire.resolve("react/jsx-dev-runtime")],
500
- ["react-dom/client", appRequire.resolve("react-dom/client")]
501
- ]);
502
- Module._resolveFilename = function(request, parent, isMain, options) {
503
- if (forcedResolutions.has(request)) {
504
- return forcedResolutions.get(request);
505
- }
506
- return originalResolveFilename.call(this, request, parent, isMain, options);
507
- };
508
- const { createFileRouter } = require("webframez-react/router");
509
- const reactDomPkg = require.resolve("react-dom/package.json", {
510
- paths: [process.cwd(), input.pagesDir]
511
- });
512
- const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node.js"));
513
-
514
- (async () => {
515
- const router = createFileRouter({ pagesDir: input.pagesDir });
516
- const resolved = await router.resolve({
517
- pathname: input.pathname,
518
- searchParams: input.searchParams || {},
519
- cookies: input.cookies || {},
520
- });
521
- const html = reactDomServer.renderToString(resolved.model);
522
- process.stdout.write(JSON.stringify({ html }));
523
- })().catch((error) => {
524
- process.stderr.write(error && (error.stack || String(error)) ? (error.stack || String(error)) : "Unknown SSR worker error");
525
- process.exit(1);
526
- });
527
- `;
528
- const { stdout } = await runNodeCommand(["-e", script, payload]);
529
- const parsed = JSON.parse(stdout || "{}");
530
- return parsed.html || "";
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
+ };
531
641
  }
532
642
  function withRequestBasename(basename, fn) {
533
643
  const target = globalThis;
@@ -587,6 +697,13 @@ function createNodeRequestHandler(options) {
587
697
  const liveReloadClients = /* @__PURE__ */ new Set();
588
698
  const router = createFileRouter({ pagesDir });
589
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);
590
707
  return async function handleRequest(req, res) {
591
708
  if (!req.url) {
592
709
  res.statusCode = 400;
@@ -686,8 +803,7 @@ function createNodeRequestHandler(options) {
686
803
  );
687
804
  let rootHtml = "";
688
805
  try {
689
- rootHtml = await renderInitialHtmlInWorker({
690
- pagesDir,
806
+ rootHtml = await initialHtmlWorker.render({
691
807
  pathname: stripBasePath(url.pathname, basePath),
692
808
  searchParams: parseSearchParams(url.searchParams),
693
809
  cookies: requestCookies,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-react",
3
- "version": "0.0.3",
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": {
@@ -58,6 +58,7 @@
58
58
  "import": "./dist/webframez-core.js",
59
59
  "require": "./dist/webframez-core.cjs"
60
60
  },
61
+ "./register": "./register.cjs",
61
62
  "./defaults/webpack.client": "./defaults/webpack.client.cjs",
62
63
  "./defaults/webpack.server": "./defaults/webpack.server.cjs",
63
64
  "./defaults/tsconfig.server": "./defaults/tsconfig.server.json",
@@ -67,7 +68,8 @@
67
68
  "files": [
68
69
  "dist",
69
70
  "defaults",
70
- "bin"
71
+ "bin",
72
+ "register.cjs"
71
73
  ],
72
74
  "scripts": {
73
75
  "clean": "rm -rf dist",
package/register.cjs ADDED
@@ -0,0 +1 @@
1
+ require("react-server-dom-webpack/node-register")();