vite-react-ssg 0.6.4 → 0.7.0-beta.2

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
@@ -6,6 +6,7 @@ const reactHelmetAsync = require('react-helmet-async');
6
6
  const reactRouterDom = require('react-router-dom');
7
7
  const ClientOnly = require('./shared/vite-react-ssg.c1d49976.cjs');
8
8
  const state = require('./shared/vite-react-ssg.bee8a5a9.cjs');
9
+ const remixRouter = require('./shared/vite-react-ssg.0408f7e1.cjs');
9
10
 
10
11
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
11
12
 
@@ -86,7 +87,7 @@ function ViteReactSSG(routerOptions, fn, options = {}) {
86
87
  const isClient = typeof window !== "undefined";
87
88
  const BASE_URL = routerOptions.basename ?? "/";
88
89
  async function createRoot(client = false, routePath) {
89
- const browserRouter = client ? reactRouterDom.createBrowserRouter(routerOptions.routes, { basename: BASE_URL }) : void 0;
90
+ const browserRouter = client ? reactRouterDom.createBrowserRouter(remixRouter.convertRoutesToDataRoutes(routerOptions.routes, transformStaticLoaderRoute), { basename: BASE_URL }) : void 0;
90
91
  const appRenderCallbacks = [];
91
92
  const onSSRAppRendered = client ? () => {
92
93
  } : (cb) => appRenderCallbacks.push(cb);
@@ -152,6 +153,34 @@ function ViteReactSSG(routerOptions, fn, options = {}) {
152
153
  })();
153
154
  }
154
155
  return createRoot;
156
+ function transformStaticLoaderRoute(route) {
157
+ const loader = async ({ request }) => {
158
+ if (undefined.DEV) {
159
+ const routeId = encodeURIComponent(route.id);
160
+ const dataQuery = `_data=${routeId}`;
161
+ const url = request.url.includes("?") ? `${request.url}&${dataQuery}` : `${request.url}?${dataQuery}`;
162
+ return fetch(url);
163
+ } else {
164
+ let staticLoadData;
165
+ if (window.__VITE_REACT_SSG_STATIC_LOADER_DATA__) {
166
+ staticLoadData = window.__VITE_REACT_SSG_STATIC_LOADER_DATA__;
167
+ } else {
168
+ const manifestUrl = remixRouter.joinUrlSegments(BASE_URL, `static-loader-data-manifest-${window.__VITE_REACT_SSG_HASH__}.json`);
169
+ staticLoadData = await (await fetch(remixRouter.withLeadingSlash(manifestUrl))).json();
170
+ window.__VITE_REACT_SSG_STATIC_LOADER_DATA__ = staticLoadData;
171
+ }
172
+ const { url } = request;
173
+ let { pathname } = new URL(url);
174
+ if (BASE_URL !== "/") {
175
+ pathname = remixRouter.stripBase(pathname, BASE_URL);
176
+ }
177
+ const routeData = staticLoadData?.[pathname]?.[route.id];
178
+ return routeData ?? null;
179
+ }
180
+ };
181
+ route.loader = loader;
182
+ return route;
183
+ }
155
184
  }
156
185
 
157
186
  exports.ClientOnly = ClientOnly.ClientOnly;
package/dist/index.d.cts CHANGED
@@ -10,5 +10,11 @@ declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttribu
10
10
  declare const NavLink: React.ForwardRefExoticComponent<NavLinkProps & React.RefAttributes<HTMLAnchorElement>>;
11
11
 
12
12
  declare function ViteReactSSG(routerOptions: RouterOptions, fn?: (context: ViteReactSSGContext<true>) => Promise<void> | void, options?: ViteReactSSGClientOptions): (client?: boolean, routePath?: string) => Promise<ViteReactSSGContext<true>>;
13
+ declare global {
14
+ interface Window {
15
+ __VITE_REACT_SSG_STATIC_LOADER_DATA__: any;
16
+ __VITE_REACT_SSG_HASH__: string;
17
+ }
18
+ }
13
19
 
14
20
  export { Link, NavLink, RouterOptions, ViteReactSSG, ViteReactSSGClientOptions, ViteReactSSGContext };
package/dist/index.d.mts CHANGED
@@ -10,5 +10,11 @@ declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttribu
10
10
  declare const NavLink: React.ForwardRefExoticComponent<NavLinkProps & React.RefAttributes<HTMLAnchorElement>>;
11
11
 
12
12
  declare function ViteReactSSG(routerOptions: RouterOptions, fn?: (context: ViteReactSSGContext<true>) => Promise<void> | void, options?: ViteReactSSGClientOptions): (client?: boolean, routePath?: string) => Promise<ViteReactSSGContext<true>>;
13
+ declare global {
14
+ interface Window {
15
+ __VITE_REACT_SSG_STATIC_LOADER_DATA__: any;
16
+ __VITE_REACT_SSG_HASH__: string;
17
+ }
18
+ }
13
19
 
14
20
  export { Link, NavLink, RouterOptions, ViteReactSSG, ViteReactSSGClientOptions, ViteReactSSGContext };
package/dist/index.d.ts CHANGED
@@ -10,5 +10,11 @@ declare const Link: React.ForwardRefExoticComponent<LinkProps & React.RefAttribu
10
10
  declare const NavLink: React.ForwardRefExoticComponent<NavLinkProps & React.RefAttributes<HTMLAnchorElement>>;
11
11
 
12
12
  declare function ViteReactSSG(routerOptions: RouterOptions, fn?: (context: ViteReactSSGContext<true>) => Promise<void> | void, options?: ViteReactSSGClientOptions): (client?: boolean, routePath?: string) => Promise<ViteReactSSGContext<true>>;
13
+ declare global {
14
+ interface Window {
15
+ __VITE_REACT_SSG_STATIC_LOADER_DATA__: any;
16
+ __VITE_REACT_SSG_HASH__: string;
17
+ }
18
+ }
13
19
 
14
20
  export { Link, NavLink, RouterOptions, ViteReactSSG, ViteReactSSGClientOptions, ViteReactSSGContext };
