vite-react-ssg 0.6.2 → 0.7.0-beta.1

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/node.cjs CHANGED
@@ -6,12 +6,15 @@ const kolorist = require('kolorist');
6
6
  const fs = require('fs-extra');
7
7
  const vite = require('vite');
8
8
  const JSDOM = require('jsdom');
9
- const state = require('./shared/vite-react-ssg.e6991406.cjs');
9
+ const state = require('./shared/vite-react-ssg.bee8a5a9.cjs');
10
+ const remixRouter = require('./shared/vite-react-ssg.0408f7e1.cjs');
10
11
  const node_fs = require('node:fs');
11
12
  const React = require('react');
12
13
  const reactHelmetAsync = require('react-helmet-async');
13
14
  const node_stream = require('node:stream');
14
15
  const server = require('react-dom/server');
16
+ const reactRouterDom = require('react-router-dom');
17
+ const node_events = require('node:events');
15
18
 
16
19
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
17
20
 
@@ -919,23 +922,6 @@ function createRequest(path) {
919
922
  url.pathname = path;
920
923
  return new Request(url.href);
921
924
  }
922
- function joinUrlSegments(a, b) {
923
- if (!a || !b)
924
- return a || b || "";
925
- if (a[a.length - 1] === "/")
926
- a = a.substring(0, a.length - 1);
927
- if (b[0] !== "/")
928
- b = `/${b}`;
929
- return a + b;
930
- }
931
- function removeLeadingSlash(str) {
932
- return str[0] === "/" ? str.slice(1) : str;
933
- }
934
- function withTrailingSlash(path) {
935
- if (path[path.length - 1] !== "/")
936
- return `${path}/`;
937
- return path;
938
- }
939
925
  const dynamicRE = /[:*?]/;
