vite-react-ssg 0.7.3 → 0.8.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.
Files changed (44) hide show
  1. package/README.md +7 -1
  2. package/dist/chunks/jsdomGlobal.cjs +10 -65
  3. package/dist/chunks/jsdomGlobal.mjs +10 -65
  4. package/dist/client/single-page.cjs +8 -6
  5. package/dist/client/single-page.d.cts +3 -4
  6. package/dist/client/single-page.d.mts +3 -4
  7. package/dist/client/single-page.d.ts +3 -4
  8. package/dist/client/single-page.mjs +9 -7
  9. package/dist/index.cjs +19 -12
  10. package/dist/index.d.cts +3 -4
  11. package/dist/index.d.mts +3 -4
  12. package/dist/index.d.ts +3 -4
  13. package/dist/index.mjs +17 -10
  14. package/dist/node/cli.cjs +9 -8
  15. package/dist/node/cli.mjs +7 -6
  16. package/dist/node.cjs +22 -1713
  17. package/dist/node.d.cts +1 -2
  18. package/dist/node.d.mts +1 -2
  19. package/dist/node.d.ts +1 -2
  20. package/dist/node.mjs +17 -1707
  21. package/dist/shared/vite-react-ssg.4y2OEiyV.mjs +31 -0
  22. package/dist/shared/{vite-react-ssg.a292c181.mjs → vite-react-ssg.B6SGTWpD.mjs} +5 -4
  23. package/dist/shared/vite-react-ssg.BoLst82g.mjs +1036 -0
  24. package/dist/shared/{vite-react-ssg.c1d49976.cjs → vite-react-ssg.BuZVBVT6.cjs} +5 -8
  25. package/dist/shared/{vite-react-ssg.054e813a.mjs → vite-react-ssg.C0y5wbxl.mjs} +1 -30
  26. package/dist/shared/{vite-react-ssg.0408f7e1.cjs → vite-react-ssg.C2GpVZF1.cjs} +0 -34
  27. package/dist/shared/vite-react-ssg.CFQGqC60.cjs +36 -0
  28. package/dist/shared/vite-react-ssg.CjIppNIS.mjs +30 -0
  29. package/dist/shared/vite-react-ssg.D0EGioth.cjs +35 -0
  30. package/dist/shared/vite-react-ssg.Dyp0kt3B.cjs +1045 -0
  31. package/dist/shared/{vite-react-ssg.de54c34e.d.cts → vite-react-ssg.Txy2EJkI.d.cts} +25 -6
  32. package/dist/shared/{vite-react-ssg.de54c34e.d.mts → vite-react-ssg.Txy2EJkI.d.mts} +25 -6
  33. package/dist/shared/{vite-react-ssg.de54c34e.d.ts → vite-react-ssg.Txy2EJkI.d.ts} +25 -6
  34. package/dist/tanstack.cjs +150 -0
  35. package/dist/tanstack.d.cts +29 -0
  36. package/dist/tanstack.d.mts +29 -0
  37. package/dist/tanstack.d.ts +29 -0
  38. package/dist/tanstack.mjs +143 -0
  39. package/package.json +30 -17
  40. /package/dist/shared/{vite-react-ssg.579feabb.mjs → vite-react-ssg.C6pK7rvr.mjs} +0 -0
  41. /package/dist/shared/{vite-react-ssg.bee8a5a9.cjs → vite-react-ssg.CjsEygxB.cjs} +0 -0
  42. /package/dist/shared/{vite-react-ssg.faf3855a.d.cts → vite-react-ssg.Cm9gBlfg.d.cts} +0 -0
  43. /package/dist/shared/{vite-react-ssg.faf3855a.d.mts → vite-react-ssg.Cm9gBlfg.d.mts} +0 -0
  44. /package/dist/shared/{vite-react-ssg.faf3855a.d.ts → vite-react-ssg.Cm9gBlfg.d.ts} +0 -0
