@webtypen/webframez-react 0.0.35 → 0.0.36

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.js CHANGED
@@ -11,6 +11,12 @@ import fs2 from "node:fs";
11
11
  import path3 from "node:path";
12
12
  import { fileURLToPath, pathToFileURL } from "node:url";
13
13
  import { spawn } from "node:child_process";
14
+ import {
15
+ brotliCompressSync,
16
+ createBrotliCompress,
17
+ createGzip,
18
+ gzipSync
19
+ } from "node:zlib";
14
20
 
15
21
  // src/server.ts
16
22
  import path from "node:path";
@@ -767,7 +773,77 @@ function parseSearchParams(query) {
767
773
  function createInitialHtmlErrorMarkup(message) {
768
774
  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>`;
769
775
  }
776
+ function createClientAssetVersion(distRootDir) {
777
+ try {
778
+ const stat = fs2.statSync(path3.join(distRootDir, "client.js"));
779
+ return `${Math.floor(stat.mtimeMs)}-${stat.size}`;
780
+ } catch {
781
+ return "";
782
+ }
783
+ }
784
+ function appendAssetVersion(url, version) {
785
+ if (!version) {
786
+ return url;
787
+ }
788
+ const separator = url.includes("?") ? "&" : "?";
789
+ return `${url}${separator}v=${encodeURIComponent(version)}`;
790
+ }
791
+ function isWithinDirectory(filePath, directory) {
792
+ return filePath === directory || filePath.startsWith(`${directory}${path3.sep}`);
793
+ }
794
+ function isCompressibleAsset(ext) {
795
+ return [".js", ".mjs", ".json", ".css", ".svg", ".txt", ".html"].includes(ext);
796
+ }
797
+ function isHashedAssetPath(filePath) {
798
+ return /-[a-f0-9]{12,}\.[cm]?js$/i.test(path3.basename(filePath));
799
+ }
800
+ function setAssetContentType(res, ext) {
801
+ if (ext === ".js" || ext === ".mjs") {
802
+ res.setHeader("Content-Type", "text/javascript; charset=utf-8");
803
+ } else if (ext === ".json") {
804
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
805
+ } else if (ext === ".css") {
806
+ res.setHeader("Content-Type", "text/css; charset=utf-8");
807
+ } else if (ext === ".svg") {
808
+ res.setHeader("Content-Type", "image/svg+xml; charset=utf-8");
809
+ }
810
+ }
811
+ function getPreferredContentEncoding(req, ext, fileSize) {
812
+ if (!isCompressibleAsset(ext) || fileSize < 1024) {
813
+ return "";
814
+ }
815
+ const acceptEncoding = String(req.headers["accept-encoding"] || "");
816
+ if (/\bbr\b/.test(acceptEncoding)) {
817
+ return "br";
818
+ }
819
+ if (/\bgzip\b/.test(acceptEncoding)) {
820
+ return "gzip";
821
+ }
822
+ return "";
823
+ }
824
+ function sendTextResponse(req, res, body, options) {
825
+ const bodyBuffer = Buffer.from(body);
826
+ const contentEncoding = options.compress === false ? "" : getPreferredContentEncoding(req, ".html", bodyBuffer.length);
827
+ res.statusCode = options.statusCode ?? 200;
828
+ res.setHeader("Content-Type", options.contentType);
829
+ if (options.cacheControl) {
830
+ res.setHeader("Cache-Control", options.cacheControl);
831
+ }
832
+ res.setHeader("Vary", "Accept-Encoding");
833
+ if (contentEncoding === "br") {
834
+ res.setHeader("Content-Encoding", "br");
835
+ res.end(brotliCompressSync(bodyBuffer));
836
+ return;
837
+ }
838
+ if (contentEncoding === "gzip") {
839
+ res.setHeader("Content-Encoding", "gzip");
840
+ res.end(gzipSync(bodyBuffer));
841
+ return;
842
+ }
843
+ res.end(bodyBuffer);
844
+ }
770
845
  var INITIAL_HTML_WORKER_SCRIPT = `
846
+ const fs = require("node:fs");
771
847
  const path = require("node:path");
772
848
  const Module = require("node:module");
773
849
  const { Readable, Writable } = require("node:stream");
@@ -817,11 +893,42 @@ const reactDomServer = require(path.join(path.dirname(reactDomPkg), "server.node
817
893
  globalThis.__webpack_chunk_load__ = function __webframezNoopChunkLoad() {
818
894
  return Promise.resolve();
819
895
  };
896
+ function normalizeWebframezRequireCandidate(candidate) {
897
+ const stagingMarker = path.join(".webframez-build", "");
898
+ const appMarker = path.join("app", "");
899
+ const stagingIndex = candidate.indexOf(stagingMarker);
900
+ const appIndex = candidate.indexOf(appMarker, stagingIndex >= 0 ? stagingIndex : 0);
901
+ if (stagingIndex >= 0 && appIndex >= 0) {
902
+ const runtimeCandidate = path.resolve(process.cwd(), candidate.slice(appIndex));
903
+ if (fs.existsSync(runtimeCandidate)) {
904
+ return runtimeCandidate;
905
+ }
906
+ }
907
+
908
+ const frameworkDistDir = path.join("node_modules", "@webtypen", "webframez-react", "dist");
909
+ const shouldUseCjs =
910
+ candidate.includes(frameworkDistDir) &&
911
+ (candidate.endsWith(path.join("dist", "navigation.js")) ||
912
+ candidate.endsWith(path.join("dist", "route-slot.js")));
913
+
914
+ if (!shouldUseCjs) {
915
+ return candidate;
916
+ }
917
+
918
+ const cjsCandidate = candidate.slice(0, -3) + ".cjs";
919
+ return fs.existsSync(cjsCandidate) ? cjsCandidate : candidate;
920
+ }
921
+
820
922
  globalThis.__webpack_require__ = function __webframezNodeRequire(id) {
821
923
  if (typeof id !== "string") {
822
924
  return require(id);
823
925
  }
824
926
 
927
+ const directRequireTarget = normalizeWebframezRequireCandidate(id);
928
+ if (directRequireTarget !== id) {
929
+ return require(directRequireTarget);
930
+ }
931
+
825
932
  if (id.startsWith("./")) {
826
933
  const relativeId = id.slice(2);
827
934
  const candidates = [
@@ -829,10 +936,16 @@ globalThis.__webpack_require__ = function __webframezNodeRequire(id) {
829
936
  path.resolve(process.cwd(), "..", relativeId)
830
937
  ];
831
938
  for (const candidate of candidates) {
939
+ const requireTarget = normalizeWebframezRequireCandidate(candidate);
832
940
  try {
833
- return require(candidate);
941
+ return require(requireTarget);
834
942
  } catch (error) {
835
- const missingCandidate = error && error.code === "MODULE_NOT_FOUND" && typeof error.message === "string" && error.message.includes("'" + candidate + "'");
943
+ const missingCandidate =
944
+ error &&
945
+ error.code === "MODULE_NOT_FOUND" &&
946
+ typeof error.message === "string" &&
947
+ (error.message.includes("'" + candidate + "'") ||
948
+ error.message.includes("'" + requireTarget + "'"));
836
949
  if (!missingCandidate) {
837
950
  throw error;
838
951
  }
@@ -1054,6 +1167,14 @@ function joinRuntimeBasePath(basePath, pathname) {
1054
1167
  }
1055
1168
  return normalizedPath === "/" ? basePath : `${basePath}${normalizedPath}`;
1056
1169
  }
1170
+ function joinRuntimeAssetPath(basePath, pathname) {
1171
+ const normalizedBasePath = normalizeRuntimeBasePath(basePath) ?? "";
1172
+ const normalizedPath = !pathname || pathname === "/" ? "/" : pathname.startsWith("/") ? pathname : `/${pathname}`;
1173
+ if (normalizedBasePath && (normalizedPath === normalizedBasePath || normalizedPath.startsWith(`${normalizedBasePath}/`))) {
1174
+ return normalizedPath;
1175
+ }
1176
+ return joinRuntimeBasePath(normalizedBasePath, normalizedPath);
1177
+ }
1057
1178
  function sanitizeInitialHtmlWorkerNodeOptions(rawNodeOptions) {
1058
1179
  if (!rawNodeOptions || rawNodeOptions.trim() === "") {
1059
1180
  return "";
@@ -1248,6 +1369,18 @@ function createServerConsumerManifest(manifest) {
1248
1369
  const consumerManifest = {};
1249
1370
  const normalizeWorkerModuleId = (requestKey, rawModuleId) => {
1250
1371
  if (typeof rawModuleId === "string" && rawModuleId.trim() !== "") {
1372
+ const isStagingModuleId = rawModuleId.startsWith("./.webframez-build/") || rawModuleId.includes(`${path3.sep}.webframez-build${path3.sep}`);
1373
+ const isRuntimeBuildKey = requestKey.startsWith("file://") || requestKey.startsWith("/") && requestKey.includes(`${path3.sep}build${path3.sep}app${path3.sep}`);
1374
+ if (isStagingModuleId && isRuntimeBuildKey) {
1375
+ if (requestKey.startsWith("file://")) {
1376
+ try {
1377
+ return fileURLToPath(requestKey);
1378
+ } catch {
1379
+ return rawModuleId;
1380
+ }
1381
+ }
1382
+ return requestKey;
1383
+ }
1251
1384
  return rawModuleId;
1252
1385
  }
1253
1386
  if (typeof rawModuleId !== "number" || !Number.isFinite(rawModuleId)) {
@@ -1329,6 +1462,14 @@ function createServerConsumerManifest(manifest) {
1329
1462
  name: exportName
1330
1463
  });
1331
1464
  }
1465
+ } else if (typeof entry.id === "string" && entry.id.trim() !== "") {
1466
+ addReference(entry.id, "*", reference);
1467
+ for (const exportName of exportNames) {
1468
+ addReference(entry.id, exportName, {
1469
+ ...reference,
1470
+ name: exportName
1471
+ });
1472
+ }
1332
1473
  }
1333
1474
  }
1334
1475
  return consumerManifest;
@@ -1443,24 +1584,49 @@ function createNodeRequestHandler(options) {
1443
1584
  if (url.pathname.startsWith(assetsPrefix)) {
1444
1585
  const relative = url.pathname.slice(assetsPrefix.length);
1445
1586
  const filePath = path3.resolve(distRootDir, relative);
1446
- if (!filePath.startsWith(distRootDir)) {
1587
+ if (!isWithinDirectory(filePath, distRootDir)) {
1447
1588
  res.statusCode = 400;
1448
1589
  res.end("Invalid path");
1449
1590
  return;
1450
1591
  }
1451
1592
  const ext = path3.extname(filePath);
1452
- if (ext === ".js" || ext === ".mjs") {
1453
- res.setHeader("Content-Type", "text/javascript; charset=utf-8");
1454
- } else if (ext === ".json") {
1455
- res.setHeader("Content-Type", "application/json; charset=utf-8");
1593
+ let stat;
1594
+ try {
1595
+ stat = fs2.statSync(filePath);
1596
+ if (!stat.isFile()) {
1597
+ throw new Error("Asset path is not a file");
1598
+ }
1599
+ } catch {
1600
+ res.statusCode = 404;
1601
+ res.end("Not found");
1602
+ return;
1456
1603
  }
1457
- res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
1604
+ setAssetContentType(res, ext);
1605
+ const isDevelopmentAsset = nodeEnv !== "production";
1606
+ const isVersionedClientAsset = path3.basename(filePath) === "client.js" && url.searchParams.has("v");
1607
+ const canCacheLongTerm = !isDevelopmentAsset && (isHashedAssetPath(filePath) || isVersionedClientAsset);
1608
+ res.setHeader(
1609
+ "Cache-Control",
1610
+ canCacheLongTerm ? "public, max-age=31536000, immutable" : "no-store, no-cache, must-revalidate, proxy-revalidate"
1611
+ );
1612
+ res.setHeader("Vary", "Accept-Encoding");
1613
+ const contentEncoding = !isDevelopmentAsset ? getPreferredContentEncoding(req, ext, stat.size) : "";
1458
1614
  const stream = fs2.createReadStream(filePath);
1459
1615
  stream.on("error", () => {
1460
- res.statusCode = 404;
1616
+ if (!res.headersSent) {
1617
+ res.statusCode = 404;
1618
+ }
1461
1619
  res.end("Not found");
1462
1620
  });
1463
- stream.pipe(res);
1621
+ if (contentEncoding === "br") {
1622
+ res.setHeader("Content-Encoding", "br");
1623
+ stream.pipe(createBrotliCompress()).pipe(res);
1624
+ } else if (contentEncoding === "gzip") {
1625
+ res.setHeader("Content-Encoding", "gzip");
1626
+ stream.pipe(createGzip()).pipe(res);
1627
+ } else {
1628
+ stream.pipe(res);
1629
+ }
1464
1630
  return;
1465
1631
  }
1466
1632
  const resolved = await withRequestBasename(
@@ -1478,22 +1644,23 @@ function createNodeRequestHandler(options) {
1478
1644
  );
1479
1645
  const initialPayload = {
1480
1646
  model: resolved.model,
1481
- contextModel: resolved.contextModel,
1482
- pageModel: resolved.pageModel,
1483
1647
  head: resolved.head
1484
1648
  };
1485
1649
  const initialFlightData = await renderRSCToString(initialPayload, {
1486
1650
  moduleMap
1487
1651
  });
1488
1652
  const transportBasePath = normalizeRuntimeBasePath(resolved.head.transportBasePath) ?? normalizeRuntimeBasePath(basePath) ?? "";
1489
- const shellClientScriptUrl = joinRuntimeBasePath(
1490
- transportBasePath,
1491
- "/assets/client.js"
1653
+ const shellClientScriptUrl = appendAssetVersion(
1654
+ joinRuntimeAssetPath(
1655
+ transportBasePath,
1656
+ clientScriptUrl
1657
+ ),
1658
+ createClientAssetVersion(distRootDir)
1492
1659
  );
1493
1660
  const shellRscEndpoint = joinRuntimeBasePath(transportBasePath, "/rsc");
1494
1661
  const shellBasename = normalizeRuntimeBasePath(resolved.head.basename) ?? normalizeRuntimeBasePath(basePath) ?? "";
1495
1662
  const shellRouteBasePath = normalizeRuntimeBasePath(resolved.head.routeBasePath) ?? "";
1496
- let rootHtml = "";
1663
+ let rootHtml;
1497
1664
  try {
1498
1665
  rootHtml = await initialHtmlWorker.renderFromFlightData({
1499
1666
  flightData: initialFlightData,
@@ -1517,29 +1684,23 @@ function createNodeRequestHandler(options) {
1517
1684
  });
1518
1685
  } catch (flightRenderError) {
1519
1686
  console.error("[webframez-react] Flight-to-HTML render failed", flightRenderError);
1520
- res.statusCode = 500;
1521
- res.setHeader("Content-Type", "text/html");
1522
- res.end(
1523
- createHTMLShell({
1524
- title: "500 - Initial HTML render failed",
1525
- headTags: "",
1526
- clientScriptUrl: shellClientScriptUrl,
1527
- rscEndpoint: shellRscEndpoint,
1528
- rootHtml: createInitialHtmlErrorMarkup("Initial HTML render failed."),
1529
- initialFlightData,
1530
- basename: shellBasename,
1531
- routeBasePath: shellRouteBasePath,
1532
- liveReloadPath: liveReloadPath || void 0,
1533
- liveReloadServerId: liveReloadPath ? devServerId : void 0
1534
- })
1687
+ sendTextResponse(
1688
+ req,
1689
+ res,
1690
+ createInitialHtmlErrorMarkup("Failed to render initial React HTML."),
1691
+ {
1692
+ statusCode: 500,
1693
+ contentType: "text/html; charset=utf-8",
1694
+ compress: nodeEnv === "production"
1695
+ }
1535
1696
  );
1536
1697
  return;
1537
1698
  }
1538
1699
  }
1539
1700
  }
1540
- res.statusCode = resolved.statusCode;
1541
- res.setHeader("Content-Type", "text/html");
1542
- res.end(
1701
+ sendTextResponse(
1702
+ req,
1703
+ res,
1543
1704
  createHTMLShell({
1544
1705
  title: resolved.head.title || "Webframez React",
1545
1706
  headTags: renderHeadToString(resolved.head),
@@ -1551,7 +1712,13 @@ function createNodeRequestHandler(options) {
1551
1712
  routeBasePath: shellRouteBasePath,
1552
1713
  liveReloadPath: liveReloadPath || void 0,
1553
1714
  liveReloadServerId: liveReloadPath ? devServerId : void 0
1554
- })
1715
+ }),
1716
+ {
1717
+ statusCode: resolved.statusCode,
1718
+ contentType: "text/html; charset=utf-8",
1719
+ cacheControl: "no-store, no-cache, must-revalidate, proxy-revalidate",
1720
+ compress: nodeEnv === "production"
1721
+ }
1555
1722
  );
1556
1723
  };
1557
1724
  }