940
926
  function isDynamicSegmentsRoute(route) {
941
927
  if (!route)
@@ -1050,6 +1036,7 @@ function extractHelmet(context, styleCollector) {
1050
1036
  return { htmlAttributes, bodyAttributes, metaAttributes, styleTag };
1051
1037
  }
1052
1038
 
1039
+ const SCRIPT_COMMENT_PLACEHOLDER = "/* SCRIPT_COMMENT_PLACEHOLDER */";
1053
1040
  async function renderHTML({
1054
1041
  rootContainerId,
1055
1042
  indexHTML,
@@ -1061,6 +1048,8 @@ async function renderHTML({
1061
1048
  }) {
1062
1049
  const stateScript = initialState ? `
1063
1050
  <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
1051
+ const scriptPlaceHolder = `
1052
+ <script>${SCRIPT_COMMENT_PLACEHOLDER}<\/script>`;
1064
1053
  const headStartTag = "<head>";
1065
1054
  const metaTags = metaAttributes.join("");
1066
1055
  indexHTML = indexHTML.replace(headStartTag, headStartTag + metaTags);
@@ -1072,7 +1061,7 @@ async function renderHTML({
1072
1061
  if (indexHTML.includes(container)) {
1073
1062
  return indexHTML.replace(
1074
1063
  container,
1075
- `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}`
1064
+ `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}${scriptPlaceHolder}`
1076
1065
  );
1077
1066
  }
1078
1067
  const html5Parser = await import('html5parser');
@@ -1093,7 +1082,7 @@ async function renderHTML({
1093
1082
  return renderedOutput;
1094
1083
  }
1095
1084
  async function detectEntry(root) {
1096
- const scriptSrcReg = /<script(?:.*?)src=["'](.+?)["'](?!<)(?:.*)\>(?:[\n\r\s]*?)(?:<\/script>)/img;
1085
+ const scriptSrcReg = /<script(?:.*?)src=["'](.+?)["'](?!<)(?:.*)\>(?:[\n\r\s]*?)(?:<\/script>)/gim;
1097
1086
  const html = await fs__default.readFile(node_path.join(root, "index.html"), "utf-8");
1098
1087
  const scripts = [...html.matchAll(scriptSrcReg)];
1099
1088
  const [, entry] = scripts.find((matchResult) => {
@@ -1166,7 +1155,8 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1166
1155
  const config = await vite.resolveConfig(viteConfig, "build", mode, mode);
1167
1156
  const cwd = process.cwd();
1168
1157
  const root = config.root || cwd;
1169
- const ssgOut = node_path.join(root, ".vite-react-ssg-temp", Math.random().toString(36).substring(2, 12));
1158
+ const hash = Math.random().toString(36).substring(2, 12);
1159
+ const ssgOut = node_path.join(root, ".vite-react-ssg-temp", hash);
1170
1160
  const outDir = config.build.outDir || "dist";
1171
1161
  const out = node_path.isAbsolute(outDir) ? outDir : node_path.join(root, outDir);
1172
1162
  const configBase = config.base;
@@ -1205,7 +1195,8 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1205
1195
  }
1206
1196
  }
1207
1197
  },
1208
- mode: config.mode
1198
+ mode: config.mode,
1199
+ ssr: { noExternal: ["vite-react-ssg"] }
1209
1200
  }));
1210
1201
  if (mock) {
1211
1202
  const { jsdomGlobal } = await import('./chunks/jsdomGlobal.cjs');
@@ -1230,7 +1221,8 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1230
1221
  }
1231
1222
  }
1232
1223
  },
1233
- mode: config.mode
1224
+ mode: config.mode,
1225
+ ssr: { noExternal: ["vite-react-ssg"] }
1234
1226
  }));
1235
1227
  const prefix = format === "esm" && process.platform === "win32" ? "file://" : "";
1236
1228
  const ext = format === "esm" ? ".mjs" : ".cjs";
@@ -1254,6 +1246,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1254
1246
  indexHTML = rewriteScripts(indexHTML, script);
1255
1247
  const queue = new PQueue({ concurrency });
1256
1248
  const crittersQueue = new PQueue({ concurrency: 1 });
1249
+ const staticLoaderDataManifest = {};
1257
1250
  for (const path of routesPaths) {
1258
1251
  queue.add(async () => {
1259
1252
  try {
@@ -1261,9 +1254,10 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1261
1254
  const { initialState, base, routes: routes2, triggerOnSSRAppRendered, transformState = state.serializeState, getStyleCollector, app } = appCtx;
1262
1255
  const styleCollector = getStyleCollector ? await getStyleCollector() : null;
1263
1256
  const transformedIndexHTML = await onBeforePageRender?.(path, indexHTML, appCtx) || indexHTML;
1264
- const fetchUrl = `${withTrailingSlash(base)}${removeLeadingSlash(path)}`;
1257
+ const fetchUrl = `${remixRouter.withTrailingSlash(base)}${remixRouter.removeLeadingSlash(path)}`;
1265
1258
  const request = createRequest(fetchUrl);
1266
- const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag } = await render(app ?? [...routes2], request, styleCollector, base);
1259
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await render(app ?? [...routes2], request, styleCollector, base);
1260
+ staticLoaderDataManifest[path] = routerContext?.loaderData;
1267
1261
  await triggerOnSSRAppRendered?.(path, appHTML, appCtx);
1268
1262
  const renderedHTML = await renderHTML({
1269
1263
  rootContainerId,
@@ -1279,6 +1273,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1279
1273
  renderPreloadLinks(jsdom.window.document, modules, ssrManifest);
1280
1274
  const html = jsdom.serialize();
1281
1275
  let transformed = await onPageRendered?.(path, html, appCtx) || html;
1276
+ transformed = transformed.replace(SCRIPT_COMMENT_PLACEHOLDER, `window.__VITE_REACT_SSG_HASH__ = '${hash}'`);
1282
1277
  if (critters) {
1283
1278
  transformed = await crittersQueue.add(() => critters.process(transformed));
1284
1279
  transformed = transformed.replace(/<link\srel="stylesheet"/g, '<link rel="stylesheet" crossorigin');
@@ -1300,6 +1295,7 @@ ${err.stack}`);
1300
1295
  });
1301
1296
  }
1302
1297
  await queue.start().onIdle();
1298
+ await fs__default.writeFile(node_path.join(out, `static-loader-data-manifest-${hash}.json`), JSON.stringify(staticLoaderDataManifest, null, 2));
1303
1299
  await fs__default.remove(node_path.join(root, ".vite-react-ssg-temp"));
1304
1300
  const pwaPlugin = config.plugins.find((i) => i.name === "vite-plugin-pwa")?.api;