@@ -0,0 +1,1036 @@
1
+ import { join, isAbsolute, parse, dirname } from 'node:path';
2
+ import { createRequire } from 'node:module';
3
+ import { gray, yellow, blue, dim, cyan, red, green, reset, bold, bgLightCyan } from 'kolorist';
4
+ import PQueue from 'p-queue';
5
+ import fs from 'fs-extra';
6
+ import { resolveConfig, createLogger, build as build$1, mergeConfig, version as version$1, send, createServer } from 'vite';
7
+ import { JSDOM } from 'jsdom';
8
+ import { s as serializeState } from './vite-react-ssg.C6pK7rvr.mjs';
9
+ import { a as withTrailingSlash, r as removeLeadingSlash, w as withLeadingSlash, s as stripBase, j as joinUrlSegments } from './vite-react-ssg.CjIppNIS.mjs';
10
+ import { jsx } from 'react/jsx-runtime';
11
+ import { HelmetProvider } from 'react-helmet-async';
12
+ import { Writable, Readable } from 'node:stream';
13
+ import { renderToPipeableStream } from 'react-dom/server';
14
+ import { once } from 'node:events';
15
+ import { c as convertRoutesToDataRoutes } from './vite-react-ssg.C0y5wbxl.mjs';
16
+ import { M as META_CONTAINER_ID } from './vite-react-ssg.4y2OEiyV.mjs';
17
+
18
+ const version = "0.8.0-beta.2";
19
+
20
+ function buildLog(text, count) {
21
+ console.log(`
22
+ ${gray("[vite-react-ssg]")} ${yellow(text)}${count ? blue(` (${count})`) : ""}`);
23
+ }
24
+ function getSize(str) {
25
+ return `${(str.length / 1024).toFixed(2)} KiB`;
26
+ }
27
+ async function routesToPaths(routes) {
28
+ if (!routes || routes.length === 0)
29
+ return { paths: ["/"] };
30
+ const paths = /* @__PURE__ */ new Set();
31
+ const getPaths = async (routes2, prefix = "") => {
32
+ prefix = prefix.replace(/\/$/g, "");
33
+ for (let route of routes2) {
34
+ if (route.lazy) {
35
+ const lazyData = await route.lazy();
36
+ if (lazyData) {
37
+ route = {
38
+ ...route,
39
+ ...lazyData
40
+ };
41
+ }
42
+ }
43
+ let path = route.path;
44
+ path = handlePath(path, prefix);
45
+ if (route.getStaticPaths && isDynamicSegmentsRoute(path)) {
46
+ const staticPaths = await route.getStaticPaths();
47
+ for (let staticPath of staticPaths) {
48
+ staticPath = handlePath(staticPath, prefix);
49
+ if (Array.isArray(route.children))
50
+ await getPaths(route.children, staticPath);
51
+ }
52
+ }
53
+ if (route.index && !path) {
54
+ paths.add("/");
55
+ }
56
+ if (Array.isArray(route.children))
57
+ await getPaths(route.children, path);
58
+ }
59
+ };
60
+ await getPaths(routes);
61
+ return { paths: Array.from(paths) };
62
+ function handlePath(path, prefix) {
63
+ if (path != null) {
64
+ path = prefix && !path.startsWith("/") ? `${prefix}${path ? `/${path}` : ""}` : path;
65
+ paths.add(path);
66
+ }
67
+ return path;
68
+ }
69
+ }
70
+ async function resolveAlias(config, entry) {
71
+ const resolver = config.createResolver();
72
+ const result = await resolver(entry, config.root);
73
+ return result || join(config.root, entry);
74
+ }
75
+ function createRequest(path) {
76
+ const url = new URL(path, "http://vite-react-ssg.com");
77
+ url.search = "";
78
+ url.hash = "";
79
+ url.pathname = path;
80
+ return new Request(url.href);
81
+ }
82
+ const dynamicRE = /[:*?]/;
83
+ function isDynamicSegmentsRoute(route) {
84
+ if (!route)
85
+ return false;
86
+ return dynamicRE.test(route);
87
+ }
88
+
89
+ async function getCritters(outDir, options = {}) {
90
+ try {
91
+ const CrittersClass = (await import('critters')).default;
92
+ return new CrittersClass({
93
+ path: outDir,
94
+ logLevel: "warn",
95
+ external: true,
96
+ inlineFonts: true,
97
+ preloadFonts: true,
98
+ ...options
99
+ });
100
+ } catch (e) {
101
+ return void 0;
102
+ }
103
+ }
104
+
105
+ const SCRIPT_COMMENT_PLACEHOLDER = "/* SCRIPT_COMMENT_PLACEHOLDER */";
106
+ async function renderHTML({
107
+ rootContainerId,
108
+ indexHTML,
109
+ appHTML,
110
+ metaAttributes,
111
+ bodyAttributes,
112
+ htmlAttributes,
113
+ initialState
114
+ }) {
115
+ const stateScript = initialState ? `
116
+ <script>window.__INITIAL_STATE__=${initialState}<\/script>` : "";
117
+ const scriptPlaceHolder = `
118
+ <script>${SCRIPT_COMMENT_PLACEHOLDER}<\/script>`;
119
+ const headStartTag = "<head>";
120
+ const metaTags = metaAttributes.join("");
121
+ indexHTML = indexHTML.replace(headStartTag, headStartTag + metaTags);
122
+ const bodyStartTag = "<body";
123
+ indexHTML = indexHTML.replace(bodyStartTag, `${bodyStartTag} ${bodyAttributes}`);
124
+ const htmlStartTag = "<html";
125
+ indexHTML = indexHTML.replace(htmlStartTag, `${htmlStartTag} ${htmlAttributes}`);
126
+ const container = `<div id="${rootContainerId}"></div>`;
127
+ if (indexHTML.includes(container)) {
128
+ return indexHTML.replace(
129
+ container,
130
+ `<div id="${rootContainerId}" data-server-rendered="true">${appHTML}</div>${stateScript}${scriptPlaceHolder}`
131
+ );
132
+ }
133
+ const html5Parser = await import('html5parser');
134
+ const ast = html5Parser.parse(indexHTML);
135
+ let renderedOutput;
136
+ html5Parser.walk(ast, {
137
+ enter: (node) => {
138
+ if (!renderedOutput && node?.type === html5Parser.SyntaxKind.Tag && Array.isArray(node.attributes) && node.attributes.length > 0 && node.attributes.some((attr) => attr.name.value === "id" && attr.value?.value === rootContainerId)) {
139
+ const attributesStringified = [...node.attributes.map(({ name: { value: name }, value }) => `${name}="${value.value}"`)].join(" ");
140
+ const indexHTMLBefore = indexHTML.slice(0, node.start);
141
+ const indexHTMLAfter = indexHTML.slice(node.end);
142
+ renderedOutput = `${indexHTMLBefore}<${node.name} ${attributesStringified} data-server-rendered="true">${appHTML}</${node.name}>${stateScript}${indexHTMLAfter}`;
143
+ }
144
+ }
145
+ });
146
+ if (!renderedOutput)
147
+ throw new Error(`Could not find a tag with id="${rootContainerId}" to replace it with server-side rendered HTML`);
148
+ return renderedOutput;
149
+ }
150
+ async function detectEntry(root) {
151
+ const scriptSrcReg = /<script(?:.*?)src=["'](.+?)["'](?!<)(?:.*)\>(?:[\n\r\s]*?)(?:<\/script>)/gim;
152
+ const html = await fs.readFile(join(root, "index.html"), "utf-8");
153
+ const scripts = [...html.matchAll(scriptSrcReg)];
154
+ const [, entry] = scripts.find((matchResult) => {
155
+ const [script] = matchResult;
156
+ const [, scriptType] = script.match(/.*\stype=(?:'|")?([^>'"\s]+)/i) || [];
157
+ return scriptType === "module";
158
+ }) || [];
159
+ return entry || "src/main.ts";
160
+ }
161
+ function createLink$1(href) {
162
+ return `<link rel="stylesheet" href="${href}">`;
163
+ }
164
+
165
+ function renderPreloadLinks(document, assets) {
166
+ const seen = /* @__PURE__ */ new Set();
167
+ if (assets) {
168
+ assets.forEach((file) => {
169
+ if (!seen.has(file)) {
170
+ seen.add(file);
171
+ renderPreloadLink(document, file);
172
+ }
173
+ });
174
+ }
175
+ }
176
+ function renderPreloadLink(document, file) {
177
+ if (file.endsWith(".js")) {
178
+ appendLink(document, {
179
+ rel: "modulepreload",
180
+ crossOrigin: "",
181
+ href: file
182
+ });
183
+ } else if (file.endsWith(".css")) {
184
+ appendLink(document, {
185
+ rel: "stylesheet",
186
+ href: file,
187
+ crossOrigin: ""
188
+ });
189
+ } else if (file.endsWith(".woff") || file.endsWith(".woff2") || file.endsWith(".ttf")) {
190
+ appendLink(document, {
191
+ rel: "preload",
192
+ as: "font",
193
+ type: "font/woff2",
194
+ href: file,
195
+ crossOrigin: ""
196
+ });
197
+ } else if (file.endsWith(".png") || file.endsWith(".jpg") || file.endsWith(".jpeg") || file.endsWith(".webp") || file.endsWith(".gif") || file.endsWith(".ico") || file.endsWith(".svg")) {
198
+ appendLink(document, {
199
+ rel: "preload",
200
+ as: "image",
201
+ href: file,
202
+ crossOrigin: ""
203
+ });
204
+ }
205
+ }
206
+ function createLink(document) {
207
+ return document.createElement("link");
208
+ }
209
+ function setAttrs(el, attrs) {
210
+ const keys = Object.keys(attrs);
211
+ for (const key of keys)
212
+ el.setAttribute(key, attrs[key]);
213
+ }
214
+ function appendLink(document, attrs) {
215
+ const exits = document.head.querySelector(`link[href='${attrs.file}']`);
216
+ if (exits)
217
+ return;
218
+ const link = createLink(document);
219
+ setAttrs(link, attrs);
220
+ document.head.appendChild(link);
221
+ }
222
+
223
+ const DYNAMIC_IMPORT_REGEX = /import\("([^)]+)"\)/g;
224
+ async function collectAssets({
225
+ routes,
226
+ locationArg,
227
+ base,
228
+ serverManifest,
229
+ manifest,
230
+ ssrManifest
231
+ }) {
232
+ const { matchRoutes } = await import('react-router-dom');
233
+ const matches = matchRoutes([...routes], locationArg, base);
234
+ const routeEntries = matches?.map((item) => item.route.entry).filter(Boolean) ?? [];
235
+ const dynamicImports = /* @__PURE__ */ new Set();
236
+ matches?.forEach((item) => {
237
+ let lazyStr = "";
238
+ if (item.route.lazy) {
239
+ lazyStr += item.route.lazy.toString();
240
+ }
241
+ if (item.route.Component?._payload?._result) {
242
+ lazyStr += item.route.Component._payload._result.toString();
243
+ }
244
+ const match = lazyStr.matchAll(DYNAMIC_IMPORT_REGEX);
245
+ for (const m of match) {
246
+ dynamicImports.add(m[1].split("/").at(-1) ?? "");
247
+ }
248
+ });
249
+ const entries = /* @__PURE__ */ new Set();
250
+ routeEntries.forEach((e) => entries.add(e));
251
+ const manifestEntries = [...Object.entries(serverManifest)];
252
+ dynamicImports.forEach((name) => {
253
+ const result = manifestEntries.find(([_, value]) => value.file.endsWith(name));
254
+ if (result) {
255
+ entries.add(result[0]);
256
+ }
257
+ });
258
+ const modules = collectModulesForEntries(manifest, entries);
259
+ const assets = /* @__PURE__ */ new Set();
260
+ Array.from(modules).forEach((id) => {
261
+ const files = ssrManifest[id] || [];
262
+ files.forEach((file) => {
263
+ assets.add(file);
264
+ });
265
+ });
266
+ return assets;
267
+ }
268
+ function collectModulesForEntries(manifest, entries) {
269
+ const mods = /* @__PURE__ */ new Set();
270
+ if (!entries)
271
+ return mods;
272
+ for (const entry of entries)
273
+ collectModules(manifest, entry, mods);
274
+ return mods;
275
+ }
276
+ function collectModules(manifest, entry, mods = /* @__PURE__ */ new Set()) {
277
+ if (!entry)
278
+ return mods;
279
+ mods.add(entry);
280
+ manifest[entry]?.dynamicImports?.forEach((item) => {
281
+ collectModules(manifest, item, mods);
282
+ });
283
+ return mods;
284
+ }
285
+
286
+ async function renderStaticApp(app) {
287
+ const writableStream = new WritableAsPromise();
288
+ const { pipe } = renderToPipeableStream(app, {
289
+ onError(error) {
290
+ writableStream.destroy(error);
291
+ },
292
+ onAllReady() {
293
+ pipe(writableStream);
294
+ }
295
+ });
296
+ return writableStream.getPromise();
297
+ }
298
+ class WritableAsPromise extends Writable {
299
+ _output;
300
+ _deferred;
301
+ constructor() {
302
+ super();
303
+ this._output = "";
304
+ this._deferred = {
305
+ promise: null,
306
+ resolve: () => null,
307
+ reject: () => null
308
+ };
309
+ this._deferred.promise = new Promise((resolve, reject) => {
310
+ this._deferred.resolve = resolve;
311
+ this._deferred.reject = reject;
312
+ });
313
+ }
314
+ _write(chunk, _enc, next) {
315
+ this._output += chunk.toString();
316
+ next();
317
+ }
318
+ _destroy(error, next) {
319
+ if (error instanceof Error)
320
+ this._deferred.reject(error);
321
+ else
322
+ next();
323
+ }
324
+ end() {
325
+ this._deferred.resolve(this._output);
326
+ return this.destroy();
327
+ }
328
+ getPromise() {
329
+ return this._deferred.promise;
330
+ }
331
+ }
332
+
333
+ function extractHelmet(context, styleCollector) {
334
+ const { helmet } = context;
335
+ const htmlAttributes = helmet.htmlAttributes.toString();
336
+ const bodyAttributes = helmet.bodyAttributes.toString();
337
+ let titleString = helmet.title.toString();
338
+ if (titleString.split(">")[1] === "</title") {
339
+ titleString = "";
340
+ }
341
+ const metaStrings = [
342
+ titleString,
343
+ helmet.meta.toString(),
344
+ helmet.link.toString(),
345
+ helmet.script.toString()
346
+ ];
347
+ const styleTag = styleCollector?.toString?.("") ?? "";
348
+ const metaAttributes = metaStrings.filter(Boolean);
349
+ return { htmlAttributes, bodyAttributes, metaAttributes, styleTag };
350
+ }
351
+
352
+ function invariant(value, message) {
353
+ if (value === false || value === null || typeof value === "undefined") {
354
+ console.error(
355
+ "The following error is a bug in Vite-react-ssg; please open an issue! https://github.com/Daydreamer-riri/vite-react-ssg/issues/new"
356
+ );
357
+ throw new Error(message);
358
+ }
359
+ }
360
+
361
+ function fromNodeHeaders(nodeHeaders) {
362
+ const headers = new Headers();
363
+ for (const [key, values] of Object.entries(nodeHeaders)) {
364
+ if (values) {
365
+ if (Array.isArray(values)) {
366
+ for (const value of values)
367
+ headers.append(key, value);
368
+ } else {
369
+ headers.set(key, values);
370
+ }
371
+ }
372
+ }
373
+ return headers;
374
+ }
375
+ function fromNodeRequest(nodeReq) {
376
+ const origin = nodeReq.headers.origin && nodeReq.headers.origin !== "null" ? nodeReq.headers.origin : `http://${nodeReq.headers.host}`;
377
+ invariant(
378
+ nodeReq.originalUrl,
379
+ "Expected `nodeReq.originalUrl` to be defined"
380
+ );
381
+ const url = new URL(nodeReq.originalUrl, origin);
382
+ const init = {
383
+ method: nodeReq.method,
384
+ headers: fromNodeHeaders(nodeReq.headers)
385
+ };
386
+ return new Request(url.href, init);
387
+ }
388
+ async function toNodeRequest(res, nodeRes) {
389
+ nodeRes.statusCode = res.status;
390
+ nodeRes.statusMessage = res.statusText;
391
+ for (const [name, value] of res.headers) {
392
+ nodeRes.setHeader(name, value);
393
+ }
394
+ if (res.body) {
395
+ const responseBody = res.body;
396
+ const readable = Readable.from(responseBody);
397
+ readable.pipe(nodeRes);
398
+ await once(readable, "end");
399
+ } else {
400
+ nodeRes.end();
401
+ }
402
+ }
403
+ function json(data, init) {
404
+ if (init === void 0) {
405
+ init = {};
406
+ }
407
+ const responseInit = typeof init === "number" ? {
408
+ status: init
409
+ } : init;
410
+ const headers = new Headers(responseInit.headers);
411
+ if (!headers.has("Content-Type")) {
412
+ headers.set("Content-Type", "application/json; charset=utf-8");
413
+ }
414
+ return new Response(JSON.stringify(data), {
415
+ ...responseInit,
416
+ headers
417
+ });
418
+ }
419
+ function stripDataParam(request) {
420
+ const url = new URL(request.url);
421
+ url.searchParams.delete("_data");
422
+ const init = {
423
+ method: request.method,
424
+ body: request.body,
425
+ headers: request.headers,
426
+ signal: request.signal
427
+ };
428
+ if (init.body) {
429
+ init.duplex = "half";
430
+ }
431
+ return new Request(url.href, init);
432
+ }
433
+
434
+ class RemixAdapter {
435
+ context;
436
+ constructor(context) {
437
+ this.context = context;
438
+ }
439
+ async render(path) {
440
+ const { base, routes, getStyleCollector } = this.context;
441
+ const fetchUrl = `${withTrailingSlash(base)}${removeLeadingSlash(path)}`;
442
+ const request = createRequest(fetchUrl);
443
+ const styleCollector = getStyleCollector ? await getStyleCollector() : null;
444
+ const helmetContext = {};
445
+ let routerContext = null;
446
+ const { StaticRouterProvider, createStaticHandler, createStaticRouter } = await import('react-router-dom/server.js');
447
+ const { dataRoutes, query } = createStaticHandler([...routes], { basename: base });
448
+ const _context = await query(request);
449
+ if (_context instanceof Response)
450
+ throw _context;
451
+ routerContext = _context;
452
+ const router = createStaticRouter(dataRoutes, routerContext);
453
+ let app = /* @__PURE__ */ jsx(HelmetProvider, { context: helmetContext, children: /* @__PURE__ */ jsx(StaticRouterProvider, { router, context: routerContext }) });
454
+ if (styleCollector)
455
+ app = styleCollector.collect(app);
456
+ const appHTML = await renderStaticApp(app);
457
+ const { htmlAttributes, bodyAttributes, metaAttributes, styleTag } = extractHelmet(helmetContext, styleCollector);
458
+ return { appHTML, htmlAttributes, bodyAttributes, metaAttributes, styleTag, routerContext };
459
+ }
460
+ handleLoader = async (req, res) => {
461
+ const { routes, base } = this.context;
462
+ const { matchRoutes } = await import('react-router-dom');
463
+ const request = fromNodeRequest(req);
464
+ const url = new URL(request.url);
465
+ const routeId = decodeURIComponent(url.searchParams.get("_data"));
466
+ const matches = matchRoutes(
467
+ convertRoutesToDataRoutes([...routes], (route) => route),
468
+ {
469
+ pathname: url.pathname,
470
+ search: url.search,
471
+ hash: url.hash,
472
+ state: null,
473
+ key: "default"
474
+ },
475
+ base
476
+ );
477
+ if (!matches) {
478
+ res.statusCode = 404;
479
+ res.end(`Route not found: ${routeId}`);
480
+ return;
481
+ }
482
+ const match = matches.find((m) => m.route.id === routeId);
483
+ if (!match) {
484
+ res.statusCode = 404;
485
+ res.end(`Route not found: ${routeId}`);
486
+ return;
487
+ }
488
+ const loader = match.route.loader ?? await match.route.lazy?.().then((m) => m.loader);
489
+ if (!loader) {
490
+ res.statusCode = 200;
491
+ res.end(`There is no loader for the route: ${routeId}`);
492
+ return;
493
+ }
494
+ const response = await callRouteLoader({
495
+ loader,
496
+ params: match.params,
497
+ request,
498
+ routeId
499
+ });
500
+ await toNodeRequest(response, res);
501
+ };
502
+ }
503
+ async function callRouteLoader({
504
+ // loadContext,
505
+ loader,
506
+ params,
507
+ request,
508
+ routeId
509
+ }) {
510
+ const { json } = await import('react-router-dom');
511
+ const result = await loader({
512
+ request: stripDataParam(stripIndexParam(request)),
513
+ params
514
+ });
515
+ if (result === void 0) {
516
+ throw new Error(
517
+ `You defined a loader for route "${routeId}" but didn't return anything from your \`loader\` function. Please return a value or \`null\`.`
518
+ );
519
+ }
520
+ return isResponse(result) ? result : json(result);
521
+ }
522
+ function isResponse(value) {
523
+ return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
524
+ }
525
+ function stripIndexParam(request) {
526
+ const url = new URL(request.url);
527
+ const indexValues = url.searchParams.getAll("index");
528
+ url.searchParams.delete("index");
529
+ const indexValuesToKeep = [];
530
+ for (const indexValue of indexValues) {
531
+ if (indexValue) {
532
+ indexValuesToKeep.push(indexValue);
533
+ }
534
+ }
535
+ for (const toKeep of indexValuesToKeep) {
536
+ url.searchParams.append("index", toKeep);
537
+ }
538
+ const init = {
539
+ method: request.method,
540
+ body: request.body,
541
+ headers: request.headers,
542
+ signal: request.signal
543
+ };
544
+ if (init.body) {
545
+ init.duplex = "half";
546
+ }
547
+ return new Request(url.href, init);
548
+ }
549
+
550
+ class SinglePageAdapter {
551
+ context;
552
+ constructor(context) {
553
+ this.context = context;
554
+ }
555
+ render = async () => {
556
+ const { app: inner, getStyleCollector } = this.context;
557
+ const helmetContext = {};
558
+ const styleCollector = getStyleCollector ? await getStyleCollector() : null;
559
+ let app = /* @__PURE__ */ jsx(HelmetProvider, { context: helmetContext, children: inner });
560
+ if (styleCollector)
561
+ app = styleCollector.collect(app);
562
+ const appHTML = await renderStaticApp(app);
563
+ const { htmlAttributes, bodyAttributes, metaAttributes, styleTag } = extractHelmet(helmetContext, styleCollector);
564
+ return { appHTML, htmlAttributes, bodyAttributes, metaAttributes, styleTag, routerContext: { loaderData: {} } };
565
+ };
566
+ handleLoader = () => {
567
+ };
568
+ }
569
+
570
+ class TanstackAdapter {
571
+ context;
572
+ constructor(context) {
573
+ this.context = context;
574
+ }
575
+ render = async (path) => {
576
+ const { getStyleCollector, router: _router } = this.context;
577
+ const styleCollector = getStyleCollector ? await getStyleCollector() : null;
578
+ path = withLeadingSlash(path);
579
+ const { createRouter, createMemoryHistory } = await import('@tanstack/react-router');
580
+ const router = createRouter(_router.options);
581
+ const { StartServer } = await import('@tanstack/start/server');
582
+ const memoryHistory = createMemoryHistory({
583
+ initialEntries: [path]
584
+ });
585
+ router.update({
586
+ history: memoryHistory
587
+ });
588
+ await router.load();
589
+ const helmetContext = {};
590
+ let app = /* @__PURE__ */ jsx(HelmetProvider, { context: helmetContext, children: /* @__PURE__ */ jsx(StartServer, { router }) });
591
+ const matchRoutes = router.matchRoutes(router.latestLocation);
592
+ const routerContext = {
593
+ loaderData: matchRoutes.map((item) => ({ id: item.routeId, loaderData: item.loaderData }))
594
+ };
595
+ if (styleCollector)
596
+ app = styleCollector.collect(app);
597
+ const appHTML = await renderStaticApp(app);
598
+ const jsdom = new JSDOM(appHTML);
599
+ const headElements = jsdom.window.document.querySelector(`#${META_CONTAINER_ID}`);
600
+ const { htmlAttributes, bodyAttributes, metaAttributes, styleTag } = extractHelmet(helmetContext, styleCollector);
601
+ if (headElements?.innerHTML) {
602
+ metaAttributes.unshift(headElements.innerHTML);
603
+ headElements.innerHTML = "";
604
+ }
605
+ return {
606
+ appHTML: jsdom.window.document.body.innerHTML,
607
+ htmlAttributes,
608
+ bodyAttributes,
609
+ metaAttributes,
610
+ styleTag,
611
+ routerContext
612
+ };
613
+ };
614
+ handleLoader = async (req, res) => {
615
+ const request = fromNodeRequest(req);
616
+ const url = new URL(request.url);
617
+ const routeId = decodeURIComponent(url.searchParams.get("_data"));
618
+ const { router } = this.context;
619
+ const matches = router.matchRoutes(url.pathname, Object.fromEntries(url.searchParams.entries()));
620
+ const _match = matches.find((m) => m.routeId === routeId);
621
+ const matchRoute = router.flatRoutes.find((item) => {
622
+ const matchRouteId = _match?.routeId === "__root__" ? "/" : _match?.routeId;
623
+ return item.id === matchRouteId;
624
+ });
625
+ if (!matchRoute) {
626
+ res.statusCode = 404;
627
+ res.end(`Route not found: ${routeId}`);
628
+ return;
629
+ }
630
+ const getLoaderContext = () => {
631
+ const {
632
+ params,
633
+ loaderDeps,
634
+ abortController,
635
+ context,
636
+ cause
637
+ } = _match;
638
+ const search = router.options.parseSearch(url.search);
639
+ const searchStr = router.options.stringifySearch(search);
640
+ return {
641
+ preload: false,
642
+ navigate: async () => {
643
+ },
644
+ params,
645
+ deps: loaderDeps,
646
+ abortController,
647
+ context,
648
+ location: {
649
+ ...url,
650
+ search,
651
+ searchStr,
652
+ state: {}
653
+ },
654
+ cause,
655
+ route: matchRoute
656
+ };
657
+ };
658
+ const loaderData = await matchRoute.options.loader?.(getLoaderContext());
659
+ if (!loaderData) {
660
+ res.statusCode = 200;
661
+ res.end(`There is no loader for the route: ${routeId}`);
662
+ return;
663
+ }
664
+ await toNodeRequest(json(loaderData), res);
665
+ };
666
+ }
667
+
668
+ function getAdapter(context) {
669
+ switch (context.routerType) {
670
+ case "remix":
671
+ return new RemixAdapter(context);
672
+ case "tanstack":
673
+ return new TanstackAdapter(context);
674
+ case "single-page":
675
+ return new SinglePageAdapter(context);
676
+ }
677
+ }
678
+
679
+ const dotVitedir = Number.parseInt(version$1) >= 5 ? [".vite"] : [];
680
+ function DefaultIncludedRoutes(paths, _routes) {
681
+ return paths.filter((i) => !i.includes(":") && !i.includes("*"));
682
+ }
683
+ async function build(ssgOptions = {}, viteConfig = {}) {
684
+ const mode = process.env.MODE || process.env.NODE_ENV || ssgOptions.mode || "production";
685
+ const config = await resolveConfig(viteConfig, "build", mode, mode);
686
+ const cwd = process.cwd();
687
+ const root = config.root || cwd;
688
+ const hash = Math.random().toString(36).substring(2, 12);
689
+ const ssgOut = join(root, ".vite-react-ssg-temp", hash);
690
+ const outDir = config.build.outDir || "dist";
691
+ const out = isAbsolute(outDir) ? outDir : join(root, outDir);
692
+ const configBase = config.base;
693
+ const {
694
+ script = "sync",
695
+ mock = false,
696
+ entry = await detectEntry(root),
697
+ formatting = "none",
698
+ crittersOptions = {},
699
+ includedRoutes: configIncludedRoutes = DefaultIncludedRoutes,
700
+ onBeforePageRender,
701
+ onPageRendered,
702
+ onFinished,
703
+ dirStyle = "flat",
704
+ includeAllRoutes = false,
705
+ format = "esm",
706
+ concurrency = 20,
707
+ rootContainerId = "root"
708
+ } = Object.assign({}, config.ssgOptions || {}, ssgOptions);
709
+ if (fs.existsSync(ssgOut))
710
+ await fs.remove(ssgOut);
711
+ const clientLogger = createLogger();
712
+ const loggerWarn = clientLogger.warn;
713
+ clientLogger.warn = (msg, options) => {
714
+ if (msg.includes("vite:resolve") && msg.includes("externalized for browser compatibility"))
715
+ return;
716
+ loggerWarn(msg, options);
717
+ };
718
+ buildLog("Build for client...");
719
+ await build$1(mergeConfig(viteConfig, {
720
+ build: {
721
+ manifest: true,
722
+ ssrManifest: true,
723
+ rollupOptions: {
724
+ input: {
725
+ app: join(root, "./index.html")
726
+ },
727
+ // @ts-expect-error rollup type
728
+ onLog(level, log, handler) {
729
+ if (log.message.includes("react-helmet-async"))
730
+ return;
731
+ handler(level, log);
732
+ }
733
+ }
734
+ },
735
+ customLogger: clientLogger,
736
+ mode: config.mode,
737
+ ssr: { noExternal: ["vite-react-ssg"] }
738
+ }));
739
+ if (mock) {
740
+ const { jsdomGlobal } = await import('../chunks/jsdomGlobal.mjs');
741
+ jsdomGlobal();
742
+ }
743
+ buildLog("Build for server...");
744
+ process.env.VITE_SSG = "true";
745
+ const ssrEntry = await resolveAlias(config, entry);
746
+ await build$1(mergeConfig(viteConfig, {
747
+ build: {
748
+ ssr: ssrEntry,
749
+ manifest: true,
750
+ outDir: ssgOut,
751
+ minify: false,
752
+ cssCodeSplit: false,
753
+ rollupOptions: {
754
+ output: format === "esm" ? {
755
+ entryFileNames: "[name].mjs",
756
+ format: "esm"
757
+ } : {
758
+ entryFileNames: "[name].cjs",
759
+ format: "cjs"
760
+ }
761
+ }
762
+ },
763
+ mode: config.mode,
764
+ ssr: { noExternal: ["vite-react-ssg", "vite-react-ssg/tanstack"] }
765
+ }));
766
+ const prefix = format === "esm" && process.platform === "win32" ? "file://" : "";
767
+ const ext = format === "esm" ? ".mjs" : ".cjs";
768
+ const serverEntry = join(prefix, ssgOut, parse(ssrEntry).name + ext);
769
+ const serverManifest = JSON.parse(await fs.readFile(join(ssgOut, ...dotVitedir, "manifest.json"), "utf-8"));
770
+ const _require = createRequire(import.meta.url);
771
+ const { createRoot, includedRoutes: serverEntryIncludedRoutes } = format === "esm" ? await import(serverEntry) : _require(serverEntry);
772
+ const includedRoutes = serverEntryIncludedRoutes || configIncludedRoutes;
773
+ const { routes } = await createRoot(false);
774
+ const { paths } = await routesToPaths(routes);
775
+ let routesPaths = includeAllRoutes ? paths : await includedRoutes(paths, routes || []);
776
+ routesPaths = DefaultIncludedRoutes(routesPaths);
777
+ routesPaths = Array.from(new Set(routesPaths));
778
+ buildLog("Rendering Pages...", routesPaths.length);
779
+ const critters = crittersOptions !== false ? await getCritters(outDir, { publicPath: configBase, ...crittersOptions }) : void 0;
780
+ if (critters)
781
+ console.log(`${gray("[vite-react-ssg]")} ${blue("Critical CSS generation enabled via `critters`")}`);
782
+ const ssrManifest = JSON.parse(await fs.readFile(join(out, ...dotVitedir, "ssr-manifest.json"), "utf-8"));
783
+ const manifest = JSON.parse(await fs.readFile(join(out, ...dotVitedir, "manifest.json"), "utf-8"));
784
+ let indexHTML = await fs.readFile(join(out, "index.html"), "utf-8");
785
+ indexHTML = rewriteScripts(indexHTML, script);
786
+ const queue = new PQueue({ concurrency });
787
+ const crittersQueue = new PQueue({ concurrency: 1 });
788
+ const staticLoaderDataManifest = {};
789
+ for (const path of routesPaths) {
790
+ queue.add(async () => {
791
+ try {
792
+ const appCtx = await createRoot(false, path);
793
+ const { base, routes: routes2, triggerOnSSRAppRendered, transformState = serializeState, app, routerType } = appCtx;
794
+ const transformedIndexHTML = await onBeforePageRender?.(path, indexHTML, appCtx) || indexHTML;
795
+ const fetchUrl = `${withTrailingSlash(base)}${removeLeadingSlash(path)}`;
796
+ const adapter = getAdapter(appCtx);
797
+ const assets = !app && routerType === "remix" ? await collectAssets({ routes: [...routes2], locationArg: fetchUrl, base, serverManifest, manifest, ssrManifest }) : /* @__PURE__ */ new Set();
798
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await adapter.render(path);
799
+ staticLoaderDataManifest[withLeadingSlash(path)] = routerContext?.loaderData;
800
+ await triggerOnSSRAppRendered?.(path, appHTML, appCtx);
801
+ const renderedHTML = await renderHTML({
802
+ rootContainerId,
803
+ appHTML,
804
+ indexHTML: transformedIndexHTML,
805
+ metaAttributes,
806
+ bodyAttributes,
807
+ htmlAttributes,
808
+ initialState: null
809
+ });
810
+ const jsdom = new JSDOM(renderedHTML);
811
+ renderPreloadLinks(jsdom.window.document, assets);
812
+ const html = jsdom.serialize();
813
+ let transformed = await onPageRendered?.(path, html, appCtx) || html;
814
+ transformed = transformed.replace(SCRIPT_COMMENT_PLACEHOLDER, `window.__VITE_REACT_SSG_HASH__ = '${hash}'`);
815
+ if (critters) {
816
+ transformed = await crittersQueue.add(() => critters.process(transformed));
817
+ transformed = transformed.replace(/<link\srel="stylesheet"/g, '<link rel="stylesheet" crossorigin');
818
+ }
819
+ if (styleTag)
820
+ transformed = transformed.replace("<head>", `<head>${styleTag}`);
821
+ const formatted = await formatHtml(transformed, formatting);
822
+ const relativeRouteFile = `${(path.endsWith("/") ? `${path}index` : path).replace(/^\//g, "")}.html`;
823
+ const filename = dirStyle === "nested" ? join(path.replace(/^\//g, ""), "index.html") : relativeRouteFile;
824
+ await fs.ensureDir(join(out, dirname(filename)));
825
+ await fs.writeFile(join(out, filename), formatted, "utf-8");
826
+ config.logger.info(
827
+ `${dim(`${outDir}/`)}${cyan(filename.padEnd(15, " "))} ${dim(getSize(formatted))}`
828
+ );
829
+ } catch (err) {
830
+ throw new Error(`${gray("[vite-react-ssg]")} ${red(`Error on page: ${cyan(path)}`)}
831
+ ${err.stack}`);
832
+ }
833
+ });
834
+ }
835
+ await queue.start().onIdle();
836
+ buildLog("Generating static loader data manifest...");
837
+ const staticLoaderDataManifestString = JSON.stringify(staticLoaderDataManifest, null, 0);
838
+ await fs.writeFile(join(out, `static-loader-data-manifest-${hash}.json`), staticLoaderDataManifestString);
839
+ config.logger.info(
840
+ `${dim(`${outDir}/`)}${cyan(`static-loader-data-manifest-${hash}.json`.padEnd(15, " "))} ${dim(getSize(staticLoaderDataManifestString))}`
841
+ );
842
+ await fs.remove(join(root, ".vite-react-ssg-temp"));
843
+ const pwaPlugin = config.plugins.find((i) => i.name === "vite-plugin-pwa")?.api;
844
+ if (pwaPlugin && !pwaPlugin.disabled && pwaPlugin.generateSW) {
845
+ buildLog("Regenerate PWA...");
846
+ await pwaPlugin.generateSW();
847
+ }
848
+ console.log(`
849
+ ${gray("[vite-react-ssg]")} ${green("Build finished.")}`);
850
+ await onFinished?.();
851
+ const waitInSeconds = 15;
852
+ const timeout = setTimeout(() => {
853
+ console.log(`${gray("[vite-react-ssg]")} ${yellow(`Build process still running after ${waitInSeconds}s`)}. There might be something misconfigured in your setup. Force exit.`);
854
+ process.exit(0);
855
+ }, waitInSeconds * 1e3);
856
+ timeout.unref();
857
+ }
858
+ function rewriteScripts(indexHTML, mode) {
859
+ if (!mode || mode === "sync")
860
+ return indexHTML;
861
+ return indexHTML.replace(/<script type="module" /g, `<script type="module" ${mode} `);
862
+ }
863
+ async function formatHtml(html, formatting) {
864
+ if (formatting === "prettify") {
865
+ try {
866
+ const prettier = (await import('prettier/esm/standalone.mjs')).default;
867
+ const parserHTML = (await import('prettier/esm/parser-html.mjs')).default;
868
+ return prettier.format(html, { semi: false, parser: "html", plugins: [parserHTML] });
869
+ } catch (e) {
870
+ console.error(`${gray("[vite-react-ssg]")} ${red(`Error formatting html: ${e?.message}`)}`);
871
+ return html;
872
+ }
873
+ }
874
+ return html;
875
+ }
876
+
877
+ function ssrServerPlugin({
878
+ template,
879
+ ssrEntry,
880
+ onBeforePageRender,
881
+ entry,
882
+ rootContainerId,
883
+ onPageRendered
884
+ }) {
885
+ return {
886
+ name: "vite-react-ssg:dev-server",
887
+ configureServer(server) {
888
+ const renderMiddleware = async (req, res, next) => {
889
+ try {
890
+ const url = req.originalUrl;
891
+ const createRoot = await server.ssrLoadModule(ssrEntry).then((m) => m.createRoot);
892
+ const appCtx = await createRoot(false, url);
893
+ const adapter = getAdapter(appCtx);
894
+ const { app, base } = appCtx;
895
+ const searchParams = new URLSearchParams(url.split("?")[1]);
896
+ if (!app && searchParams.has("_data")) {
897
+ return adapter.handleLoader(req, res);
898
+ }
899
+ const indexHTML = await server.transformIndexHtml(url, template);
900
+ const transformedIndexHTML = await onBeforePageRender?.(url, indexHTML, appCtx) || indexHTML;
901
+ const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag } = await adapter.render(stripBase(url, base));
902
+ metaAttributes.push(styleTag);
903
+ const mods = await Promise.all(
904
+ [ssrEntry, entry].map(async (entry2) => await server.moduleGraph.getModuleByUrl(entry2))
905
+ );
906
+ const assetsUrls = /* @__PURE__ */ new Set();
907
+ const collectAssets = async (mod) => {
908
+ if (!mod || !mod?.ssrTransformResult)
909
+ return;
910
+ const { deps = [], dynamicDeps = [] } = mod?.ssrTransformResult;
911
+ const allDeps = [...deps, ...dynamicDeps];
912
+ for (const dep of allDeps) {
913
+ if (dep.endsWith(".css") || dep.endsWith(".scss") || dep.endsWith(".sass") || dep.endsWith(".less")) {
914
+ assetsUrls.add(dep);
915
+ } else if (dep.endsWith(".ts") || dep.endsWith(".tsx")) {
916
+ const depModule = await server.moduleGraph.getModuleByUrl(dep);
917
+ depModule && await collectAssets(depModule);
918
+ }
919
+ }
920
+ };
921
+ await Promise.all(mods.map(async (mod) => collectAssets(mod)));
922
+ const preloadLink = [...assetsUrls].map((item) => createLink$1(joinUrlSegments(server.config.base, item)));
923
+ metaAttributes.push(...preloadLink);
924
+ const renderedHTML = await renderHTML({
925
+ rootContainerId,
926
+ appHTML,
927
+ indexHTML: transformedIndexHTML,
928
+ metaAttributes,
929
+ bodyAttributes,
930
+ htmlAttributes,
931
+ initialState: null
932
+ });
933
+ const transformed = await onPageRendered?.(url, renderedHTML, appCtx) || renderedHTML;
934
+ res.statusCode = 200;
935
+ res.setHeader("Content-Type", "text/html");
936
+ const isDev = "pluginContainer" in server;
937
+ const headers = isDev ? server.config.server.headers : server.config.preview.headers;
938
+ send(req, res, transformed, "html", { headers });
939
+ } catch (e) {
940
+ server.ssrFixStacktrace(e);
941
+ console.error(`[vite-react-ssg] error: ${e.stack}`);
942
+ res.statusCode = 500;
943
+ res.end(e.stack);
944
+ }
945
+ };
946
+ return () => {
947
+ server.middlewares.use(renderMiddleware);
948
+ };
949
+ }
950
+ };
951
+ }
952
+
953
+ async function dev(ssgOptions = {}, viteConfig = {}, customOptions) {
954
+ const mode = process.env.MODE || process.env.NODE_ENV || ssgOptions.mode || "development";
955
+ const config = await resolveConfig(viteConfig, "serve", mode, mode);
956
+ const cwd = process.cwd();
957
+ const root = config.root || cwd;
958
+ const {
959
+ entry = await detectEntry(root),
960
+ onBeforePageRender,
961
+ onPageRendered,
962
+ rootContainerId = "root",
963
+ mock = false
964
+ } = Object.assign({}, config.ssgOptions || {}, ssgOptions);
965
+ const ssrEntry = await resolveAlias(config, entry);
966
+ const template = await fs.readFile(join(root, "index.html"), "utf-8");
967
+ let viteServer;
968
+ globalThis.__ssr_start_time = performance.now();
969
+ createServer$1().catch((err) => {
970
+ console.error(
971
+ `${red(`failed to start server. error:`)}
972
+ ${err.stack}`
973
+ );
974
+ process.exit(1);
975
+ });
976
+ async function createServer$1() {
977
+ process.env.__DEV_MODE_SSR = "true";
978
+ if (mock) {
979
+ const { jsdomGlobal } = await import('../chunks/jsdomGlobal.mjs');
980
+ jsdomGlobal();
981
+ }
982
+ viteServer = await createServer(
983
+ mergeConfig(
984
+ {
985
+ ...viteConfig,
986
+ plugins: [
987
+ ...viteConfig.plugins ?? [],
988
+ ssrServerPlugin({
989
+ template,
990
+ ssrEntry,
991
+ onBeforePageRender,
992
+ onPageRendered,
993
+ entry,
994
+ rootContainerId
995
+ })
996
+ ]
997
+ },
998
+ {
999
+ ssr: { noExternal: ["vite-react-ssg"] }
1000
+ }
1001
+ )
1002
+ );
1003
+ await viteServer.listen();
1004
+ printServerInfo(viteServer, !!customOptions);
1005
+ viteServer.bindCLIShortcuts({ print: true });
1006
+ return viteServer;
1007
+ }
1008
+ }
1009
+ async function printServerInfo(server, onlyUrl = false) {
1010
+ if (onlyUrl)
1011
+ return server.printUrls();
1012
+ const info = server.config.logger.info;
1013
+ let ssrReadyMessage = " -- SSR";
1014
+ if (globalThis.__ssr_start_time) {
1015
+ ssrReadyMessage += ` ready in ${reset(bold(`${Math.round(
1016
+ // @ts-expect-error global var
1017
+ performance.now() - globalThis.__ssr_start_time
1018
+ )}ms`))}`;
1019
+ }
1020
+ info(
1021
+ `
1022
+ ${bgLightCyan(` VITE-REACT-SSG v${version} `)}`,
1023
+ { clear: !server.config.logger.hasWarned }
1024
+ );
1025
+ info(
1026
+ `${cyan(`
1027
+ VITE v${version$1}`) + dim(ssrReadyMessage)}
1028
+ `
1029
+ );
1030
+ info(
1031
+ green(" dev server running at:")
1032
+ );
1033
+ server.printUrls();
1034
+ }
1035
+
1036
+ export { build as b, dev as d };