@tanstack/start-plugin-core 1.166.12 → 1.166.13

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 (87) hide show
  1. package/dist/esm/build-sitemap.js +94 -123
  2. package/dist/esm/build-sitemap.js.map +1 -1
  3. package/dist/esm/constants.js +15 -20
  4. package/dist/esm/constants.js.map +1 -1
  5. package/dist/esm/dev-server-plugin/dev-styles.js +137 -150
  6. package/dist/esm/dev-server-plugin/dev-styles.js.map +1 -1
  7. package/dist/esm/dev-server-plugin/extract-html-scripts.js +16 -15
  8. package/dist/esm/dev-server-plugin/extract-html-scripts.js.map +1 -1
  9. package/dist/esm/dev-server-plugin/plugin.js +125 -195
  10. package/dist/esm/dev-server-plugin/plugin.js.map +1 -1
  11. package/dist/esm/import-protection-plugin/ast.js +6 -5
  12. package/dist/esm/import-protection-plugin/ast.js.map +1 -1
  13. package/dist/esm/import-protection-plugin/constants.js +20 -22
  14. package/dist/esm/import-protection-plugin/constants.js.map +1 -1
  15. package/dist/esm/import-protection-plugin/defaults.js +35 -25
  16. package/dist/esm/import-protection-plugin/defaults.js.map +1 -1
  17. package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js +93 -92
  18. package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js.map +1 -1
  19. package/dist/esm/import-protection-plugin/matchers.js +23 -24
  20. package/dist/esm/import-protection-plugin/matchers.js.map +1 -1
  21. package/dist/esm/import-protection-plugin/plugin.js +1045 -1361
  22. package/dist/esm/import-protection-plugin/plugin.js.map +1 -1
  23. package/dist/esm/import-protection-plugin/postCompileUsage.js +58 -55
  24. package/dist/esm/import-protection-plugin/postCompileUsage.js.map +1 -1
  25. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +187 -259
  26. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +1 -1
  27. package/dist/esm/import-protection-plugin/sourceLocation.js +238 -248
  28. package/dist/esm/import-protection-plugin/sourceLocation.js.map +1 -1
  29. package/dist/esm/import-protection-plugin/trace.js +173 -184
  30. package/dist/esm/import-protection-plugin/trace.js.map +1 -1
  31. package/dist/esm/import-protection-plugin/utils.js +132 -111
  32. package/dist/esm/import-protection-plugin/utils.js.map +1 -1
  33. package/dist/esm/import-protection-plugin/virtualModules.js +216 -196
  34. package/dist/esm/import-protection-plugin/virtualModules.js.map +1 -1
  35. package/dist/esm/index.js +2 -7
  36. package/dist/esm/load-env-plugin/plugin.js +12 -11
  37. package/dist/esm/load-env-plugin/plugin.js.map +1 -1
  38. package/dist/esm/output-directory.js +10 -10
  39. package/dist/esm/output-directory.js.map +1 -1
  40. package/dist/esm/plugin.js +275 -355
  41. package/dist/esm/plugin.js.map +1 -1
  42. package/dist/esm/post-server-build.js +39 -53
  43. package/dist/esm/post-server-build.js.map +1 -1
  44. package/dist/esm/prerender.js +177 -239
  45. package/dist/esm/prerender.js.map +1 -1
  46. package/dist/esm/preview-server-plugin/plugin.js +41 -44
  47. package/dist/esm/preview-server-plugin/plugin.js.map +1 -1
  48. package/dist/esm/queue.js +115 -126
  49. package/dist/esm/queue.js.map +1 -1
  50. package/dist/esm/resolve-entries.js +31 -32
  51. package/dist/esm/resolve-entries.js.map +1 -1
  52. package/dist/esm/schema.js +156 -179
  53. package/dist/esm/schema.js.map +1 -1
  54. package/dist/esm/start-compiler-plugin/compiler.js +655 -812
  55. package/dist/esm/start-compiler-plugin/compiler.js.map +1 -1
  56. package/dist/esm/start-compiler-plugin/handleClientOnlyJSX.js +25 -8
  57. package/dist/esm/start-compiler-plugin/handleClientOnlyJSX.js.map +1 -1
  58. package/dist/esm/start-compiler-plugin/handleCreateIsomorphicFn.js +22 -19
  59. package/dist/esm/start-compiler-plugin/handleCreateIsomorphicFn.js.map +1 -1
  60. package/dist/esm/start-compiler-plugin/handleCreateMiddleware.js +20 -22
  61. package/dist/esm/start-compiler-plugin/handleCreateMiddleware.js.map +1 -1
  62. package/dist/esm/start-compiler-plugin/handleCreateServerFn.js +187 -255
  63. package/dist/esm/start-compiler-plugin/handleCreateServerFn.js.map +1 -1
  64. package/dist/esm/start-compiler-plugin/handleEnvOnly.js +23 -33
  65. package/dist/esm/start-compiler-plugin/handleEnvOnly.js.map +1 -1
  66. package/dist/esm/start-compiler-plugin/plugin.js +247 -291
  67. package/dist/esm/start-compiler-plugin/plugin.js.map +1 -1
  68. package/dist/esm/start-compiler-plugin/utils.js +27 -27
  69. package/dist/esm/start-compiler-plugin/utils.js.map +1 -1
  70. package/dist/esm/start-manifest-plugin/manifestBuilder.js +272 -378
  71. package/dist/esm/start-manifest-plugin/manifestBuilder.js.map +1 -1
  72. package/dist/esm/start-manifest-plugin/plugin.js +35 -44
  73. package/dist/esm/start-manifest-plugin/plugin.js.map +1 -1
  74. package/dist/esm/start-router-plugin/constants.js +6 -5
  75. package/dist/esm/start-router-plugin/constants.js.map +1 -1
  76. package/dist/esm/start-router-plugin/generator-plugins/prerender-routes-plugin.js +24 -19
  77. package/dist/esm/start-router-plugin/generator-plugins/prerender-routes-plugin.js.map +1 -1
  78. package/dist/esm/start-router-plugin/generator-plugins/routes-manifest-plugin.js +28 -29
  79. package/dist/esm/start-router-plugin/generator-plugins/routes-manifest-plugin.js.map +1 -1
  80. package/dist/esm/start-router-plugin/plugin.js +146 -199
  81. package/dist/esm/start-router-plugin/plugin.js.map +1 -1
  82. package/dist/esm/start-router-plugin/pruneServerOnlySubtrees.js +32 -31
  83. package/dist/esm/start-router-plugin/pruneServerOnlySubtrees.js.map +1 -1
  84. package/dist/esm/utils.js +14 -14
  85. package/dist/esm/utils.js.map +1 -1
  86. package/package.json +7 -7
  87. package/dist/esm/index.js.map +0 -1
@@ -1,248 +1,186 @@
1
- import { promises } from "node:fs";
2
- import os from "node:os";
3
- import path__default from "pathe";
4
- import { joinURL, withTrailingSlash, withBase, withoutBase } from "ufo";
5
- import { VITE_ENVIRONMENT_NAMES } from "./constants.js";
6
1
  import { createLogger } from "./utils.js";
2
+ import { VITE_ENVIRONMENT_NAMES } from "./constants.js";
7
3
  import { Queue } from "./queue.js";