1305
1301
  if (pwaPlugin && !pwaPlugin.disabled && pwaPlugin.generateSW) {
@@ -1397,6 +1393,201 @@ function fromNodeRequest(nodeReq) {
1397
1393
  };
1398
1394
  return new Request(url.href, init);
1399
1395
  }
1396
+ async function toNodeRequest(res, nodeRes) {
1397
+ nodeRes.statusCode = res.status;
1398
+ nodeRes.statusMessage = res.statusText;
1399
+ for (const [name, value] of res.headers) {
1400
+ nodeRes.setHeader(name, value);
1401
+ }
1402
+ if (res.body) {
1403
+ const responseBody = res.body;
1404
+ const readable = node_stream.Readable.from(responseBody);
1405
+ readable.pipe(nodeRes);
1406
+ await node_events.once(readable, "end");
1407
+ } else {
1408
+ nodeRes.end();
1409
+ }
1410
+ }
1411
+
1412
+ function ssrServerPlugin({
1413
+ template,
1414
+ ssrEntry,
1415
+ onBeforePageRender,
1416
+ entry,
1417
+ rootContainerId,
1418
+ onPageRendered
1419
+ }) {
1420
+ return {
1421
+ name: "vite-react-ssg:dev-server-remix",
1422
+ configureServer(server) {
1423
+ const renderMiddleware = async (req, res, _next) => {
1424
+ try {
1425
+ const url = req.originalUrl;
1426
+ const createRoot = await server.ssrLoadModule(ssrEntry).then((m) => m.createRoot);
1427
+ const appCtx = await createRoot(false, url);
1428
+ const { routes, getStyleCollector, base, app } = appCtx;
1429
+ const searchParams = new URLSearchParams(url.split("?")[1]);
1430
+ if (!app && searchParams.has("_data")) {
1431
+ const request = fromNodeRequest(req);
1432
+ const url2 = new URL(request.url);
1433
+ const routeId = decodeURIComponent(searchParams.get("_data"));
1434
+ const matches = reactRouterDom.matchRoutes(
1435
+ remixRouter.convertRoutesToDataRoutes([...routes], (route) => route),
1436
+ {
1437
+ pathname: url2.pathname,
1438
+ search: url2.search,
1439
+ hash: url2.hash,
1440
+ state: null,
1441
+ key: "default"
1442
+ },
1443
+ base
1444
+ );
1445
+ if (!matches) {
1446
+ res.statusCode = 404;
1447
+ res.end(`Route not found: ${routeId}`);
1448
+ return;
1449
+ }
1450
+ const match = matches.find((m) => m.route.id === routeId);
1451
+ if (!match) {
1452
+ res.statusCode = 404;
1453
+ res.end(`Route not found: ${routeId}`);
1454
+ return;
1455
+ }
1456
+ const loader = match.route.loader ?? await match.route.lazy?.().then((m) => m.loader);
1457
+ if (!loader) {
1458
+ res.statusCode = 200;
1459
+ res.end(`There is no loader for the route: ${routeId}`);
1460
+ return;
1461
+ }
1462
+ const response = await callRouteLoader({
1463
+ loader,
1464
+ params: match.params,
1465
+ request,
1466
+ routeId
1467
+ });
1468
+ await toNodeRequest(response, res);
1469
+ return;
1470
+ }
1471
+ const indexHTML = await server.transformIndexHtml(url, template);
1472
+ const transformedIndexHTML = await onBeforePageRender?.(url, indexHTML, appCtx) || indexHTML;
1473
+ const styleCollector = getStyleCollector ? await getStyleCollector() : null;
1474
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await render(app ?? [...routes], fromNodeRequest(req), styleCollector, base);
1475
+ metaAttributes.push(styleTag);
1476
+ const matchesEntries = routerContext?.matches.map((match) => match.route.entry).filter((entry2) => !!entry2).map((entry2) => entry2[0] === "/" ? entry2 : `/${entry2}`) ?? [];
1477
+ const mods = await Promise.all(
1478
+ [ssrEntry, entry, ...matchesEntries].map(async (entry2) => await server.moduleGraph.getModuleByUrl(entry2))
1479
+ );
1480
+ const assetsUrls = /* @__PURE__ */ new Set();
1481
+ const collectAssets = async (mod) => {
1482
+ if (!mod || !mod?.ssrTransformResult)
1483
+ return;
1484
+ const { deps = [], dynamicDeps = [] } = mod?.ssrTransformResult;
1485
+ const allDeps = [...deps, ...dynamicDeps];
1486
+ for (const dep of allDeps) {
1487
+ if (dep.endsWith(".css")) {
1488
+ assetsUrls.add(dep);
1489
+ } else if (dep.endsWith(".ts") || dep.endsWith(".tsx")) {
1490
+ const depModule = await server.moduleGraph.getModuleByUrl(dep);
1491
+ depModule && await collectAssets(depModule);
1492
+ }
1493
+ }
1494
+ };
1495
+ await Promise.all(mods.map(async (mod) => collectAssets(mod)));
1496
+ const preloadLink = [...assetsUrls].map((item) => createLink$1(remixRouter.joinUrlSegments(server.config.base, item)));
1497
+ metaAttributes.push(...preloadLink);
1498
+ const renderedHTML = await renderHTML({
1499
+ rootContainerId,
1500
+ appHTML,
1501
+ indexHTML: transformedIndexHTML,
1502
+ metaAttributes,
1503
+ bodyAttributes,
1504
+ htmlAttributes,
1505
+ initialState: null
1506
+ });
1507
+ const transformed = await onPageRendered?.(url, renderedHTML, appCtx) || renderedHTML;
1508
+ res.statusCode = 200;
1509
+ res.setHeader("Content-Type", "text/html");
1510
+ const isDev = "pluginContainer" in server;
1511
+ const headers = isDev ? server.config.server.headers : server.config.preview.headers;
1512
+ vite.send(req, res, transformed, "html", { headers });
1513
+ } catch (e) {
1514
+ server.ssrFixStacktrace(e);
1515
+ console.error(`[vite-react-ssg] error: ${e.stack}`);
1516
+ res.statusCode = 500;
1517
+ res.end(e.stack);
1518
+ }
1519
+ };
1520
+ return () => {
1521
+ server.middlewares.use(renderMiddleware);
1522
+ };
1523
+ }
1524
+ };
1525
+ }
1526
+ async function callRouteLoader({
1527
+ // loadContext,
1528
+ loader,
1529
+ params,
1530
+ request,
1531
+ routeId
1532
+ // singleFetch,
1533
+ // response,
1534
+ }) {
1535
+ const result = await loader({
1536
+ request: stripDataParam(stripIndexParam(request)),
1537
+ // context: loadContext,
1538
+ params
1539
+ // Only provided when single fetch is enabled, and made available via
1540
+ // `defineLoader` types, not `LoaderFunctionArgs`
1541
+ // ...(singleFetch ? { response } : null),
1542
+ });
1543
+ if (result === void 0) {
1544
+ throw new Error(
1545
+ `You defined a loader for route "${routeId}" but didn't return anything from your \`loader\` function. Please return a value or \`null\`.`
1546
+ );
1547
+ }
1548
+ return isResponse(result) ? result : reactRouterDom.json(result);
1549
+ }
1550
+ function isResponse(value) {
1551
+ return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
1552
+ }
1553
+ function stripIndexParam(request) {
1554
+ const url = new URL(request.url);
1555
+ const indexValues = url.searchParams.getAll("index");
1556
+ url.searchParams.delete("index");
1557
+ const indexValuesToKeep = [];
1558
+ for (const indexValue of indexValues) {
1559
+ if (indexValue) {
1560
+ indexValuesToKeep.push(indexValue);
1561
+ }
1562
+ }
1563
+ for (const toKeep of indexValuesToKeep) {
1564
+ url.searchParams.append("index", toKeep);
1565
+ }
1566
+ const init = {
1567
+ method: request.method,
1568
+ body: request.body,
1569
+ headers: request.headers,
1570
+ signal: request.signal
1571
+ };
1572
+ if (init.body) {
1573
+ init.duplex = "half";
1574
+ }
1575
+ return new Request(url.href, init);
1576
+ }
1577
+ function stripDataParam(request) {
1578
+ const url = new URL(request.url);
1579
+ url.searchParams.delete("_data");
1580
+ const init = {
1581
+ method: request.method,
1582
+ body: request.body,
1583
+ headers: request.headers,
1584
+ signal: request.signal
1585
+ };
1586
+ if (init.body) {
1587
+ init.duplex = "half";
1588
+ }
1589
+ return new Request(url.href, init);
1590
+ }
1400
1591
 