package/dist/index.mjs CHANGED
@@ -5,6 +5,7 @@ import { useLinkClickHandler, Link as Link$1, NavLink as NavLink$1, matchRoutes,
5
5
  import { d as documentReady } from './shared/vite-react-ssg.a292c181.mjs';
6
6
  export { C as ClientOnly, H as Head } from './shared/vite-react-ssg.a292c181.mjs';
7
7
  import { d as deserializeState } from './shared/vite-react-ssg.579feabb.mjs';
8
+ import { c as convertRoutesToDataRoutes, j as joinUrlSegments, w as withLeadingSlash, s as stripBase } from './shared/vite-react-ssg.054e813a.mjs';
8
9
 
9
10
  const Link = forwardRef((props, ref) => {
10
11
  const {
@@ -81,7 +82,7 @@ function ViteReactSSG(routerOptions, fn, options = {}) {
81
82
  const isClient = typeof window !== "undefined";
82
83
  const BASE_URL = routerOptions.basename ?? "/";
83
84
  async function createRoot$1(client = false, routePath) {
84
- const browserRouter = client ? createBrowserRouter(routerOptions.routes, { basename: BASE_URL }) : void 0;
85
+ const browserRouter = client ? createBrowserRouter(convertRoutesToDataRoutes(routerOptions.routes, transformStaticLoaderRoute), { basename: BASE_URL }) : void 0;
85
86
  const appRenderCallbacks = [];
86
87
  const onSSRAppRendered = client ? () => {
87
88
  } : (cb) => appRenderCallbacks.push(cb);
@@ -147,6 +148,34 @@ function ViteReactSSG(routerOptions, fn, options = {}) {
147
148
  })();
148
149
  }
149
150
  return createRoot$1;
151
+ function transformStaticLoaderRoute(route) {
152
+ const loader = async ({ request }) => {
153
+ if (import.meta.env.DEV) {
154
+ const routeId = encodeURIComponent(route.id);
155
+ const dataQuery = `_data=${routeId}`;
156
+ const url = request.url.includes("?") ? `${request.url}&${dataQuery}` : `${request.url}?${dataQuery}`;
157
+ return fetch(url);
158
+ } else {
159
+ let staticLoadData;
160
+ if (window.__VITE_REACT_SSG_STATIC_LOADER_DATA__) {
161
+ staticLoadData = window.__VITE_REACT_SSG_STATIC_LOADER_DATA__;
162
+ } else {
163
+ const manifestUrl = joinUrlSegments(BASE_URL, `static-loader-data-manifest-${window.__VITE_REACT_SSG_HASH__}.json`);
164
+ staticLoadData = await (await fetch(withLeadingSlash(manifestUrl))).json();
165
+ window.__VITE_REACT_SSG_STATIC_LOADER_DATA__ = staticLoadData;
166
+ }
167
+ const { url } = request;
168
+ let { pathname } = new URL(url);
169
+ if (BASE_URL !== "/") {
170
+ pathname = stripBase(pathname, BASE_URL);
171
+ }
172
+ const routeData = staticLoadData?.[pathname]?.[route.id];
173
+ return routeData ?? null;
174
+ }
175
+ };
176
+ route.loader = loader;
177
+ return route;
178
+ }
150
179
  }
151
180
 
152
181
  export { Link, NavLink, ViteReactSSG };
package/dist/node/cli.cjs CHANGED
@@ -11,11 +11,14 @@ require('fs-extra');
11
11
  require('vite');
12
12
  require('jsdom');
13
13
  require('../shared/vite-react-ssg.bee8a5a9.cjs');
14
+ require('../shared/vite-react-ssg.0408f7e1.cjs');
14
15
  require('node:fs');
15
16
  require('react');
16
17
  require('react-helmet-async');
17
18
  require('node:stream');
18
19
  require('react-dom/server');
20
+ require('react-router-dom');
21
+ require('node:events');
19
22
 
20
23
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
21
24
 
package/dist/node/cli.mjs CHANGED
@@ -9,11 +9,14 @@ import 'fs-extra';
9
9
  import 'vite';
10
10
  import 'jsdom';
11
11
  import '../shared/vite-react-ssg.579feabb.mjs';
12
+ import '../shared/vite-react-ssg.054e813a.mjs';
12
13
  import 'node:fs';
13
14
  import 'react';
14
15
  import 'react-helmet-async';
15
16
  import 'node:stream';
16
17
  import 'react-dom/server';
18
+ import 'react-router-dom';
19
+ import 'node:events';
17
20
 
18
21
  function installGlobals() {
19
22
  global.Headers = Headers;
package/dist/node.cjs CHANGED
@@ -7,11 +7,14 @@ const fs = require('fs-extra');
7
7
  const vite = require('vite');
8
8
  const JSDOM = require('jsdom');
9
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
 
@@ -885,6 +888,10 @@ async function routesToPaths(routes) {
885
888
  }
886
889
  if (route.index)
887
890
  addEntry(prefix, route.entry);
891
+ if (route.index && !path) {
892
+ addEntry("/", route.entry);
893
+ paths.add("/");
894
+ }
888
895
  if (Array.isArray(route.children))
889
896
  await getPaths(route.children, path);
890
897
  }
@@ -919,23 +926,6 @@ function createRequest(path) {
919
926
  url.pathname = path;
920
927
  return new Request(url.href);
921
928
  }
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
929
  const dynamicRE = /[:*?]/;
940
930
  function isDynamicSegmentsRoute(route) {
941
931
  if (!route)
@@ -1050,6 +1040,7 @@ function extractHelmet(context, styleCollector) {
1050
1040
  return { htmlAttributes, bodyAttributes, metaAttributes, styleTag };
1051
1041
  }
1052
1042
 
1043
+ const SCRIPT_COMMENT_PLACEHOLDER = "/* SCRIPT_COMMENT_PLACEHOLDER */";
1053
1044
  async function renderHTML({
1054
1045
  rootContainerId,
1055
1046
  indexHTML,
@@ -1061,6 +1052,8 @@ async function renderHTML({
1061
1052
  }) {
1062
1053
  const stateScript = initialState ? `
1063
1054
  <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
1055
+ const scriptPlaceHolder = `
1056
+ <script>${SCRIPT_COMMENT_PLACEHOLDER}<\/script>`;
1064
1057
  const headStartTag = "<head>";
1065
1058
  const metaTags = metaAttributes.join("");
1066
1059
  indexHTML = indexHTML.replace(headStartTag, headStartTag + metaTags);
@@ -1072,7 +1065,7 @@ async function renderHTML({
1072
1065
  if (indexHTML.includes(container)) {
1073
1066
  return indexHTML.replace(
1074
1067
  container,
1075
- `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}`
1068
+ `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}${scriptPlaceHolder}`
1076
1069
  );
1077
1070
  }
1078
1071
  const html5Parser = await import('html5parser');
@@ -1166,7 +1159,8 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1166
1159
  const config = await vite.resolveConfig(viteConfig, "build", mode, mode);
1167
1160
  const cwd = process.cwd();
1168
1161
  const root = config.root || cwd;
1169
- const ssgOut = node_path.join(root, ".vite-react-ssg-temp", Math.random().toString(36).substring(2, 12));
1162
+ const hash = Math.random().toString(36).substring(2, 12);
1163
+ const ssgOut = node_path.join(root, ".vite-react-ssg-temp", hash);
1170
1164
  const outDir = config.build.outDir || "dist";
1171
1165
  const out = node_path.isAbsolute(outDir) ? outDir : node_path.join(root, outDir);
1172
1166
  const configBase = config.base;
@@ -1188,6 +1182,13 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1188
1182
  } = Object.assign({}, config.ssgOptions || {}, ssgOptions);
1189
1183
  if (fs__default.existsSync(ssgOut))
1190
1184
  await fs__default.remove(ssgOut);
1185
+ const clientLogger = vite.createLogger();
1186
+ const loggerWarn = clientLogger.warn;
1187
+ clientLogger.warn = (msg, options) => {
1188
+ if (msg.includes("vite:resolve") && msg.includes("externalized for browser compatibility"))
1189
+ return;
1190
+ loggerWarn(msg, options);
1191
+ };
1191
1192
  buildLog("Build for client...");
1192
1193
  await vite.build(vite.mergeConfig(viteConfig, {
1193
1194
  build: {
@@ -1205,7 +1206,9 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1205
1206
  }
1206
1207
  }
1207
1208
  },
1208
- mode: config.mode
1209
+ customLogger: clientLogger,
1210
+ mode: config.mode,
1211
+ ssr: { noExternal: ["vite-react-ssg"] }
1209
1212
  }));
1210
1213
  if (mock) {
1211
1214
  const { jsdomGlobal } = await import('./chunks/jsdomGlobal.cjs');
@@ -1230,7 +1233,8 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1230
1233
  }
1231
1234
  }
1232
1235
  },
1233
- mode: config.mode
1236
+ mode: config.mode,
1237
+ ssr: { noExternal: ["vite-react-ssg"] }
1234
1238
  }));
1235
1239
  const prefix = format === "esm" && process.platform === "win32" ? "file://" : "";
1236
1240
  const ext = format === "esm" ? ".mjs" : ".cjs";
@@ -1254,6 +1258,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1254
1258
  indexHTML = rewriteScripts(indexHTML, script);
1255
1259
  const queue = new PQueue({ concurrency });
1256
1260
  const crittersQueue = new PQueue({ concurrency: 1 });
1261
+ const staticLoaderDataManifest = {};
1257
1262
  for (const path of routesPaths) {
1258
1263
  queue.add(async () => {
1259
1264
  try {
@@ -1261,9 +1266,10 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1261
1266
  const { initialState, base, routes: routes2, triggerOnSSRAppRendered, transformState = state.serializeState, getStyleCollector, app } = appCtx;
1262
1267
  const styleCollector = getStyleCollector ? await getStyleCollector() : null;
1263
1268
  const transformedIndexHTML = await onBeforePageRender?.(path, indexHTML, appCtx) || indexHTML;
1264
- const fetchUrl = `${withTrailingSlash(base)}${removeLeadingSlash(path)}`;
1269
+ const fetchUrl = `${remixRouter.withTrailingSlash(base)}${remixRouter.removeLeadingSlash(path)}`;
1265
1270
  const request = createRequest(fetchUrl);
1266
- const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag } = await render(app ?? [...routes2], request, styleCollector, base);
1271
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await render(app ?? [...routes2], request, styleCollector, base);
1272
+ staticLoaderDataManifest[path] = routerContext?.loaderData;
1267
1273
  await triggerOnSSRAppRendered?.(path, appHTML, appCtx);
1268
1274
  const renderedHTML = await renderHTML({
1269
1275
  rootContainerId,
@@ -1279,6 +1285,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1279
1285
  renderPreloadLinks(jsdom.window.document, modules, ssrManifest);
1280
1286
  const html = jsdom.serialize();
1281
1287
  let transformed = await onPageRendered?.(path, html, appCtx) || html;
1288
+ transformed = transformed.replace(SCRIPT_COMMENT_PLACEHOLDER, `window.__VITE_REACT_SSG_HASH__ = '${hash}'`);
1282
1289
  if (critters) {
1283
1290
  transformed = await crittersQueue.add(() => critters.process(transformed));
1284
1291
  transformed = transformed.replace(/<link\srel="stylesheet"/g, '<link rel="stylesheet" crossorigin');
@@ -1300,6 +1307,12 @@ ${err.stack}`);
1300
1307
  });
1301
1308
  }
1302
1309
  await queue.start().onIdle();
1310
+ buildLog("Generating static loader data manifest...");
1311
+ const staticLoaderDataManifestString = JSON.stringify(staticLoaderDataManifest, null, 0);
1312
+ await fs__default.writeFile(node_path.join(out, `static-loader-data-manifest-${hash}.json`), staticLoaderDataManifestString);
1313
+ config.logger.info(
1314
+ `${kolorist.dim(`${outDir}/`)}${kolorist.cyan(`static-loader-data-manifest-${hash}.json`.padEnd(15, " "))} ${kolorist.dim(getSize(staticLoaderDataManifestString))}`
1315
+ );
1303
1316
  await fs__default.remove(node_path.join(root, ".vite-react-ssg-temp"));
1304
1317
  const pwaPlugin = config.plugins.find((i) => i.name === "vite-plugin-pwa")?.api;
1305
1318
  if (pwaPlugin && !pwaPlugin.disabled && pwaPlugin.generateSW) {
@@ -1397,6 +1410,201 @@ function fromNodeRequest(nodeReq) {
1397
1410
  };
1398
1411
  return new Request(url.href, init);
1399
1412
  }
1413
+ async function toNodeRequest(res, nodeRes) {
1414
+ nodeRes.statusCode = res.status;
1415
+ nodeRes.statusMessage = res.statusText;
1416
+ for (const [name, value] of res.headers) {
1417
+ nodeRes.setHeader(name, value);
1418
+ }
1419
+ if (res.body) {
1420
+ const responseBody = res.body;
1421
+ const readable = node_stream.Readable.from(responseBody);
1422
+ readable.pipe(nodeRes);
1423
+ await node_events.once(readable, "end");
1424
+ } else {
1425
+ nodeRes.end();
1426
+ }
1427
+ }
1428
+
1429
+ function ssrServerPlugin({
1430
+ template,
1431
+ ssrEntry,
1432
+ onBeforePageRender,
1433
+ entry,
1434
+ rootContainerId,
1435
+ onPageRendered
1436
+ }) {
1437
+ return {
1438
+ name: "vite-react-ssg:dev-server-remix",
1439
+ configureServer(server) {
1440
+ const renderMiddleware = async (req, res, _next) => {
1441
+ try {
1442
+ const url = req.originalUrl;
1443
+ const createRoot = await server.ssrLoadModule(ssrEntry).then((m) => m.createRoot);
1444
+ const appCtx = await createRoot(false, url);
1445
+ const { routes, getStyleCollector, base, app } = appCtx;
1446
+ const searchParams = new URLSearchParams(url.split("?")[1]);
1447
+ if (!app && searchParams.has("_data")) {
1448
+ const request = fromNodeRequest(req);
1449
+ const url2 = new URL(request.url);
1450
+ const routeId = decodeURIComponent(searchParams.get("_data"));
1451
+ const matches = reactRouterDom.matchRoutes(
1452
+ remixRouter.convertRoutesToDataRoutes([...routes], (route) => route),
1453
+ {
1454
+ pathname: url2.pathname,
1455
+ search: url2.search,
1456
+ hash: url2.hash,
1457
+ state: null,
1458
+ key: "default"
1459
+ },
1460
+ base
1461
+ );
1462
+ if (!matches) {
1463
+ res.statusCode = 404;
1464
+ res.end(`Route not found: ${routeId}`);
1465
+ return;
1466
+ }
1467
+ const match = matches.find((m) => m.route.id === routeId);
1468
+ if (!match) {
1469
+ res.statusCode = 404;
1470
+ res.end(`Route not found: ${routeId}`);
1471
+ return;
1472
+ }
1473
+ const loader = match.route.loader ?? await match.route.lazy?.().then((m) => m.loader);
1474
+ if (!loader) {
1475
+ res.statusCode = 200;
1476
+ res.end(`There is no loader for the route: ${routeId}`);
1477
+ return;
1478
+ }
1479
+ const response = await callRouteLoader({
1480
+ loader,
1481
+ params: match.params,
1482
+ request,
1483
+ routeId
1484
+ });
1485
+ await toNodeRequest(response, res);
1486
+ return;
1487
+ }
1488
+ const indexHTML = await server.transformIndexHtml(url, template);
1489
+ const transformedIndexHTML = await onBeforePageRender?.(url, indexHTML, appCtx) || indexHTML;
1490
+ const styleCollector = getStyleCollector ? await getStyleCollector() : null;
1491
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await render(app ?? [...routes], fromNodeRequest(req), styleCollector, base);
1492
+ metaAttributes.push(styleTag);
1493
+ const matchesEntries = routerContext?.matches.map((match) => match.route.entry).filter((entry2) => !!entry2).map((entry2) => entry2[0] === "/" ? entry2 : `/${entry2}`) ?? [];
1494
+ const mods = await Promise.all(
1495
+ [ssrEntry, entry, ...matchesEntries].map(async (entry2) => await server.moduleGraph.getModuleByUrl(entry2))
1496
+ );
1497
+ const assetsUrls = /* @__PURE__ */ new Set();
1498
+ const collectAssets = async (mod) => {
1499
+ if (!mod || !mod?.ssrTransformResult)
1500
+ return;
1501
+ const { deps = [], dynamicDeps = [] } = mod?.ssrTransformResult;
1502
+ const allDeps = [...deps, ...dynamicDeps];
1503
+ for (const dep of allDeps) {
1504
+ if (dep.endsWith(".css")) {
1505
+ assetsUrls.add(dep);
1506
+ } else if (dep.endsWith(".ts") || dep.endsWith(".tsx")) {
1507
+ const depModule = await server.moduleGraph.getModuleByUrl(dep);
1508
+ depModule && await collectAssets(depModule);
1509
+ }
1510
+ }
1511
+ };
1512
+ await Promise.all(mods.map(async (mod) => collectAssets(mod)));
1513
+ const preloadLink = [...assetsUrls].map((item) => createLink$1(remixRouter.joinUrlSegments(server.config.base, item)));
1514
+ metaAttributes.push(...preloadLink);
1515
+ const renderedHTML = await renderHTML({
1516
+ rootContainerId,
1517
+ appHTML,
1518
+ indexHTML: transformedIndexHTML,
1519
+ metaAttributes,
1520
+ bodyAttributes,
1521
+ htmlAttributes,
1522
+ initialState: null
1523
+ });
1524
+ const transformed = await onPageRendered?.(url, renderedHTML, appCtx) || renderedHTML;
1525
+ res.statusCode = 200;
1526
+ res.setHeader("Content-Type", "text/html");
1527
+ const isDev = "pluginContainer" in server;
1528
+ const headers = isDev ? server.config.server.headers : server.config.preview.headers;
1529
+ vite.send(req, res, transformed, "html", { headers });
1530
+ } catch (e) {
1531
+ server.ssrFixStacktrace(e);
1532
+ console.error(`[vite-react-ssg] error: ${e.stack}`);
1533
+ res.statusCode = 500;
1534
+ res.end(e.stack);
1535
+ }
1536
+ };
1537
+ return () => {
1538
+ server.middlewares.use(renderMiddleware);
1539
+ };
1540
+ }
1541
+ };
1542
+ }
1543
+ async function callRouteLoader({
1544
+ // loadContext,
1545
+ loader,
1546
+ params,
1547
+ request,
1548
+ routeId
1549
+ // singleFetch,
1550
+ // response,
1551
+ }) {
1552
+ const result = await loader({
1553
+ request: stripDataParam(stripIndexParam(request)),
1554
+ // context: loadContext,
1555
+ params
1556
+ // Only provided when single fetch is enabled, and made available via
1557
+ // `defineLoader` types, not `LoaderFunctionArgs`
1558
+ // ...(singleFetch ? { response } : null),
1559
+ });
1560
+ if (result === void 0) {
1561
+ throw new Error(
1562
+ `You defined a loader for route "${routeId}" but didn't return anything from your \`loader\` function. Please return a value or \`null\`.`
1563
+ );
1564
+ }
1565
+ return isResponse(result) ? result : reactRouterDom.json(result);
1566
+ }
1567
+ function isResponse(value) {
1568
+ return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
1569
+ }
1570
+ function stripIndexParam(request) {
1571
+ const url = new URL(request.url);
1572
+ const indexValues = url.searchParams.getAll("index");
1573
+ url.searchParams.delete("index");
1574
+ const indexValuesToKeep = [];
1575
+ for (const indexValue of indexValues) {
1576
+ if (indexValue) {
1577
+ indexValuesToKeep.push(indexValue);
1578
+ }
1579
+ }
1580
+ for (const toKeep of indexValuesToKeep) {
1581
+ url.searchParams.append("index", toKeep);
1582
+ }
1583
+ const init = {
1584
+ method: request.method,
1585
+ body: request.body,
1586
+ headers: request.headers,
1587
+ signal: request.signal
1588
+ };
1589
+ if (init.body) {
1590
+ init.duplex = "half";
1591
+ }
1592
+ return new Request(url.href, init);
1593
+ }
1594
+ function stripDataParam(request) {
1595
+ const url = new URL(request.url);
1596
+ url.searchParams.delete("_data");
1597
+ const init = {
1598
+ method: request.method,
1599
+ body: request.body,
1600
+ headers: request.headers,
1601
+ signal: request.signal
1602
+ };
1603
+ if (init.body) {
1604
+ init.duplex = "half";
1605
+ }
1606
+ return new Request(url.href, init);
1607
+ }
1400
1608
 
1401
1609
  async function dev(ssgOptions = {}, viteConfig = {}, customOptions) {
1402
1610
  const mode = process.env.MODE || process.env.NODE_ENV || ssgOptions.mode || "development";
@@ -1431,68 +1639,14 @@ ${err.stack}`
1431
1639
  ...viteConfig,
1432
1640
  plugins: [
1433
1641
  ...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
- const isDev = "pluginContainer" in server;
1484
- const headers = isDev ? server.config.server.headers : server.config.preview.headers;
1485
- vite.send(req, res, transformed, "html", { headers });
1486
- } catch (e) {
1487
- viteServer.ssrFixStacktrace(e);
1488
- console.error(`[vite-react-ssg] error: ${e.stack}`);
1489
- res.statusCode = 500;
1490
- res.end(e.stack);
1491
- }
1492
- });
1493
- };
1494
- }
1495
- }
1642
+ ssrServerPlugin({
1643
+ template,
1644
+ ssrEntry,
1645
+ onBeforePageRender,
1646
+ onPageRendered,
1647
+ entry,
1648
+ rootContainerId
1649
+ })
1496
1650
  ]
1497
1651
  });
1498
1652
  await viteServer.listen();
package/dist/node.mjs CHANGED
@@ -2,14 +2,17 @@ import { join, isAbsolute, parse, dirname } from 'node:path';
2
2
  import { createRequire } from 'node:module';
3
3
  import { gray, yellow, blue, dim, cyan, red, green, reset, bold, bgLightCyan } from 'kolorist';
4
4
  import fs from 'fs-extra';
5
- import { resolveConfig, build as build$1, mergeConfig, version as version$1, createServer, send } from 'vite';
5
+ import { resolveConfig, createLogger, build as build$1, mergeConfig, version as version$1, send, createServer } from 'vite';
6
6
  import { JSDOM } from 'jsdom';
7
7
  import { s as serializeState } from './shared/vite-react-ssg.579feabb.mjs';
8
+ import { a as withTrailingSlash, r as removeLeadingSlash, c as convertRoutesToDataRoutes, j as joinUrlSegments } from './shared/vite-react-ssg.054e813a.mjs';
8
9
  import { readFileSync } from 'node:fs';
9
10
  import React from 'react';
10
11
  import { HelmetProvider } from 'react-helmet-async';
11
- import { Writable } from 'node:stream';
12
+ import { Writable, Readable } from 'node:stream';
12
13
  import { renderToPipeableStream } from 'react-dom/server';
14
+ import { matchRoutes, json } from 'react-router-dom';
15
+ import { once } from 'node:events';
13
16
 
14
17
  function getDefaultExportFromCjs (x) {
15
18
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -878,6 +881,10 @@ async function routesToPaths(routes) {
878
881
  }
879
882
  if (route.index)
880
883
  addEntry(prefix, route.entry);
884
+ if (route.index && !path) {
885
+ addEntry("/", route.entry);
886
+ paths.add("/");
887
+ }
881
888
  if (Array.isArray(route.children))
882
889
  await getPaths(route.children, path);
883
890
  }
@@ -912,23 +919,6 @@ function createRequest(path) {
912
919
  url.pathname = path;
913
920
  return new Request(url.href);
914
921
  }
915
- function joinUrlSegments(a, b) {
916
- if (!a || !b)
917
- return a || b || "";
918
- if (a[a.length - 1] === "/")
919
- a = a.substring(0, a.length - 1);
920
- if (b[0] !== "/")
921
- b = `/${b}`;
922
- return a + b;
923
- }
924
- function removeLeadingSlash(str) {
925
- return str[0] === "/" ? str.slice(1) : str;
926
- }
927
- function withTrailingSlash(path) {
928
- if (path[path.length - 1] !== "/")
929
- return `${path}/`;
930
- return path;
931
- }
932
922
  const dynamicRE = /[:*?]/;
933
923
  function isDynamicSegmentsRoute(route) {
934
924
  if (!route)
@@ -1043,6 +1033,7 @@ function extractHelmet(context, styleCollector) {
1043
1033
  return { htmlAttributes, bodyAttributes, metaAttributes, styleTag };
1044
1034
  }
1045
1035
 
1036
+ const SCRIPT_COMMENT_PLACEHOLDER = "/* SCRIPT_COMMENT_PLACEHOLDER */";
1046
1037
  async function renderHTML({
1047
1038
  rootContainerId,
1048
1039
  indexHTML,
@@ -1054,6 +1045,8 @@ async function renderHTML({
1054
1045
  }) {
1055
1046
  const stateScript = initialState ? `
1056
1047
  <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
1048
+ const scriptPlaceHolder = `
1049
+ <script>${SCRIPT_COMMENT_PLACEHOLDER}<\/script>`;
1057
1050
  const headStartTag = "<head>";
1058
1051
  const metaTags = metaAttributes.join("");
1059
1052
  indexHTML = indexHTML.replace(headStartTag, headStartTag + metaTags);
@@ -1065,7 +1058,7 @@ async function renderHTML({
1065
1058
  if (indexHTML.includes(container)) {
1066
1059
  return indexHTML.replace(
1067
1060
  container,
1068
- `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}`
1061
+ `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}${scriptPlaceHolder}`
1069
1062
  );
1070
1063
  }
1071
1064
  const html5Parser = await import('html5parser');
@@ -1159,7 +1152,8 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1159
1152
  const config = await resolveConfig(viteConfig, "build", mode, mode);
1160
1153
  const cwd = process.cwd();
1161
1154
  const root = config.root || cwd;
1162
- const ssgOut = join(root, ".vite-react-ssg-temp", Math.random().toString(36).substring(2, 12));
1155
+ const hash = Math.random().toString(36).substring(2, 12);
1156
+ const ssgOut = join(root, ".vite-react-ssg-temp", hash);
1163
1157
  const outDir = config.build.outDir || "dist";
1164
1158
  const out = isAbsolute(outDir) ? outDir : join(root, outDir);
1165
1159
  const configBase = config.base;
@@ -1181,6 +1175,13 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1181
1175
  } = Object.assign({}, config.ssgOptions || {}, ssgOptions);
1182
1176
  if (fs.existsSync(ssgOut))
1183
1177
  await fs.remove(ssgOut);
1178
+ const clientLogger = createLogger();
1179
+ const loggerWarn = clientLogger.warn;
1180
+ clientLogger.warn = (msg, options) => {
1181
+ if (msg.includes("vite:resolve") && msg.includes("externalized for browser compatibility"))
1182
+ return;
1183
+ loggerWarn(msg, options);
1184
+ };
1184
1185
  buildLog("Build for client...");
1185
1186
  await build$1(mergeConfig(viteConfig, {
1186
1187
  build: {
@@ -1198,7 +1199,9 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1198
1199
  }
1199
1200
  }
1200
1201
  },
1201
- mode: config.mode
1202
+ customLogger: clientLogger,
1203
+ mode: config.mode,
1204
+ ssr: { noExternal: ["vite-react-ssg"] }
1202
1205
  }));
1203
1206
  if (mock) {
1204
1207
  const { jsdomGlobal } = await import('./chunks/jsdomGlobal.mjs');
@@ -1223,7 +1226,8 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1223
1226
  }
1224
1227
  }