8
- async function prerender({
9
- startConfig,
10
- builder
11
- }) {
12
- const logger = createLogger("prerender");
13
- logger.info("Prerendering pages...");
14
- if (startConfig.prerender?.enabled) {
15
- let pages = startConfig.pages.length ? startConfig.pages : [{ path: "/" }];
16
- if (startConfig.prerender.autoStaticPathsDiscovery ?? true) {
17
- const pagesMap = new Map(pages.map((item) => [item.path, item]));
18
- const discoveredPages = globalThis.TSS_PRERENDABLE_PATHS || [];
19
- for (const page of discoveredPages) {
20
- if (!pagesMap.has(page.path)) {
21
- pagesMap.set(page.path, page);
22
- }
23
- }
24
- pages = Array.from(pagesMap.values());
25
- }
26
- startConfig.pages = pages;
27
- }
28
- const routerBasePath = joinURL("/", startConfig.router.basepath ?? "");
29
- const routerBaseUrl = new URL(routerBasePath, "http://localhost");
30
- startConfig.pages = validateAndNormalizePrerenderPages(
31
- startConfig.pages,
32
- routerBaseUrl
33
- );
34
- const serverEnv = builder.environments[VITE_ENVIRONMENT_NAMES.server];
35
- if (!serverEnv) {
36
- throw new Error(
37
- `Vite's "${VITE_ENVIRONMENT_NAMES.server}" environment not found`
38
- );
39
- }
40
- const clientEnv = builder.environments[VITE_ENVIRONMENT_NAMES.client];
41
- if (!clientEnv) {
42
- throw new Error(
43
- `Vite's "${VITE_ENVIRONMENT_NAMES.client}" environment not found`
44
- );
45
- }
46
- const outputDir = clientEnv.config.build.outDir;
47
- process.env.TSS_PRERENDERING = "true";
48
- const previewServer = await startPreviewServer(serverEnv.config);
49
- const baseUrl = getResolvedUrl(previewServer);
50
- const isRedirectResponse = (res) => {
51
- return res.status >= 300 && res.status < 400 && res.headers.get("location");
52
- };
53
- async function localFetch(path2, options, maxRedirects = 5) {
54
- const url = new URL(path2, baseUrl);
55
- const request = new Request(url, options);
56
- const response = await fetch(request);
57
- if (isRedirectResponse(response) && maxRedirects > 0) {
58
- const location = response.headers.get("location");
59
- if (location.startsWith("http://localhost") || location.startsWith("/")) {
60
- const newUrl = location.replace("http://localhost", "");
61
- return localFetch(newUrl, options, maxRedirects - 1);
62
- } else {
63
- logger.warn(`Skipping redirect to external location: ${location}`);
64
- }
65
- }
66
- return response;
67
- }
68
- try {
69
- const pages = await prerenderPages({ outputDir });
70
- logger.info(`Prerendered ${pages.length} pages:`);
71
- pages.forEach((page) => {
72
- logger.info(`- ${page}`);
73
- });
74
- } catch (error) {
75
- logger.error(error);
76
- } finally {
77
- await previewServer.close();
78
- }
79
- function extractLinks(html) {
80
- const linkRegex = /<a[^>]+href=["']([^"']+)["'][^>]*>/g;
81
- const links = [];
82
- let match;
83
- while ((match = linkRegex.exec(html)) !== null) {
84
- const href = match[1];
85
- if (href && (href.startsWith("/") || href.startsWith("./"))) {
86
- links.push(href);
87
- }
88
- }
89
- return links;
90
- }
91
- async function prerenderPages({ outputDir: outputDir2 }) {
92
- const seen = /* @__PURE__ */ new Set();
93
- const prerendered = /* @__PURE__ */ new Set();
94
- const retriesByPath = /* @__PURE__ */ new Map();
95
- const concurrency = startConfig.prerender?.concurrency ?? os.cpus().length;
96
- logger.info(`Concurrency: ${concurrency}`);
97
- const queue = new Queue({ concurrency });
98
- const routerBasePath2 = joinURL("/", startConfig.router.basepath ?? "");
99
- const routerBaseUrl2 = new URL(routerBasePath2, "http://localhost");
100
- startConfig.pages = validateAndNormalizePrerenderPages(
101
- startConfig.pages,
102
- routerBaseUrl2
103
- );
104
- startConfig.pages.forEach((page) => addCrawlPageTask(page));
105
- if (queue.isSettled()) {
106
- logger.info("No pages matched prerender filter; skipping.");
107
- return Array.from(prerendered);
108
- }
109
- await queue.start();
110
- return Array.from(prerendered);
111
- function addCrawlPageTask(page) {
112
- if (seen.has(page.path)) return;
113
- seen.add(page.path);
114
- if (page.fromCrawl) {
115
- startConfig.pages.push(page);
116
- }
117
- if (!(page.prerender?.enabled ?? true)) return;
118
- if (startConfig.prerender?.filter && !startConfig.prerender.filter(page))
119
- return;
120
- const prerenderOptions = {
121
- ...startConfig.prerender,
122
- ...page.prerender
123
- };
124
- queue.add(async () => {
125
- logger.info(`Crawling: ${page.path}`);
126
- const retries = retriesByPath.get(page.path) || 0;
127
- try {
128
- const res = await localFetch(
129
- withTrailingSlash(withBase(page.path, routerBasePath2)),
130
- {
131
- headers: {
132
- ...prerenderOptions.headers ?? {}
133
- }
134
- },
135
- prerenderOptions.maxRedirects
136
- );
137
- if (!res.ok) {
138
- if (isRedirectResponse(res)) {
139
- logger.warn(`Max redirects reached for ${page.path}`);
140
- }
141
- throw new Error(`Failed to fetch ${page.path}: ${res.statusText}`, {
142
- cause: res
143
- });
144
- }
145
- const cleanPagePath = (prerenderOptions.outputPath || page.path).split(/[?#]/)[0];
146
- const contentType = res.headers.get("content-type") || "";
147
- const isImplicitHTML = !cleanPagePath.endsWith(".html") && contentType.includes("html");
148
- const routeWithIndex = cleanPagePath.endsWith("/") ? cleanPagePath + "index" : cleanPagePath;
149
- const isSpaShell = startConfig.spa?.prerender.outputPath === cleanPagePath;
150
- let htmlPath;
151
- if (isSpaShell) {
152
- htmlPath = cleanPagePath + ".html";
153
- } else {
154
- if (cleanPagePath.endsWith("/") || (prerenderOptions.autoSubfolderIndex ?? true)) {
155
- htmlPath = joinURL(cleanPagePath, "index.html");
156
- } else {
157
- htmlPath = cleanPagePath + ".html";
158
- }
159
- }
160
- const filename = withoutBase(
161
- isImplicitHTML ? htmlPath : routeWithIndex,
162
- routerBasePath2
163
- );
164
- const html = await res.text();
165
- const filepath = path__default.join(outputDir2, filename);
166
- await promises.mkdir(path__default.dirname(filepath), {
167
- recursive: true
168
- });
169
- await promises.writeFile(filepath, html);
170
- prerendered.add(page.path);
171
- const newPage = await prerenderOptions.onSuccess?.({ page, html });
172
- if (newPage) {
173
- Object.assign(page, newPage);
174
- }
175
- if (prerenderOptions.crawlLinks ?? true) {
176
- const links = extractLinks(html);
177
- for (const link of links) {
178
- addCrawlPageTask({ path: link, fromCrawl: true });
179
- }
180
- }
181
- } catch (error) {
182
- if (retries < (prerenderOptions.retryCount ?? 0)) {
183
- logger.warn(`Encountered error, retrying: ${page.path} in 500ms`);
184
- await new Promise(
185
- (resolve) => setTimeout(resolve, prerenderOptions.retryDelay)
186
- );
187
- retriesByPath.set(page.path, retries + 1);
188
- addCrawlPageTask(page);
189
- } else {
190
- if (prerenderOptions.failOnError ?? true) {
191
- throw error;
192
- }
193
- }
194
- }
195
- });
196
- }
197
- }
4
+ import path from "pathe";
5
+ import { joinURL, withBase, withTrailingSlash, withoutBase } from "ufo";
6
+ import { promises } from "node:fs";
7
+ import os from "node:os";
8
+ //#region src/prerender.ts
9
+ async function prerender({ startConfig, builder }) {
10
+ const logger = createLogger("prerender");
11
+ logger.info("Prerendering pages...");
12
+ if (startConfig.prerender?.enabled) {
13
+ let pages = startConfig.pages.length ? startConfig.pages : [{ path: "/" }];
14
+ if (startConfig.prerender.autoStaticPathsDiscovery ?? true) {
15
+ const pagesMap = new Map(pages.map((item) => [item.path, item]));
16
+ const discoveredPages = globalThis.TSS_PRERENDABLE_PATHS || [];
17
+ for (const page of discoveredPages) if (!pagesMap.has(page.path)) pagesMap.set(page.path, page);
18
+ pages = Array.from(pagesMap.values());
19
+ }
20
+ startConfig.pages = pages;
21
+ }
22
+ const routerBasePath = joinURL("/", startConfig.router.basepath ?? "");
23
+ const routerBaseUrl = new URL(routerBasePath, "http://localhost");
24
+ startConfig.pages = validateAndNormalizePrerenderPages(startConfig.pages, routerBaseUrl);
25
+ const serverEnv = builder.environments[VITE_ENVIRONMENT_NAMES.server];
26
+ if (!serverEnv) throw new Error(`Vite's "${VITE_ENVIRONMENT_NAMES.server}" environment not found`);
27
+ const clientEnv = builder.environments[VITE_ENVIRONMENT_NAMES.client];
28
+ if (!clientEnv) throw new Error(`Vite's "${VITE_ENVIRONMENT_NAMES.client}" environment not found`);
29
+ const outputDir = clientEnv.config.build.outDir;
30
+ process.env.TSS_PRERENDERING = "true";
31
+ const previewServer = await startPreviewServer(serverEnv.config);
32
+ const baseUrl = getResolvedUrl(previewServer);
33
+ const isRedirectResponse = (res) => {
34
+ return res.status >= 300 && res.status < 400 && res.headers.get("location");
35
+ };
36
+ async function localFetch(path, options, maxRedirects = 5) {
37
+ const url = new URL(path, baseUrl);
38
+ const request = new Request(url, options);
39
+ const response = await fetch(request);
40
+ if (isRedirectResponse(response) && maxRedirects > 0) {
41
+ const location = response.headers.get("location");
42
+ if (location.startsWith("http://localhost") || location.startsWith("/")) return localFetch(location.replace("http://localhost", ""), options, maxRedirects - 1);
43
+ else logger.warn(`Skipping redirect to external location: ${location}`);
44
+ }
45
+ return response;
46
+ }
47
+ try {
48
+ const pages = await prerenderPages({ outputDir });
49
+ logger.info(`Prerendered ${pages.length} pages:`);
50
+ pages.forEach((page) => {
51
+ logger.info(`- ${page}`);
52
+ });
53
+ } catch (error) {
54
+ logger.error(error);
55
+ } finally {
56
+ await previewServer.close();
57
+ }
58
+ function extractLinks(html) {
59
+ const linkRegex = /<a[^>]+href=["']([^"']+)["'][^>]*>/g;
60
+ const links = [];
61
+ let match;
62
+ while ((match = linkRegex.exec(html)) !== null) {
63
+ const href = match[1];
64
+ if (href && (href.startsWith("/") || href.startsWith("./"))) links.push(href);
65
+ }
66
+ return links;
67
+ }
68
+ async function prerenderPages({ outputDir }) {
69
+ const seen = /* @__PURE__ */ new Set();
70
+ const prerendered = /* @__PURE__ */ new Set();
71
+ const retriesByPath = /* @__PURE__ */ new Map();
72
+ const concurrency = startConfig.prerender?.concurrency ?? os.cpus().length;
73
+ logger.info(`Concurrency: ${concurrency}`);
74
+ const queue = new Queue({ concurrency });
75
+ const routerBasePath = joinURL("/", startConfig.router.basepath ?? "");
76
+ const routerBaseUrl = new URL(routerBasePath, "http://localhost");
77
+ startConfig.pages = validateAndNormalizePrerenderPages(startConfig.pages, routerBaseUrl);
78
+ startConfig.pages.forEach((page) => addCrawlPageTask(page));
79
+ if (queue.isSettled()) {
80
+ logger.info("No pages matched prerender filter; skipping.");
81
+ return Array.from(prerendered);
82
+ }
83
+ await queue.start();
84
+ return Array.from(prerendered);
85
+ function addCrawlPageTask(page) {
86
+ if (seen.has(page.path)) return;
87
+ seen.add(page.path);
88
+ if (page.fromCrawl) startConfig.pages.push(page);
89
+ if (!(page.prerender?.enabled ?? true)) return;
90
+ if (startConfig.prerender?.filter && !startConfig.prerender.filter(page)) return;
91
+ const prerenderOptions = {
92
+ ...startConfig.prerender,
93
+ ...page.prerender
94
+ };
95
+ queue.add(async () => {
96
+ logger.info(`Crawling: ${page.path}`);
97
+ const retries = retriesByPath.get(page.path) || 0;
98
+ try {
99
+ const res = await localFetch(withTrailingSlash(withBase(page.path, routerBasePath)), { headers: { ...prerenderOptions.headers ?? {} } }, prerenderOptions.maxRedirects);
100
+ if (!res.ok) {
101
+ if (isRedirectResponse(res)) logger.warn(`Max redirects reached for ${page.path}`);
102
+ throw new Error(`Failed to fetch ${page.path}: ${res.statusText}`, { cause: res });
103
+ }
104
+ const cleanPagePath = (prerenderOptions.outputPath || page.path).split(/[?#]/)[0];
105
+ const contentType = res.headers.get("content-type") || "";
106
+ const isImplicitHTML = !cleanPagePath.endsWith(".html") && contentType.includes("html");
107
+ const routeWithIndex = cleanPagePath.endsWith("/") ? cleanPagePath + "index" : cleanPagePath;
108
+ const isSpaShell = startConfig.spa?.prerender.outputPath === cleanPagePath;
109
+ let htmlPath;
110
+ if (isSpaShell) htmlPath = cleanPagePath + ".html";
111
+ else if (cleanPagePath.endsWith("/") || (prerenderOptions.autoSubfolderIndex ?? true)) htmlPath = joinURL(cleanPagePath, "index.html");
112
+ else htmlPath = cleanPagePath + ".html";
113
+ const filename = withoutBase(isImplicitHTML ? htmlPath : routeWithIndex, routerBasePath);
114
+ const html = await res.text();
115
+ const filepath = path.join(outputDir, filename);
116
+ await promises.mkdir(path.dirname(filepath), { recursive: true });
117
+ await promises.writeFile(filepath, html);
118
+ prerendered.add(page.path);
119
+ const newPage = await prerenderOptions.onSuccess?.({
120
+ page,
121
+ html
122
+ });
123
+ if (newPage) Object.assign(page, newPage);
124
+ if (prerenderOptions.crawlLinks ?? true) {
125
+ const links = extractLinks(html);
126
+ for (const link of links) addCrawlPageTask({
127
+ path: link,
128
+ fromCrawl: true
129
+ });
130
+ }
131
+ } catch (error) {
132
+ if (retries < (prerenderOptions.retryCount ?? 0)) {
133
+ logger.warn(`Encountered error, retrying: ${page.path} in 500ms`);
134
+ await new Promise((resolve) => setTimeout(resolve, prerenderOptions.retryDelay));
135
+ retriesByPath.set(page.path, retries + 1);
136
+ addCrawlPageTask(page);
137
+ } else if (prerenderOptions.failOnError ?? true) throw error;
138
+ }
139
+ });
140
+ }
141
+ }
198
142
  }
199
143
  async function startPreviewServer(viteConfig) {
200
- const vite = await import("vite");
201
- try {
202
- return await vite.preview({
203
- configFile: viteConfig.configFile,
204
- preview: {
205
- port: 0,
206
- open: false
207
- }
208
- });
209
- } catch (error) {
210
- throw new Error(
211
- "Failed to start the Vite preview server for prerendering",
212
- {
213
- cause: error
214
- }
215
- );
216
- }
144
+ const vite = await import("vite");
145
+ try {
146
+ return await vite.preview({
147
+ configFile: viteConfig.configFile,
148
+ preview: {
149
+ port: 0,
150
+ open: false
151
+ }
152
+ });
153
+ } catch (error) {
154
+ throw new Error("Failed to start the Vite preview server for prerendering", { cause: error });
155
+ }
217
156
  }
218
157
  function getResolvedUrl(previewServer) {
219
- const baseUrl = previewServer.resolvedUrls?.local[0];
220
- if (!baseUrl) {
221
- throw new Error("No resolved URL is available from the Vite preview server");
222
- }
223
- return new URL(baseUrl);
158
+ const baseUrl = previewServer.resolvedUrls?.local[0];
159
+ if (!baseUrl) throw new Error("No resolved URL is available from the Vite preview server");
160
+ return new URL(baseUrl);
224
161
  }
162
+ /**
163
+ * Validates and normalizes prerender page paths to ensure they are relative
164
+ * (no protocol/host) and returns normalized Page objects with cleaned paths.
165
+ * Preserves unicode characters by decoding the pathname after URL validation.
166
+ */
225
167
  function validateAndNormalizePrerenderPages(pages, routerBaseUrl) {
226
- return pages.map((page) => {
227
- let url;
228
- try {
229
- url = new URL(page.path, routerBaseUrl);
230
- } catch (err) {
231
- throw new Error(`prerender page path must be relative: ${page.path}`, {
232
- cause: err
233
- });
234
- }
235
- if (url.origin !== "http://localhost") {
236
- throw new Error(`prerender page path must be relative: ${page.path}`);
237
- }
238
- const decodedPathname = decodeURIComponent(url.pathname);
239
- return {
240
- ...page,
241
- path: decodedPathname + url.search + url.hash
242
- };
243
- });
168
+ return pages.map((page) => {
169
+ let url;
170
+ try {
171
+ url = new URL(page.path, routerBaseUrl);
172
+ } catch (err) {
173
+ throw new Error(`prerender page path must be relative: ${page.path}`, { cause: err });
174
+ }
175
+ if (url.origin !== "http://localhost") throw new Error(`prerender page path must be relative: ${page.path}`);
176
+ const decodedPathname = decodeURIComponent(url.pathname);
177
+ return {
178
+ ...page,
179
+ path: decodedPathname + url.search + url.hash
180
+ };
181
+ });
244
182
  }
245
- export {
246
- prerender
247
- };
248
- //# sourceMappingURL=prerender.js.map
183
+ //#endregion
184
+ export { prerender };
185
+
186
+ //# sourceMappingURL=prerender.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"prerender.js","sources":["../../src/prerender.ts"],"sourcesContent":["import { promises as fsp } from 'node:fs'\nimport os from 'node:os'\nimport path from 'pathe'\nimport { joinURL, withBase, withTrailingSlash, withoutBase } from 'ufo'\nimport { VITE_ENVIRONMENT_NAMES } from './constants'\nimport { createLogger } from './utils'\nimport { Queue } from './queue'\nimport type { PreviewServer, ResolvedConfig, ViteBuilder } from 'vite'\nimport type { Page, TanStackStartOutputConfig } from './schema'\n\nexport async function prerender({\n startConfig,\n builder,\n}: {\n startConfig: TanStackStartOutputConfig\n builder: ViteBuilder\n}) {\n const logger = createLogger('prerender')\n logger.info('Prerendering pages...')\n\n // If prerender is enabled\n if (startConfig.prerender?.enabled) {\n // default to root page if no pages are defined\n let pages = startConfig.pages.length ? startConfig.pages : [{ path: '/' }]\n\n if (startConfig.prerender.autoStaticPathsDiscovery ?? true) {\n // merge discovered static pages with user-defined pages\n const pagesMap = new Map(pages.map((item) => [item.path, item]))\n const discoveredPages = globalThis.TSS_PRERENDABLE_PATHS || []\n\n for (const page of discoveredPages) {\n if (!pagesMap.has(page.path)) {\n pagesMap.set(page.path, page)\n }\n }\n\n pages = Array.from(pagesMap.values())\n }\n\n startConfig.pages = pages\n }\n\n const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')\n const routerBaseUrl = new URL(routerBasePath, 'http://localhost')\n\n // Enforce that prerender page paths are relative/path-based (no protocol/host)\n startConfig.pages = validateAndNormalizePrerenderPages(\n startConfig.pages,\n routerBaseUrl,\n )\n\n const serverEnv = builder.environments[VITE_ENVIRONMENT_NAMES.server]\n\n if (!serverEnv) {\n throw new Error(\n `Vite's \"${VITE_ENVIRONMENT_NAMES.server}\" environment not found`,\n )\n }\n\n const clientEnv = builder.environments[VITE_ENVIRONMENT_NAMES.client]\n if (!clientEnv) {\n throw new Error(\n `Vite's \"${VITE_ENVIRONMENT_NAMES.client}\" environment not found`,\n )\n }\n\n const outputDir = clientEnv.config.build.outDir\n\n process.env.TSS_PRERENDERING = 'true'\n\n // Start Vite preview server instead of importing module\n const previewServer = await startPreviewServer(serverEnv.config)\n const baseUrl = getResolvedUrl(previewServer)\n\n const isRedirectResponse = (res: Response) => {\n return res.status >= 300 && res.status < 400 && res.headers.get('location')\n }\n async function localFetch(\n path: string,\n options?: RequestInit,\n maxRedirects: number = 5,\n ): Promise<Response> {\n const url = new URL(path, baseUrl)\n const request = new Request(url, options)\n const response = await fetch(request)\n\n if (isRedirectResponse(response) && maxRedirects > 0) {\n const location = response.headers.get('location')!\n if (location.startsWith('http://localhost') || location.startsWith('/')) {\n const newUrl = location.replace('http://localhost', '')\n return localFetch(newUrl, options, maxRedirects - 1)\n } else {\n logger.warn(`Skipping redirect to external location: ${location}`)\n }\n }\n\n return response\n }\n\n try {\n // Crawl all pages\n const pages = await prerenderPages({ outputDir })\n\n logger.info(`Prerendered ${pages.length} pages:`)\n pages.forEach((page) => {\n logger.info(`- ${page}`)\n })\n } catch (error) {\n logger.error(error)\n } finally {\n await previewServer.close()\n }\n\n function extractLinks(html: string): Array<string> {\n const linkRegex = /<a[^>]+href=[\"']([^\"']+)[\"'][^>]*>/g\n const links: Array<string> = []\n let match\n\n while ((match = linkRegex.exec(html)) !== null) {\n const href = match[1]\n if (href && (href.startsWith('/') || href.startsWith('./'))) {\n links.push(href)\n }\n }\n\n return links\n }\n\n async function prerenderPages({ outputDir }: { outputDir: string }) {\n const seen = new Set<string>()\n const prerendered = new Set<string>()\n const retriesByPath = new Map<string, number>()\n const concurrency = startConfig.prerender?.concurrency ?? os.cpus().length\n logger.info(`Concurrency: ${concurrency}`)\n const queue = new Queue({ concurrency })\n const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')\n\n // Normalize discovered pages and enforce path-only entries\n const routerBaseUrl = new URL(routerBasePath, 'http://localhost')\n startConfig.pages = validateAndNormalizePrerenderPages(\n startConfig.pages,\n routerBaseUrl,\n )\n\n startConfig.pages.forEach((page) => addCrawlPageTask(page))\n\n if (queue.isSettled()) {\n logger.info('No pages matched prerender filter; skipping.')\n return Array.from(prerendered)\n }\n\n await queue.start()\n\n return Array.from(prerendered)\n\n function addCrawlPageTask(page: Page) {\n // Was the page already seen?\n if (seen.has(page.path)) return\n\n // Add the page to the seen set\n seen.add(page.path)\n\n if (page.fromCrawl) {\n startConfig.pages.push(page)\n }\n\n // If not enabled, skip\n if (!(page.prerender?.enabled ?? true)) return\n\n // If there is a filter link, check if the page should be prerendered\n if (startConfig.prerender?.filter && !startConfig.prerender.filter(page))\n return\n\n // Resolve the merged default and page-specific prerender options\n const prerenderOptions = {\n ...startConfig.prerender,\n ...page.prerender,\n }\n\n // Add the task\n queue.add(async () => {\n logger.info(`Crawling: ${page.path}`)\n const retries = retriesByPath.get(page.path) || 0\n try {\n // Fetch the route\n\n const res = await localFetch(\n withTrailingSlash(withBase(page.path, routerBasePath)),\n {\n headers: {\n ...(prerenderOptions.headers ?? {}),\n },\n },\n prerenderOptions.maxRedirects,\n )\n\n if (!res.ok) {\n if (isRedirectResponse(res)) {\n logger.warn(`Max redirects reached for ${page.path}`)\n }\n throw new Error(`Failed to fetch ${page.path}: ${res.statusText}`, {\n cause: res,\n })\n }\n\n const cleanPagePath = (\n prerenderOptions.outputPath || page.path\n ).split(/[?#]/)[0]!\n\n // Guess route type and populate fileName\n const contentType = res.headers.get('content-type') || ''\n const isImplicitHTML =\n !cleanPagePath.endsWith('.html') && contentType.includes('html')\n\n const routeWithIndex = cleanPagePath.endsWith('/')\n ? cleanPagePath + 'index'\n : cleanPagePath\n\n const isSpaShell =\n startConfig.spa?.prerender.outputPath === cleanPagePath\n\n let htmlPath: string\n if (isSpaShell) {\n // For SPA shell, ignore autoSubfolderIndex option\n htmlPath = cleanPagePath + '.html'\n } else {\n if (\n cleanPagePath.endsWith('/') ||\n (prerenderOptions.autoSubfolderIndex ?? true)\n ) {\n htmlPath = joinURL(cleanPagePath, 'index.html')\n } else {\n htmlPath = cleanPagePath + '.html'\n }\n }\n\n const filename = withoutBase(\n isImplicitHTML ? htmlPath : routeWithIndex,\n routerBasePath,\n )\n\n const html = await res.text()\n\n const filepath = path.join(outputDir, filename)\n\n await fsp.mkdir(path.dirname(filepath), {\n recursive: true,\n })\n\n await fsp.writeFile(filepath, html)\n\n prerendered.add(page.path)\n\n const newPage = await prerenderOptions.onSuccess?.({ page, html })\n\n if (newPage) {\n Object.assign(page, newPage)\n }\n\n // Find new links\n if (prerenderOptions.crawlLinks ?? true) {\n const links = extractLinks(html)\n for (const link of links) {\n addCrawlPageTask({ path: link, fromCrawl: true })\n }\n }\n } catch (error) {\n if (retries < (prerenderOptions.retryCount ?? 0)) {\n logger.warn(`Encountered error, retrying: ${page.path} in 500ms`)\n await new Promise((resolve) =>\n setTimeout(resolve, prerenderOptions.retryDelay),\n )\n retriesByPath.set(page.path, retries + 1)\n addCrawlPageTask(page)\n } else {\n if (prerenderOptions.failOnError ?? true) {\n throw error\n }\n }\n }\n })\n }\n }\n}\n\nasync function startPreviewServer(\n viteConfig: ResolvedConfig,\n): Promise<PreviewServer> {\n const vite = await import('vite')\n\n try {\n return await vite.preview({\n configFile: viteConfig.configFile,\n preview: {\n port: 0,\n open: false,\n },\n })\n } catch (error) {\n throw new Error(\n 'Failed to start the Vite preview server for prerendering',\n {\n cause: error,\n },\n )\n }\n}\n\nfunction getResolvedUrl(previewServer: PreviewServer): URL {\n const baseUrl = previewServer.resolvedUrls?.local[0]\n\n if (!baseUrl) {\n throw new Error('No resolved URL is available from the Vite preview server')\n }\n\n return new URL(baseUrl)\n}\n\n/**\n * Validates and normalizes prerender page paths to ensure they are relative\n * (no protocol/host) and returns normalized Page objects with cleaned paths.\n * Preserves unicode characters by decoding the pathname after URL validation.\n */\nfunction validateAndNormalizePrerenderPages(\n pages: Array<Page>,\n routerBaseUrl: URL,\n): Array<Page> {\n return pages.map((page) => {\n let url: URL\n try {\n url = new URL(page.path, routerBaseUrl)\n } catch (err) {\n throw new Error(`prerender page path must be relative: ${page.path}`, {\n cause: err,\n })\n }\n\n if (url.origin !== 'http://localhost') {\n throw new Error(`prerender page path must be relative: ${page.path}`)\n }\n\n // Decode the pathname to preserve unicode characters (e.g., /대한민국)\n // The URL constructor encodes non-ASCII characters, but we want to keep\n // the original unicode form for filesystem paths\n const decodedPathname = decodeURIComponent(url.pathname)\n\n return {\n ...page,\n path: decodedPathname + url.search + url.hash,\n }\n })\n}\n"],"names":["path","outputDir","routerBasePath","routerBaseUrl","fsp"],"mappings":"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"}
1
+ {"version":3,"file":"prerender.js","names":[],"sources":["../../src/prerender.ts"],"sourcesContent":["import { promises as fsp } from 'node:fs'\nimport os from 'node:os'\nimport path from 'pathe'\nimport { joinURL, withBase, withTrailingSlash, withoutBase } from 'ufo'\nimport { VITE_ENVIRONMENT_NAMES } from './constants'\nimport { createLogger } from './utils'\nimport { Queue } from './queue'\nimport type { PreviewServer, ResolvedConfig, ViteBuilder } from 'vite'\nimport type { Page, TanStackStartOutputConfig } from './schema'\n\nexport async function prerender({\n startConfig,\n builder,\n}: {\n startConfig: TanStackStartOutputConfig\n builder: ViteBuilder\n}) {\n const logger = createLogger('prerender')\n logger.info('Prerendering pages...')\n\n // If prerender is enabled\n if (startConfig.prerender?.enabled) {\n // default to root page if no pages are defined\n let pages = startConfig.pages.length ? startConfig.pages : [{ path: '/' }]\n\n if (startConfig.prerender.autoStaticPathsDiscovery ?? true) {\n // merge discovered static pages with user-defined pages\n const pagesMap = new Map(pages.map((item) => [item.path, item]))\n const discoveredPages = globalThis.TSS_PRERENDABLE_PATHS || []\n\n for (const page of discoveredPages) {\n if (!pagesMap.has(page.path)) {\n pagesMap.set(page.path, page)\n }\n }\n\n pages = Array.from(pagesMap.values())\n }\n\n startConfig.pages = pages\n }\n\n const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')\n const routerBaseUrl = new URL(routerBasePath, 'http://localhost')\n\n // Enforce that prerender page paths are relative/path-based (no protocol/host)\n startConfig.pages = validateAndNormalizePrerenderPages(\n startConfig.pages,\n routerBaseUrl,\n )\n\n const serverEnv = builder.environments[VITE_ENVIRONMENT_NAMES.server]\n\n if (!serverEnv) {\n throw new Error(\n `Vite's \"${VITE_ENVIRONMENT_NAMES.server}\" environment not found`,\n )\n }\n\n const clientEnv = builder.environments[VITE_ENVIRONMENT_NAMES.client]\n if (!clientEnv) {\n throw new Error(\n `Vite's \"${VITE_ENVIRONMENT_NAMES.client}\" environment not found`,\n )\n }\n\n const outputDir = clientEnv.config.build.outDir\n\n process.env.TSS_PRERENDERING = 'true'\n\n // Start Vite preview server instead of importing module\n const previewServer = await startPreviewServer(serverEnv.config)\n const baseUrl = getResolvedUrl(previewServer)\n\n const isRedirectResponse = (res: Response) => {\n return res.status >= 300 && res.status < 400 && res.headers.get('location')\n }\n async function localFetch(\n path: string,\n options?: RequestInit,\n maxRedirects: number = 5,\n ): Promise<Response> {\n const url = new URL(path, baseUrl)\n const request = new Request(url, options)\n const response = await fetch(request)\n\n if (isRedirectResponse(response) && maxRedirects > 0) {\n const location = response.headers.get('location')!\n if (location.startsWith('http://localhost') || location.startsWith('/')) {\n const newUrl = location.replace('http://localhost', '')\n return localFetch(newUrl, options, maxRedirects - 1)\n } else {\n logger.warn(`Skipping redirect to external location: ${location}`)\n }\n }\n\n return response\n }\n\n try {\n // Crawl all pages\n const pages = await prerenderPages({ outputDir })\n\n logger.info(`Prerendered ${pages.length} pages:`)\n pages.forEach((page) => {\n logger.info(`- ${page}`)\n })\n } catch (error) {\n logger.error(error)\n } finally {\n await previewServer.close()\n }\n\n function extractLinks(html: string): Array<string> {\n const linkRegex = /<a[^>]+href=[\"']([^\"']+)[\"'][^>]*>/g\n const links: Array<string> = []\n let match\n\n while ((match = linkRegex.exec(html)) !== null) {\n const href = match[1]\n if (href && (href.startsWith('/') || href.startsWith('./'))) {\n links.push(href)\n }\n }\n\n return links\n }\n\n async function prerenderPages({ outputDir }: { outputDir: string }) {\n const seen = new Set<string>()\n const prerendered = new Set<string>()\n const retriesByPath = new Map<string, number>()\n const concurrency = startConfig.prerender?.concurrency ?? os.cpus().length\n logger.info(`Concurrency: ${concurrency}`)\n const queue = new Queue({ concurrency })\n const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')\n\n // Normalize discovered pages and enforce path-only entries\n const routerBaseUrl = new URL(routerBasePath, 'http://localhost')\n startConfig.pages = validateAndNormalizePrerenderPages(\n startConfig.pages,\n routerBaseUrl,\n )\n\n startConfig.pages.forEach((page) => addCrawlPageTask(page))\n\n if (queue.isSettled()) {\n logger.info('No pages matched prerender filter; skipping.')\n return Array.from(prerendered)\n }\n\n await queue.start()\n\n return Array.from(prerendered)\n\n function addCrawlPageTask(page: Page) {\n // Was the page already seen?\n if (seen.has(page.path)) return\n\n // Add the page to the seen set\n seen.add(page.path)\n\n if (page.fromCrawl) {\n startConfig.pages.push(page)\n }\n\n // If not enabled, skip\n if (!(page.prerender?.enabled ?? true)) return\n\n // If there is a filter link, check if the page should be prerendered\n if (startConfig.prerender?.filter && !startConfig.prerender.filter(page))\n return\n\n // Resolve the merged default and page-specific prerender options\n const prerenderOptions = {\n ...startConfig.prerender,\n ...page.prerender,\n }\n\n // Add the task\n queue.add(async () => {\n logger.info(`Crawling: ${page.path}`)\n const retries = retriesByPath.get(page.path) || 0\n try {\n // Fetch the route\n\n const res = await localFetch(\n withTrailingSlash(withBase(page.path, routerBasePath)),\n {\n headers: {\n ...(prerenderOptions.headers ?? {}),\n },\n },\n prerenderOptions.maxRedirects,\n )\n\n if (!res.ok) {\n if (isRedirectResponse(res)) {\n logger.warn(`Max redirects reached for ${page.path}`)\n }\n throw new Error(`Failed to fetch ${page.path}: ${res.statusText}`, {\n cause: res,\n })\n }\n\n const cleanPagePath = (\n prerenderOptions.outputPath || page.path\n ).split(/[?#]/)[0]!\n\n // Guess route type and populate fileName\n const contentType = res.headers.get('content-type') || ''\n const isImplicitHTML =\n !cleanPagePath.endsWith('.html') && contentType.includes('html')\n\n const routeWithIndex = cleanPagePath.endsWith('/')\n ? cleanPagePath + 'index'\n : cleanPagePath\n\n const isSpaShell =\n startConfig.spa?.prerender.outputPath === cleanPagePath\n\n let htmlPath: string\n if (isSpaShell) {\n // For SPA shell, ignore autoSubfolderIndex option\n htmlPath = cleanPagePath + '.html'\n } else {\n if (\n cleanPagePath.endsWith('/') ||\n (prerenderOptions.autoSubfolderIndex ?? true)\n ) {\n htmlPath = joinURL(cleanPagePath, 'index.html')\n } else {\n htmlPath = cleanPagePath + '.html'\n }\n }\n\n const filename = withoutBase(\n isImplicitHTML ? htmlPath : routeWithIndex,\n routerBasePath,\n )\n\n const html = await res.text()\n\n const filepath = path.join(outputDir, filename)\n\n await fsp.mkdir(path.dirname(filepath), {\n recursive: true,\n })\n\n await fsp.writeFile(filepath, html)\n\n prerendered.add(page.path)\n\n const newPage = await prerenderOptions.onSuccess?.({ page, html })\n\n if (newPage) {\n Object.assign(page, newPage)\n }\n\n // Find new links\n if (prerenderOptions.crawlLinks ?? true) {\n const links = extractLinks(html)\n for (const link of links) {\n addCrawlPageTask({ path: link, fromCrawl: true })\n }\n }\n } catch (error) {\n if (retries < (prerenderOptions.retryCount ?? 0)) {\n logger.warn(`Encountered error, retrying: ${page.path} in 500ms`)\n await new Promise((resolve) =>\n setTimeout(resolve, prerenderOptions.retryDelay),\n )\n retriesByPath.set(page.path, retries + 1)\n addCrawlPageTask(page)\n } else {\n if (prerenderOptions.failOnError ?? true) {\n throw error\n }\n }\n }\n })\n }\n }\n}\n\nasync function startPreviewServer(\n viteConfig: ResolvedConfig,\n): Promise<PreviewServer> {\n const vite = await import('vite')\n\n try {\n return await vite.preview({\n configFile: viteConfig.configFile,\n preview: {\n port: 0,\n open: false,\n },\n })\n } catch (error) {\n throw new Error(\n 'Failed to start the Vite preview server for prerendering',\n {\n cause: error,\n },\n )\n }\n}\n\nfunction getResolvedUrl(previewServer: PreviewServer): URL {\n const baseUrl = previewServer.resolvedUrls?.local[0]\n\n if (!baseUrl) {\n throw new Error('No resolved URL is available from the Vite preview server')\n }\n\n return new URL(baseUrl)\n}\n\n/**\n * Validates and normalizes prerender page paths to ensure they are relative\n * (no protocol/host) and returns normalized Page objects with cleaned paths.\n * Preserves unicode characters by decoding the pathname after URL validation.\n */\nfunction validateAndNormalizePrerenderPages(\n pages: Array<Page>,\n routerBaseUrl: URL,\n): Array<Page> {\n return pages.map((page) => {\n let url: URL\n try {\n url = new URL(page.path, routerBaseUrl)\n } catch (err) {\n throw new Error(`prerender page path must be relative: ${page.path}`, {\n cause: err,\n })\n }\n\n if (url.origin !== 'http://localhost') {\n throw new Error(`prerender page path must be relative: ${page.path}`)\n }\n\n // Decode the pathname to preserve unicode characters (e.g., /대한민국)\n // The URL constructor encodes non-ASCII characters, but we want to keep\n // the original unicode form for filesystem paths\n const decodedPathname = decodeURIComponent(url.pathname)\n\n return {\n ...page,\n path: decodedPathname + url.search + url.hash,\n }\n })\n}\n"],"mappings":"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"}
@@ -1,48 +1,45 @@
1
- import { pathToFileURL } from "node:url";
2
- import { basename, extname, join } from "pathe";
3
- import { NodeRequest, sendNodeResponse } from "srvx/node";
4
- import { joinURL } from "ufo";
5
1
  import { VITE_ENVIRONMENT_NAMES } from "../constants.js";
6
2
  import { getServerOutputDirectory } from "../output-directory.js";
3
+ import { basename, extname, join } from "pathe";
4
+ import { joinURL } from "ufo";
5
+ import { NodeRequest, sendNodeResponse } from "srvx/node";
6
+ import { pathToFileURL } from "node:url";
7
+ //#region src/preview-server-plugin/plugin.ts
7
8
  function previewServerPlugin() {
8
- return {
9
- name: "tanstack-start-core:preview-server",
10
- configurePreviewServer: {
11
- // Run last so platform plugins (Cloudflare, Vercel, etc.) can register their handlers first
12
- order: "post",
13
- handler(server) {
14
- return () => {
15
- let serverBuild = null;
16
- server.middlewares.use(async (req, res, next) => {
17
- try {
18
- if (!serverBuild) {
19
- const serverEnv = server.config.environments[VITE_ENVIRONMENT_NAMES.server];
20
- const serverInput = serverEnv?.build.rollupOptions.input ?? "server";
21
- if (typeof serverInput !== "string") {
22
- throw new Error("Invalid server input. Expected a string.");
23
- }
24
- const outputFilename = `${basename(serverInput, extname(serverInput))}.js`;
25
- const serverOutputDir = getServerOutputDirectory(server.config);
26
- const serverEntryPath = join(serverOutputDir, outputFilename);
27
- const imported = await import(pathToFileURL(serverEntryPath).toString());
28
- serverBuild = imported.default;
29
- }
30
- req.url = joinURL(server.config.base, req.url ?? "/");
31
- const webReq = new NodeRequest({ req, res });
32
- const webRes = await serverBuild.fetch(webReq);
33
- res.setHeaders(webRes.headers);
34
- res.writeHead(webRes.status, webRes.statusText);
35
- return sendNodeResponse(res, webRes);
36
- } catch (error) {
37
- next(error);
38
- }
39
- });
40
- };
41
- }
42
- }
43
- };
9
+ return {
10
+ name: "tanstack-start-core:preview-server",
11
+ configurePreviewServer: {
12
+ order: "post",
13
+ handler(server) {
14
+ return () => {
15
+ let serverBuild = null;
16
+ server.middlewares.use(async (req, res, next) => {
17
+ try {
18
+ if (!serverBuild) {
19
+ const serverInput = server.config.environments[VITE_ENVIRONMENT_NAMES.server]?.build.rollupOptions.input ?? "server";
20
+ if (typeof serverInput !== "string") throw new Error("Invalid server input. Expected a string.");
21
+ const outputFilename = `${basename(serverInput, extname(serverInput))}.js`;
22
+ serverBuild = (await import(pathToFileURL(join(getServerOutputDirectory(server.config), outputFilename)).toString())).default;
23
+ }
24
+ req.url = joinURL(server.config.base, req.url ?? "/");
25
+ const webReq = new NodeRequest({
26
+ req,
27
+ res
28
+ });
29
+ const webRes = await serverBuild.fetch(webReq);
30
+ res.setHeaders(webRes.headers);
31
+ res.writeHead(webRes.status, webRes.statusText);
32
+ return sendNodeResponse(res, webRes);
33
+ } catch (error) {
34
+ next(error);
35
+ }
36
+ });
37
+ };
38
+ }
39
+ }
40
+ };
44
41
  }
45
- export {
46
- previewServerPlugin
47
- };
48
- //# sourceMappingURL=plugin.js.map
42
+ //#endregion
43
+ export { previewServerPlugin };
44
+
45
+ //# sourceMappingURL=plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["../../../src/preview-server-plugin/plugin.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url'\nimport { basename, extname, join } from 'pathe'\nimport { NodeRequest, sendNodeResponse } from 'srvx/node'\nimport { joinURL } from 'ufo'\nimport { VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { getServerOutputDirectory } from '../output-directory'\nimport type { Plugin } from 'vite'\n\nexport function previewServerPlugin(): Plugin {\n return {\n name: 'tanstack-start-core:preview-server',\n configurePreviewServer: {\n // Run last so platform plugins (Cloudflare, Vercel, etc.) can register their handlers first\n order: 'post',\n handler(server) {\n // Return a function so Vite's internal middlewares (static files, etc.) handle requests first.\n // Our SSR handler only processes requests that nothing else handled.\n return () => {\n // Cache the server build to avoid re-importing on every request\n let serverBuild: any = null\n\n server.middlewares.use(async (req, res, next) => {\n try {\n // Lazy load server build on first request\n if (!serverBuild) {\n // Derive output filename from input\n const serverEnv =\n server.config.environments[VITE_ENVIRONMENT_NAMES.server]\n const serverInput =\n serverEnv?.build.rollupOptions.input ?? 'server'\n\n if (typeof serverInput !== 'string') {\n throw new Error('Invalid server input. Expected a string.')\n }\n\n // Get basename without extension and add .js\n const outputFilename = `${basename(serverInput, extname(serverInput))}.js`\n const serverOutputDir = getServerOutputDirectory(server.config)\n const serverEntryPath = join(serverOutputDir, outputFilename)\n const imported = await import(\n pathToFileURL(serverEntryPath).toString()\n )\n\n serverBuild = imported.default\n }\n\n // Prepend base path to request URL to match routing setup\n req.url = joinURL(server.config.base, req.url ?? '/')\n\n const webReq = new NodeRequest({ req, res })\n const webRes: Response = await serverBuild.fetch(webReq)\n\n // Temporary workaround\n // Vite preview's compression middleware doesn't support flattened array headers that srvx sets\n // Call writeHead() before srvx to avoid corruption\n res.setHeaders(webRes.headers)\n res.writeHead(webRes.status, webRes.statusText)\n\n return sendNodeResponse(res, webRes)\n } catch (error) {\n next(error)\n }\n })\n }\n },\n },\n }\n}\n"],"names":[],"mappings":";;;;;;AAQO,SAAS,sBAA8B;AAC5C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,wBAAwB;AAAA;AAAA,MAEtB,OAAO;AAAA,MACP,QAAQ,QAAQ;AAGd,eAAO,MAAM;AAEX,cAAI,cAAmB;AAEvB,iBAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,gBAAI;AAEF,kBAAI,CAAC,aAAa;AAEhB,sBAAM,YACJ,OAAO,OAAO,aAAa,uBAAuB,MAAM;AAC1D,sBAAM,cACJ,WAAW,MAAM,cAAc,SAAS;AAE1C,oBAAI,OAAO,gBAAgB,UAAU;AACnC,wBAAM,IAAI,MAAM,0CAA0C;AAAA,gBAC5D;AAGA,sBAAM,iBAAiB,GAAG,SAAS,aAAa,QAAQ,WAAW,CAAC,CAAC;AACrE,sBAAM,kBAAkB,yBAAyB,OAAO,MAAM;AAC9D,sBAAM,kBAAkB,KAAK,iBAAiB,cAAc;AAC5D,sBAAM,WAAW,MAAM,OACrB,cAAc,eAAe,EAAE,SAAA;AAGjC,8BAAc,SAAS;AAAA,cACzB;AAGA,kBAAI,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,OAAO,GAAG;AAEpD,oBAAM,SAAS,IAAI,YAAY,EAAE,KAAK,KAAK;AAC3C,oBAAM,SAAmB,MAAM,YAAY,MAAM,MAAM;AAKvD,kBAAI,WAAW,OAAO,OAAO;AAC7B,kBAAI,UAAU,OAAO,QAAQ,OAAO,UAAU;AAE9C,qBAAO,iBAAiB,KAAK,MAAM;AAAA,YACrC,SAAS,OAAO;AACd,mBAAK,KAAK;AAAA,YACZ;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IAAA;AAAA,EACF;AAEJ;"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../../../src/preview-server-plugin/plugin.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url'\nimport { basename, extname, join } from 'pathe'\nimport { NodeRequest, sendNodeResponse } from 'srvx/node'\nimport { joinURL } from 'ufo'\nimport { VITE_ENVIRONMENT_NAMES } from '../constants'\nimport { getServerOutputDirectory } from '../output-directory'\nimport type { Plugin } from 'vite'\n\nexport function previewServerPlugin(): Plugin {\n return {\n name: 'tanstack-start-core:preview-server',\n configurePreviewServer: {\n // Run last so platform plugins (Cloudflare, Vercel, etc.) can register their handlers first\n order: 'post',\n handler(server) {\n // Return a function so Vite's internal middlewares (static files, etc.) handle requests first.\n // Our SSR handler only processes requests that nothing else handled.\n return () => {\n // Cache the server build to avoid re-importing on every request\n let serverBuild: any = null\n\n server.middlewares.use(async (req, res, next) => {\n try {\n // Lazy load server build on first request\n if (!serverBuild) {\n // Derive output filename from input\n const serverEnv =\n server.config.environments[VITE_ENVIRONMENT_NAMES.server]\n const serverInput =\n serverEnv?.build.rollupOptions.input ?? 'server'\n\n if (typeof serverInput !== 'string') {\n throw new Error('Invalid server input. Expected a string.')\n }\n\n // Get basename without extension and add .js\n const outputFilename = `${basename(serverInput, extname(serverInput))}.js`\n const serverOutputDir = getServerOutputDirectory(server.config)\n const serverEntryPath = join(serverOutputDir, outputFilename)\n const imported = await import(\n pathToFileURL(serverEntryPath).toString()\n )\n\n serverBuild = imported.default\n }\n\n // Prepend base path to request URL to match routing setup\n req.url = joinURL(server.config.base, req.url ?? '/')\n\n const webReq = new NodeRequest({ req, res })\n const webRes: Response = await serverBuild.fetch(webReq)\n\n // Temporary workaround\n // Vite preview's compression middleware doesn't support flattened array headers that srvx sets\n // Call writeHead() before srvx to avoid corruption\n res.setHeaders(webRes.headers)\n res.writeHead(webRes.status, webRes.statusText)\n\n return sendNodeResponse(res, webRes)\n } catch (error) {\n next(error)\n }\n })\n }\n },\n },\n }\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,sBAA8B;AAC5C,QAAO;EACL,MAAM;EACN,wBAAwB;GAEtB,OAAO;GACP,QAAQ,QAAQ;AAGd,iBAAa;KAEX,IAAI,cAAmB;AAEvB,YAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,UAAI;AAEF,WAAI,CAAC,aAAa;QAIhB,MAAM,cADJ,OAAO,OAAO,aAAa,uBAAuB,SAEvC,MAAM,cAAc,SAAS;AAE1C,YAAI,OAAO,gBAAgB,SACzB,OAAM,IAAI,MAAM,2CAA2C;QAI7D,MAAM,iBAAiB,GAAG,SAAS,aAAa,QAAQ,YAAY,CAAC,CAAC;AAOtE,uBAJiB,MAAM,OACrB,cAFsB,KADA,yBAAyB,OAAO,OAAO,EACjB,eAAe,CAE7B,CAAC,UAAU,GAGpB;;AAIzB,WAAI,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,OAAO,IAAI;OAErD,MAAM,SAAS,IAAI,YAAY;QAAE;QAAK;QAAK,CAAC;OAC5C,MAAM,SAAmB,MAAM,YAAY,MAAM,OAAO;AAKxD,WAAI,WAAW,OAAO,QAAQ;AAC9B,WAAI,UAAU,OAAO,QAAQ,OAAO,WAAW;AAE/C,cAAO,iBAAiB,KAAK,OAAO;eAC7B,OAAO;AACd,YAAK,MAAM;;OAEb;;;GAGP;EACF"}