1401
1592
  async function dev(ssgOptions = {}, viteConfig = {}, customOptions) {
1402
1593
  const mode = process.env.MODE || process.env.NODE_ENV || ssgOptions.mode || "development";
@@ -1431,66 +1622,14 @@ ${err.stack}`
1431
1622
  ...viteConfig,
1432
1623
  plugins: [
1433
1624
  ...viteConfig.plugins ?? [],
1434
- {
1435
- name: "vite-react-ssg:dev-server-remix",
1436
- configureServer(server) {
1437
- return () => {
1438
- server.middlewares.use(async (req, res, _next) => {
1439
- try {
1440
- const url = req.originalUrl;
1441
- const indexHTML = await viteServer.transformIndexHtml(url, template);
1442
- const createRoot = await viteServer.ssrLoadModule(ssrEntry).then((m) => m.createRoot);
1443
- const appCtx = await createRoot(false, url);
1444
- const { routes, getStyleCollector, base, app } = appCtx;
1445
- const transformedIndexHTML = await onBeforePageRender?.(url, indexHTML, appCtx) || indexHTML;
1446
- const styleCollector = getStyleCollector ? await getStyleCollector() : null;
1447
- const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await render(app ?? [...routes], fromNodeRequest(req), styleCollector, base);
1448
- metaAttributes.push(styleTag);
1449
- const matchesEntries = routerContext?.matches.map((match) => match.route.entry).filter((entry2) => !!entry2).map((entry2) => entry2[0] === "/" ? entry2 : `/${entry2}`) ?? [];
1450
- const mods = await Promise.all(
1451
- [ssrEntry, entry, ...matchesEntries].map(async (entry2) => await viteServer.moduleGraph.getModuleByUrl(entry2))
1452
- );
1453
- const assetsUrls = /* @__PURE__ */ new Set();
1454
- const collectAssets = async (mod) => {
1455
- if (!mod || !mod?.ssrTransformResult)
1456
- return;
1457
- const { deps = [], dynamicDeps = [] } = mod?.ssrTransformResult;
1458
- const allDeps = [...deps, ...dynamicDeps];
1459
- for (const dep of allDeps) {
1460
- if (dep.endsWith(".css")) {
1461
- assetsUrls.add(dep);
1462
- } else if (dep.endsWith(".ts") || dep.endsWith(".tsx")) {
1463
- const depModule = await viteServer.moduleGraph.getModuleByUrl(dep);
1464
- depModule && await collectAssets(depModule);
1465
- }
1466
- }
1467
- };
1468
- await Promise.all(mods.map(async (mod) => collectAssets(mod)));
1469
- const preloadLink = [...assetsUrls].map((item) => createLink$1(joinUrlSegments(config.base, item)));
1470
- metaAttributes.push(...preloadLink);
1471
- const renderedHTML = await renderHTML({
1472
- rootContainerId,
1473
- appHTML,
1474
- indexHTML: transformedIndexHTML,
1475
- metaAttributes,
1476
- bodyAttributes,
1477
- htmlAttributes,
1478
- initialState: null
1479
- });
1480
- const transformed = await onPageRendered?.(url, renderedHTML, appCtx) || renderedHTML;
1481
- res.statusCode = 200;
1482
- res.setHeader("Content-Type", "text/html");
1483
- res.end(transformed);
1484
- } catch (e) {
1485
- viteServer.ssrFixStacktrace(e);
1486
- console.error(`[vite-react-ssg] error: ${e.stack}`);
1487
- res.statusCode = 500;
1488
- res.end(e.stack);
1489
- }
1490
- });
1491
- };
1492
- }
1493
- }
1625
+ ssrServerPlugin({
1626
+ template,
1627
+ ssrEntry,
1628
+ onBeforePageRender,
1629
+ onPageRendered,
1630
+ entry,
1631
+ rootContainerId
1632
+ })
1494
1633
  ]
1495
1634
  });
1496
1635
  await viteServer.listen();