1225
1228
  },
1226
- mode: config.mode
1229
+ mode: config.mode,
1230
+ ssr: { noExternal: ["vite-react-ssg"] }
1227
1231
  }));
1228
1232
  const prefix = format === "esm" && process.platform === "win32" ? "file://" : "";
1229
1233
  const ext = format === "esm" ? ".mjs" : ".cjs";
@@ -1247,6 +1251,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1247
1251
  indexHTML = rewriteScripts(indexHTML, script);
1248
1252
  const queue = new PQueue({ concurrency });
1249
1253
  const crittersQueue = new PQueue({ concurrency: 1 });
1254
+ const staticLoaderDataManifest = {};
1250
1255
  for (const path of routesPaths) {
1251
1256
  queue.add(async () => {
1252
1257
  try {
@@ -1256,7 +1261,8 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1256
1261
  const transformedIndexHTML = await onBeforePageRender?.(path, indexHTML, appCtx) || indexHTML;
1257
1262
  const fetchUrl = `${withTrailingSlash(base)}${removeLeadingSlash(path)}`;
1258
1263
  const request = createRequest(fetchUrl);
1259
- const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag } = await render(app ?? [...routes2], request, styleCollector, base);
1264
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await render(app ?? [...routes2], request, styleCollector, base);
1265
+ staticLoaderDataManifest[path] = routerContext?.loaderData;
1260
1266
  await triggerOnSSRAppRendered?.(path, appHTML, appCtx);
1261
1267
  const renderedHTML = await renderHTML({
1262
1268
  rootContainerId,
@@ -1272,6 +1278,7 @@ async function build(ssgOptions = {}, viteConfig = {}) {
1272
1278
  renderPreloadLinks(jsdom.window.document, modules, ssrManifest);
1273
1279
  const html = jsdom.serialize();
1274
1280
  let transformed = await onPageRendered?.(path, html, appCtx) || html;
1281
+ transformed = transformed.replace(SCRIPT_COMMENT_PLACEHOLDER, `window.__VITE_REACT_SSG_HASH__ = '${hash}'`);
1275
1282
  if (critters) {
1276
1283
  transformed = await crittersQueue.add(() => critters.process(transformed));
1277
1284
  transformed = transformed.replace(/<link\srel="stylesheet"/g, '<link rel="stylesheet" crossorigin');
@@ -1293,6 +1300,12 @@ ${err.stack}`);
1293
1300
  });
1294
1301
  }
1295
1302
  await queue.start().onIdle();
1303
+ buildLog("Generating static loader data manifest...");
1304
+ const staticLoaderDataManifestString = JSON.stringify(staticLoaderDataManifest, null, 0);
1305
+ await fs.writeFile(join(out, `static-loader-data-manifest-${hash}.json`), staticLoaderDataManifestString);
1306
+ config.logger.info(
1307
+ `${dim(`${outDir}/`)}${cyan(`static-loader-data-manifest-${hash}.json`.padEnd(15, " "))} ${dim(getSize(staticLoaderDataManifestString))}`
1308
+ );
1296
1309
  await fs.remove(join(root, ".vite-react-ssg-temp"));
1297
1310
  const pwaPlugin = config.plugins.find((i) => i.name === "vite-plugin-pwa")?.api;
1298
1311
  if (pwaPlugin && !pwaPlugin.disabled && pwaPlugin.generateSW) {
@@ -1390,6 +1403,201 @@ function fromNodeRequest(nodeReq) {
1390
1403
  };
1391
1404
  return new Request(url.href, init);
1392
1405
  }
1406
+ async function toNodeRequest(res, nodeRes) {
1407
+ nodeRes.statusCode = res.status;
1408
+ nodeRes.statusMessage = res.statusText;
1409
+ for (const [name, value] of res.headers) {
1410
+ nodeRes.setHeader(name, value);
1411
+ }
1412
+ if (res.body) {
1413
+ const responseBody = res.body;
1414
+ const readable = Readable.from(responseBody);
1415
+ readable.pipe(nodeRes);
1416
+ await once(readable, "end");
1417
+ } else {
1418
+ nodeRes.end();
1419
+ }
1420
+ }
1421
+
1422
+ function ssrServerPlugin({
1423
+ template,
1424
+ ssrEntry,
1425
+ onBeforePageRender,
1426
+ entry,
1427
+ rootContainerId,
1428
+ onPageRendered
1429
+ }) {
1430
+ return {
1431
+ name: "vite-react-ssg:dev-server-remix",
1432
+ configureServer(server) {
1433
+ const renderMiddleware = async (req, res, _next) => {
1434
+ try {
1435
+ const url = req.originalUrl;
1436
+ const createRoot = await server.ssrLoadModule(ssrEntry).then((m) => m.createRoot);
1437
+ const appCtx = await createRoot(false, url);
1438
+ const { routes, getStyleCollector, base, app } = appCtx;
1439
+ const searchParams = new URLSearchParams(url.split("?")[1]);
1440
+ if (!app && searchParams.has("_data")) {
1441
+ const request = fromNodeRequest(req);
1442
+ const url2 = new URL(request.url);
1443
+ const routeId = decodeURIComponent(searchParams.get("_data"));
1444
+ const matches = matchRoutes(
1445
+ convertRoutesToDataRoutes([...routes], (route) => route),
1446
+ {
1447
+ pathname: url2.pathname,
1448
+ search: url2.search,
1449
+ hash: url2.hash,
1450
+ state: null,
1451
+ key: "default"
1452
+ },
1453
+ base
1454
+ );
1455
+ if (!matches) {
1456
+ res.statusCode = 404;
1457
+ res.end(`Route not found: ${routeId}`);
1458
+ return;
1459
+ }
1460
+ const match = matches.find((m) => m.route.id === routeId);
1461
+ if (!match) {
1462
+ res.statusCode = 404;
1463
+ res.end(`Route not found: ${routeId}`);
1464
+ return;
1465
+ }
1466
+ const loader = match.route.loader ?? await match.route.lazy?.().then((m) => m.loader);
1467
+ if (!loader) {
1468
+ res.statusCode = 200;
1469
+ res.end(`There is no loader for the route: ${routeId}`);
1470
+ return;
1471
+ }
1472
+ const response = await callRouteLoader({
1473
+ loader,
1474
+ params: match.params,
1475
+ request,
1476
+ routeId
1477
+ });
1478
+ await toNodeRequest(response, res);
1479
+ return;
1480
+ }
1481
+ const indexHTML = await server.transformIndexHtml(url, template);
1482
+ const transformedIndexHTML = await onBeforePageRender?.(url, indexHTML, appCtx) || indexHTML;
1483
+ const styleCollector = getStyleCollector ? await getStyleCollector() : null;
1484
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await render(app ?? [...routes], fromNodeRequest(req), styleCollector, base);
1485
+ metaAttributes.push(styleTag);
1486
+ const matchesEntries = routerContext?.matches.map((match) => match.route.entry).filter((entry2) => !!entry2).map((entry2) => entry2[0] === "/" ? entry2 : `/${entry2}`) ?? [];
1487
+ const mods = await Promise.all(
1488
+ [ssrEntry, entry, ...matchesEntries].map(async (entry2) => await server.moduleGraph.getModuleByUrl(entry2))
1489
+ );
1490
+ const assetsUrls = /* @__PURE__ */ new Set();
1491
+ const collectAssets = async (mod) => {
1492
+ if (!mod || !mod?.ssrTransformResult)
1493
+ return;
1494
+ const { deps = [], dynamicDeps = [] } = mod?.ssrTransformResult;
1495
+ const allDeps = [...deps, ...dynamicDeps];
1496
+ for (const dep of allDeps) {
1497
+ if (dep.endsWith(".css")) {
1498
+ assetsUrls.add(dep);
1499
+ } else if (dep.endsWith(".ts") || dep.endsWith(".tsx")) {
1500
+ const depModule = await server.moduleGraph.getModuleByUrl(dep);
1501
+ depModule && await collectAssets(depModule);
1502
+ }
1503
+ }
1504
+ };
1505
+ await Promise.all(mods.map(async (mod) => collectAssets(mod)));
1506
+ const preloadLink = [...assetsUrls].map((item) => createLink$1(joinUrlSegments(server.config.base, item)));
1507
+ metaAttributes.push(...preloadLink);
1508
+ const renderedHTML = await renderHTML({
1509
+ rootContainerId,
1510
+ appHTML,
1511
+ indexHTML: transformedIndexHTML,
1512
+ metaAttributes,
1513
+ bodyAttributes,
1514
+ htmlAttributes,
1515
+ initialState: null
1516
+ });
1517
+ const transformed = await onPageRendered?.(url, renderedHTML, appCtx) || renderedHTML;
1518
+ res.statusCode = 200;
1519
+ res.setHeader("Content-Type", "text/html");
1520
+ const isDev = "pluginContainer" in server;
1521
+ const headers = isDev ? server.config.server.headers : server.config.preview.headers;
1522
+ send(req, res, transformed, "html", { headers });
1523
+ } catch (e) {
1524
+ server.ssrFixStacktrace(e);
1525
+ console.error(`[vite-react-ssg] error: ${e.stack}`);
1526
+ res.statusCode = 500;
1527
+ res.end(e.stack);
1528
+ }
1529
+ };
1530
+ return () => {
1531
+ server.middlewares.use(renderMiddleware);
1532
+ };
1533
+ }
1534
+ };
1535
+ }
1536
+ async function callRouteLoader({
1537
+ // loadContext,
1538
+ loader,
1539
+ params,
1540
+ request,
1541
+ routeId
1542
+ // singleFetch,
1543
+ // response,
1544
+ }) {
1545
+ const result = await loader({
1546
+ request: stripDataParam(stripIndexParam(request)),
1547
+ // context: loadContext,
1548
+ params
1549
+ // Only provided when single fetch is enabled, and made available via
1550
+ // `defineLoader` types, not `LoaderFunctionArgs`
1551
+ // ...(singleFetch ? { response } : null),
1552
+ });
1553
+ if (result === void 0) {
1554
+ throw new Error(
1555
+ `You defined a loader for route "${routeId}" but didn't return anything from your \`loader\` function. Please return a value or \`null\`.`
1556
+ );
1557
+ }
1558
+ return isResponse(result) ? result : json(result);
1559
+ }
1560
+ function isResponse(value) {
1561
+ return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
1562
+ }
1563
+ function stripIndexParam(request) {
1564
+ const url = new URL(request.url);
1565
+ const indexValues = url.searchParams.getAll("index");
1566
+ url.searchParams.delete("index");
1567
+ const indexValuesToKeep = [];
1568
+ for (const indexValue of indexValues) {
1569
+ if (indexValue) {
1570
+ indexValuesToKeep.push(indexValue);
1571
+ }
1572
+ }
1573
+ for (const toKeep of indexValuesToKeep) {
1574
+ url.searchParams.append("index", toKeep);
1575
+ }
1576
+ const init = {
1577
+ method: request.method,
1578
+ body: request.body,
1579
+ headers: request.headers,
1580
+ signal: request.signal
1581
+ };
1582
+ if (init.body) {
1583
+ init.duplex = "half";
1584
+ }
1585
+ return new Request(url.href, init);
1586
+ }
1587
+ function stripDataParam(request) {
1588
+ const url = new URL(request.url);
1589
+ url.searchParams.delete("_data");
1590
+ const init = {
1591
+ method: request.method,
1592
+ body: request.body,
1593
+ headers: request.headers,
1594
+ signal: request.signal
1595
+ };
1596
+ if (init.body) {
1597
+ init.duplex = "half";
1598
+ }
1599
+ return new Request(url.href, init);
1600
+ }
1393
1601
 
1394
1602
  async function dev(ssgOptions = {}, viteConfig = {}, customOptions) {
1395
1603
  const mode = process.env.MODE || process.env.NODE_ENV || ssgOptions.mode || "development";
@@ -1424,68 +1632,14 @@ ${err.stack}`
1424
1632
  ...viteConfig,
1425
1633
  plugins: [
1426
1634
  ...viteConfig.plugins ?? [],
1427
- {
1428
- name: "vite-react-ssg:dev-server-remix",
1429
- configureServer(server) {
1430
- return () => {
1431
- server.middlewares.use(async (req, res, _next) => {
1432
- try {
1433
- const url = req.originalUrl;
1434
- const indexHTML = await viteServer.transformIndexHtml(url, template);
1435
- const createRoot = await viteServer.ssrLoadModule(ssrEntry).then((m) => m.createRoot);
1436
- const appCtx = await createRoot(false, url);
1437
- const { routes, getStyleCollector, base, app } = appCtx;
1438
- const transformedIndexHTML = await onBeforePageRender?.(url, indexHTML, appCtx) || indexHTML;
1439
- const styleCollector = getStyleCollector ? await getStyleCollector() : null;
1440
- const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await render(app ?? [...routes], fromNodeRequest(req), styleCollector, base);
1441
- metaAttributes.push(styleTag);
1442
- const matchesEntries = routerContext?.matches.map((match) => match.route.entry).filter((entry2) => !!entry2).map((entry2) => entry2[0] === "/" ? entry2 : `/${entry2}`) ?? [];
1443
- const mods = await Promise.all(
1444
- [ssrEntry, entry, ...matchesEntries].map(async (entry2) => await viteServer.moduleGraph.getModuleByUrl(entry2))
1445
- );
1446
- const assetsUrls = /* @__PURE__ */ new Set();
1447
- const collectAssets = async (mod) => {
1448
- if (!mod || !mod?.ssrTransformResult)
1449
- return;
1450
- const { deps = [], dynamicDeps = [] } = mod?.ssrTransformResult;
1451
- const allDeps = [...deps, ...dynamicDeps];
1452
- for (const dep of allDeps) {
1453
- if (dep.endsWith(".css")) {
1454
- assetsUrls.add(dep);
1455
- } else if (dep.endsWith(".ts") || dep.endsWith(".tsx")) {
1456
- const depModule = await viteServer.moduleGraph.getModuleByUrl(dep);
1457
- depModule && await collectAssets(depModule);
1458
- }
1459
- }
1460
- };
1461
- await Promise.all(mods.map(async (mod) => collectAssets(mod)));
1462
- const preloadLink = [...assetsUrls].map((item) => createLink$1(joinUrlSegments(config.base, item)));
1463
- metaAttributes.push(...preloadLink);
1464
- const renderedHTML = await renderHTML({
1465
- rootContainerId,
1466
- appHTML,
1467
- indexHTML: transformedIndexHTML,
1468
- metaAttributes,
1469
- bodyAttributes,
1470
- htmlAttributes,
1471
- initialState: null
1472
- });
1473
- const transformed = await onPageRendered?.(url, renderedHTML, appCtx) || renderedHTML;
1474
- res.statusCode = 200;
1475
- res.setHeader("Content-Type", "text/html");
1476
- const isDev = "pluginContainer" in server;
1477
- const headers = isDev ? server.config.server.headers : server.config.preview.headers;
1478
- send(req, res, transformed, "html", { headers });
1479
- } catch (e) {
1480
- viteServer.ssrFixStacktrace(e);
1481
- console.error(`[vite-react-ssg] error: ${e.stack}`);
1482
- res.statusCode = 500;
1483
- res.end(e.stack);
1484
- }
1485
- });
1486
- };
1487
- }
1488
- }
1635
+ ssrServerPlugin({
1636
+ template,
1637
+ ssrEntry,
1638
+ onBeforePageRender,
1639
+ onPageRendered,
1640
+ entry,
1641
+ rootContainerId
1642
+ })
1489
1643
  ]
1490
1644
  });
1491
1645
  await viteServer.listen();
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ function joinUrlSegments(a, b) {
4
+ if (!a || !b)
5
+ return a || b || "";
6
+ if (a[a.length - 1] === "/")
7
+ a = a.substring(0, a.length - 1);
8
+ if (b[0] !== "/")
9
+ b = `/${b}`;
10
+ return a + b;
11
+ }
12
+ function removeLeadingSlash(str) {
13
+ return str[0] === "/" ? str.slice(1) : str;
14
+ }
15
+ function stripBase(path, base) {
16
+ if (path === base)
17
+ return "/";
18
+ const devBase = withTrailingSlash(base);
19
+ return path.startsWith(devBase) ? path.slice(devBase.length - 1) : path;
20
+ }
21
+ function withTrailingSlash(path) {
22
+ if (path[path.length - 1] !== "/")
23
+ return `${path}/`;
24
+ return path;
25
+ }
26
+ function withLeadingSlash(path) {
27
+ if (path[0] !== "/")
28
+ return `/${path}`;
29
+ return path;
30
+ }
31
+
32
+ function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath = []) {
33
+ return routes.map((route, index) => {
34
+ const treePath = [...parentPath, String(index)];
35
+ const id = typeof route.id === "string" ? route.id : treePath.join("-");
36
+ route.id = id;
37
+ if (isIndexRoute(route)) {
38
+ const indexRoute = {
39
+ ...route,
40
+ ...mapRouteProperties(route),
41
+ id
42
+ };
43
+ return indexRoute;
44
+ } else {
45
+ const pathOrLayoutRoute = {
46
+ ...route,
47
+ ...mapRouteProperties(route),
48
+ id,
49
+ children: void 0
50
+ };
51
+ if (route.children) {
52
+ pathOrLayoutRoute.children = convertRoutesToDataRoutes(
53
+ route.children,
54
+ mapRouteProperties,
55
+ treePath
56
+ // manifest,
57
+ );
58
+ }
59
+ return pathOrLayoutRoute;
60
+ }
61
+ });
62
+ }
63
+ function isIndexRoute(route) {
64
+ return route.index === true;
65
+ }
66
+
67
+ exports.convertRoutesToDataRoutes = convertRoutesToDataRoutes;
68
+ exports.joinUrlSegments = joinUrlSegments;
69
+ exports.removeLeadingSlash = removeLeadingSlash;
70
+ exports.stripBase = stripBase;
71
+ exports.withLeadingSlash = withLeadingSlash;
72
+ exports.withTrailingSlash = withTrailingSlash;
@@ -0,0 +1,65 @@
1
+ function joinUrlSegments(a, b) {
2
+ if (!a || !b)
3
+ return a || b || "";
4
+ if (a[a.length - 1] === "/")
5
+ a = a.substring(0, a.length - 1);
6
+ if (b[0] !== "/")
7
+ b = `/${b}`;
8
+ return a + b;
9
+ }
10
+ function removeLeadingSlash(str) {
11
+ return str[0] === "/" ? str.slice(1) : str;
12
+ }
13
+ function stripBase(path, base) {
14
+ if (path === base)
15
+ return "/";
16
+ const devBase = withTrailingSlash(base);
17
+ return path.startsWith(devBase) ? path.slice(devBase.length - 1) : path;
18
+ }
19
+ function withTrailingSlash(path) {
20
+ if (path[path.length - 1] !== "/")
21
+ return `${path}/`;
22
+ return path;
23
+ }
24
+ function withLeadingSlash(path) {
25
+ if (path[0] !== "/")
26
+ return `/${path}`;
27
+ return path;
28
+ }
29
+
30
+ function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath = []) {
31
+ return routes.map((route, index) => {
32
+ const treePath = [...parentPath, String(index)];
33
+ const id = typeof route.id === "string" ? route.id : treePath.join("-");
34
+ route.id = id;
35
+ if (isIndexRoute(route)) {
36
+ const indexRoute = {
37
+ ...route,
38
+ ...mapRouteProperties(route),
39
+ id
40
+ };
41
+ return indexRoute;
42
+ } else {
43
+ const pathOrLayoutRoute = {
44
+ ...route,
45
+ ...mapRouteProperties(route),
46
+ id,
47
+ children: void 0
48
+ };
49
+ if (route.children) {
50
+ pathOrLayoutRoute.children = convertRoutesToDataRoutes(
51
+ route.children,
52
+ mapRouteProperties,
53
+ treePath
54
+ // manifest,
55
+ );
56
+ }
57
+ return pathOrLayoutRoute;
58
+ }
59
+ });
60
+ }
61
+ function isIndexRoute(route) {
62
+ return route.index === true;
63
+ }
64
+
65
+ export { withTrailingSlash as a, convertRoutesToDataRoutes as c, joinUrlSegments as j, removeLeadingSlash as r, stripBase as s, withLeadingSlash as w };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vite-react-ssg",
3
3
  "type": "module",
4
- "version": "0.6.4",
4
+ "version": "0.7.0-beta.2",
5
5
  "packageManager": "pnpm@9.4.0",
6
6
  "description": "",
7
7
  "author": "Riri <Daydreamerriri@outlook.com>",