@tanstack/start-plugin-core 1.167.17 → 1.167.19

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 (183) hide show
  1. package/dist/esm/config-context.d.ts +26 -0
  2. package/dist/esm/config-context.js +81 -0
  3. package/dist/esm/config-context.js.map +1 -0
  4. package/dist/esm/constants.d.ts +6 -1
  5. package/dist/esm/constants.js +3 -2
  6. package/dist/esm/constants.js.map +1 -1
  7. package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js +1 -1
  8. package/dist/esm/import-protection-plugin/plugin.js +1 -1
  9. package/dist/esm/import-protection-plugin/virtualModules.js +1 -1
  10. package/dist/esm/index.d.ts +5 -3
  11. package/dist/esm/index.js +3 -4
  12. package/dist/esm/planning.d.ts +40 -0
  13. package/dist/esm/planning.js +107 -0
  14. package/dist/esm/planning.js.map +1 -0
  15. package/dist/esm/schema.d.ts +3093 -44
  16. package/dist/esm/schema.js +5 -5
  17. package/dist/esm/schema.js.map +1 -1
  18. package/dist/esm/serialization-adapters-module.d.ts +17 -0
  19. package/dist/esm/serialization-adapters-module.js +39 -0
  20. package/dist/esm/serialization-adapters-module.js.map +1 -0
  21. package/dist/esm/{start-compiler-plugin → start-compiler}/compiler.d.ts +2 -3
  22. package/dist/esm/{start-compiler-plugin → start-compiler}/compiler.js +17 -16
  23. package/dist/esm/start-compiler/compiler.js.map +1 -0
  24. package/dist/esm/start-compiler/config.d.ts +4 -0
  25. package/dist/esm/start-compiler/config.js +54 -0
  26. package/dist/esm/start-compiler/config.js.map +1 -0
  27. package/dist/esm/{start-compiler-plugin → start-compiler}/handleClientOnlyJSX.js +1 -1
  28. package/dist/esm/start-compiler/handleClientOnlyJSX.js.map +1 -0
  29. package/dist/esm/{start-compiler-plugin → start-compiler}/handleCreateIsomorphicFn.js +1 -1
  30. package/dist/esm/start-compiler/handleCreateIsomorphicFn.js.map +1 -0
  31. package/dist/esm/{start-compiler-plugin → start-compiler}/handleCreateMiddleware.js +1 -1
  32. package/dist/esm/start-compiler/handleCreateMiddleware.js.map +1 -0
  33. package/dist/esm/{start-compiler-plugin → start-compiler}/handleCreateServerFn.js +6 -17
  34. package/dist/esm/start-compiler/handleCreateServerFn.js.map +1 -0
  35. package/dist/esm/{start-compiler-plugin → start-compiler}/handleEnvOnly.js +1 -1
  36. package/dist/esm/start-compiler/handleEnvOnly.js.map +1 -0
  37. package/dist/esm/start-compiler/host.d.ts +20 -0
  38. package/dist/esm/start-compiler/host.js +38 -0
  39. package/dist/esm/start-compiler/host.js.map +1 -0
  40. package/dist/esm/start-compiler/load-module.d.ts +14 -0
  41. package/dist/esm/start-compiler/load-module.js +18 -0
  42. package/dist/esm/start-compiler/load-module.js.map +1 -0
  43. package/dist/esm/start-compiler/server-fn-resolver-module.d.ts +8 -0
  44. package/dist/esm/start-compiler/server-fn-resolver-module.js +77 -0
  45. package/dist/esm/start-compiler/server-fn-resolver-module.js.map +1 -0
  46. package/dist/esm/{start-compiler-plugin → start-compiler}/types.d.ts +4 -0
  47. package/dist/esm/{start-compiler-plugin → start-compiler}/utils.js +1 -1
  48. package/dist/esm/start-compiler/utils.js.map +1 -0
  49. package/dist/esm/start-manifest-plugin/manifestBuilder.d.ts +16 -16
  50. package/dist/esm/start-manifest-plugin/manifestBuilder.js +14 -45
  51. package/dist/esm/start-manifest-plugin/manifestBuilder.js.map +1 -1
  52. package/dist/esm/start-router-plugin/route-tree-footer.d.ts +6 -0
  53. package/dist/esm/start-router-plugin/route-tree-footer.js +44 -0
  54. package/dist/esm/start-router-plugin/route-tree-footer.js.map +1 -0
  55. package/dist/esm/types.d.ts +44 -10
  56. package/dist/esm/{dev-server-plugin → vite/dev-server-plugin}/dev-styles.js +1 -1
  57. package/dist/esm/vite/dev-server-plugin/dev-styles.js.map +1 -0
  58. package/dist/esm/{dev-server-plugin → vite/dev-server-plugin}/extract-html-scripts.js +1 -1
  59. package/dist/esm/vite/dev-server-plugin/extract-html-scripts.js.map +1 -0
  60. package/dist/esm/vite/dev-server-plugin/plugin.d.ts +7 -0
  61. package/dist/esm/{dev-server-plugin → vite/dev-server-plugin}/plugin.js +5 -6
  62. package/dist/esm/vite/dev-server-plugin/plugin.js.map +1 -0
  63. package/dist/esm/{load-env-plugin → vite/load-env-plugin}/plugin.js +1 -1
  64. package/dist/esm/vite/load-env-plugin/plugin.js.map +1 -0
  65. package/dist/esm/{output-directory.js → vite/output-directory.js} +2 -2
  66. package/dist/esm/vite/output-directory.js.map +1 -0
  67. package/dist/esm/vite/planning.d.ts +105 -0
  68. package/dist/esm/vite/planning.js +116 -0
  69. package/dist/esm/vite/planning.js.map +1 -0
  70. package/dist/esm/vite/plugin.d.ts +4 -0
  71. package/dist/esm/vite/plugin.js +169 -0
  72. package/dist/esm/vite/plugin.js.map +1 -0
  73. package/dist/esm/vite/plugins.d.ts +17 -0
  74. package/dist/esm/vite/plugins.js +50 -0
  75. package/dist/esm/vite/plugins.js.map +1 -0
  76. package/dist/esm/{post-server-build.d.ts → vite/post-server-build.d.ts} +1 -1
  77. package/dist/esm/{post-server-build.js → vite/post-server-build.js} +4 -4
  78. package/dist/esm/vite/post-server-build.js.map +1 -0
  79. package/dist/esm/{prerender.d.ts → vite/prerender.d.ts} +1 -1
  80. package/dist/esm/{prerender.js → vite/prerender.js} +5 -10
  81. package/dist/esm/vite/prerender.js.map +1 -0
  82. package/dist/esm/{preview-server-plugin → vite/preview-server-plugin}/plugin.js +4 -4
  83. package/dist/esm/vite/preview-server-plugin/plugin.js.map +1 -0
  84. package/dist/esm/vite/schema.d.ts +3373 -0
  85. package/dist/esm/vite/schema.js +12 -0
  86. package/dist/esm/vite/schema.js.map +1 -0
  87. package/dist/esm/vite/serialization-adapters-plugin.d.ts +5 -0
  88. package/dist/esm/vite/serialization-adapters-plugin.js +42 -0
  89. package/dist/esm/vite/serialization-adapters-plugin.js.map +1 -0
  90. package/dist/esm/vite/start-compiler-plugin/module-specifier.d.ts +3 -0
  91. package/dist/esm/vite/start-compiler-plugin/module-specifier.js +19 -0
  92. package/dist/esm/vite/start-compiler-plugin/module-specifier.js.map +1 -0
  93. package/dist/esm/{start-compiler-plugin → vite/start-compiler-plugin}/plugin.d.ts +4 -3
  94. package/dist/esm/vite/start-compiler-plugin/plugin.js +202 -0
  95. package/dist/esm/vite/start-compiler-plugin/plugin.js.map +1 -0
  96. package/dist/esm/vite/start-manifest-plugin/normalized-client-build.d.ts +6 -0
  97. package/dist/esm/vite/start-manifest-plugin/normalized-client-build.js +81 -0
  98. package/dist/esm/vite/start-manifest-plugin/normalized-client-build.js.map +1 -0
  99. package/dist/esm/vite/start-manifest-plugin/plugin.d.ts +6 -0
  100. package/dist/esm/{start-manifest-plugin → vite/start-manifest-plugin}/plugin.js +14 -9
  101. package/dist/esm/vite/start-manifest-plugin/plugin.js.map +1 -0
  102. package/dist/esm/{start-router-plugin → vite/start-router-plugin}/plugin.d.ts +3 -2
  103. package/dist/esm/{start-router-plugin → vite/start-router-plugin}/plugin.js +14 -37
  104. package/dist/esm/vite/start-router-plugin/plugin.js.map +1 -0
  105. package/dist/esm/vite/types.d.ts +15 -0
  106. package/package.json +14 -3
  107. package/src/config-context.ts +138 -0
  108. package/src/constants.ts +7 -3
  109. package/src/index.ts +5 -5
  110. package/src/planning.ts +151 -0
  111. package/src/schema.ts +93 -93
  112. package/src/serialization-adapters-module.ts +82 -0
  113. package/src/{start-compiler-plugin → start-compiler}/compiler.ts +67 -61
  114. package/src/start-compiler/config.ts +73 -0
  115. package/src/{start-compiler-plugin → start-compiler}/handleCreateServerFn.ts +14 -41
  116. package/src/start-compiler/host.ts +80 -0
  117. package/src/start-compiler/load-module.ts +31 -0
  118. package/src/start-compiler/server-fn-resolver-module.ts +129 -0
  119. package/src/{start-compiler-plugin → start-compiler}/types.ts +5 -0
  120. package/src/start-manifest-plugin/manifestBuilder.ts +65 -107
  121. package/src/start-router-plugin/route-tree-footer.ts +99 -0
  122. package/src/types.ts +53 -10
  123. package/src/{dev-server-plugin → vite/dev-server-plugin}/plugin.ts +7 -6
  124. package/src/{output-directory.ts → vite/output-directory.ts} +2 -2
  125. package/src/vite/planning.ts +234 -0
  126. package/src/vite/plugin.ts +276 -0
  127. package/src/vite/plugins.ts +81 -0
  128. package/src/{post-server-build.ts → vite/post-server-build.ts} +4 -6
  129. package/src/{prerender.ts → vite/prerender.ts} +21 -46
  130. package/src/{preview-server-plugin → vite/preview-server-plugin}/plugin.ts +2 -2
  131. package/src/vite/schema.ts +30 -0
  132. package/src/vite/serialization-adapters-plugin.ts +69 -0
  133. package/src/vite/start-compiler-plugin/module-specifier.ts +31 -0
  134. package/src/vite/start-compiler-plugin/plugin.ts +345 -0
  135. package/src/vite/start-manifest-plugin/normalized-client-build.ts +131 -0
  136. package/src/{start-manifest-plugin → vite/start-manifest-plugin}/plugin.ts +21 -13
  137. package/src/{start-router-plugin → vite/start-router-plugin}/plugin.ts +14 -80
  138. package/src/vite/types.ts +18 -0
  139. package/dist/esm/dev-server-plugin/dev-styles.js.map +0 -1
  140. package/dist/esm/dev-server-plugin/extract-html-scripts.js.map +0 -1
  141. package/dist/esm/dev-server-plugin/plugin.d.ts +0 -6
  142. package/dist/esm/dev-server-plugin/plugin.js.map +0 -1
  143. package/dist/esm/load-env-plugin/plugin.js.map +0 -1
  144. package/dist/esm/output-directory.js.map +0 -1
  145. package/dist/esm/plugin.d.ts +0 -4
  146. package/dist/esm/plugin.js +0 -301
  147. package/dist/esm/plugin.js.map +0 -1
  148. package/dist/esm/post-server-build.js.map +0 -1
  149. package/dist/esm/prerender.js.map +0 -1
  150. package/dist/esm/preview-server-plugin/plugin.js.map +0 -1
  151. package/dist/esm/start-compiler-plugin/compiler.js.map +0 -1
  152. package/dist/esm/start-compiler-plugin/handleClientOnlyJSX.js.map +0 -1
  153. package/dist/esm/start-compiler-plugin/handleCreateIsomorphicFn.js.map +0 -1
  154. package/dist/esm/start-compiler-plugin/handleCreateMiddleware.js.map +0 -1
  155. package/dist/esm/start-compiler-plugin/handleCreateServerFn.js.map +0 -1
  156. package/dist/esm/start-compiler-plugin/handleEnvOnly.js.map +0 -1
  157. package/dist/esm/start-compiler-plugin/plugin.js +0 -297
  158. package/dist/esm/start-compiler-plugin/plugin.js.map +0 -1
  159. package/dist/esm/start-compiler-plugin/utils.js.map +0 -1
  160. package/dist/esm/start-manifest-plugin/plugin.d.ts +0 -6
  161. package/dist/esm/start-manifest-plugin/plugin.js.map +0 -1
  162. package/dist/esm/start-router-plugin/plugin.js.map +0 -1
  163. package/src/plugin.ts +0 -471
  164. package/src/start-compiler-plugin/plugin.ts +0 -478
  165. /package/dist/esm/{start-compiler-plugin → start-compiler}/handleClientOnlyJSX.d.ts +0 -0
  166. /package/dist/esm/{start-compiler-plugin → start-compiler}/handleCreateIsomorphicFn.d.ts +0 -0
  167. /package/dist/esm/{start-compiler-plugin → start-compiler}/handleCreateMiddleware.d.ts +0 -0
  168. /package/dist/esm/{start-compiler-plugin → start-compiler}/handleCreateServerFn.d.ts +0 -0
  169. /package/dist/esm/{start-compiler-plugin → start-compiler}/handleEnvOnly.d.ts +0 -0
  170. /package/dist/esm/{start-compiler-plugin → start-compiler}/utils.d.ts +0 -0
  171. /package/dist/esm/{dev-server-plugin → vite/dev-server-plugin}/dev-styles.d.ts +0 -0
  172. /package/dist/esm/{dev-server-plugin → vite/dev-server-plugin}/extract-html-scripts.d.ts +0 -0
  173. /package/dist/esm/{load-env-plugin → vite/load-env-plugin}/plugin.d.ts +0 -0
  174. /package/dist/esm/{output-directory.d.ts → vite/output-directory.d.ts} +0 -0
  175. /package/dist/esm/{preview-server-plugin → vite/preview-server-plugin}/plugin.d.ts +0 -0
  176. /package/src/{start-compiler-plugin → start-compiler}/handleClientOnlyJSX.ts +0 -0
  177. /package/src/{start-compiler-plugin → start-compiler}/handleCreateIsomorphicFn.ts +0 -0
  178. /package/src/{start-compiler-plugin → start-compiler}/handleCreateMiddleware.ts +0 -0
  179. /package/src/{start-compiler-plugin → start-compiler}/handleEnvOnly.ts +0 -0
  180. /package/src/{start-compiler-plugin → start-compiler}/utils.ts +0 -0
  181. /package/src/{dev-server-plugin → vite/dev-server-plugin}/dev-styles.ts +0 -0
  182. /package/src/{dev-server-plugin → vite/dev-server-plugin}/extract-html-scripts.ts +0 -0
  183. /package/src/{load-env-plugin → vite/load-env-plugin}/plugin.ts +0 -0
@@ -2,11 +2,11 @@ import { promises as fsp } from 'node:fs'
2
2
  import os from 'node:os'
3
3
  import path from 'pathe'
4
4
  import { joinURL, withBase, withTrailingSlash, withoutBase } from 'ufo'
5
- import { VITE_ENVIRONMENT_NAMES } from './constants'
6
- import { createLogger } from './utils'
7
- import { Queue } from './queue'
5
+ import { VITE_ENVIRONMENT_NAMES } from '../constants'
6
+ import { createLogger } from '../utils'
7
+ import { Queue } from '../queue'
8
8
  import type { PreviewServer, ResolvedConfig, ViteBuilder } from 'vite'
9
- import type { Page, TanStackStartOutputConfig } from './schema'
9
+ import type { Page, TanStackStartOutputConfig } from '../schema'
10
10
 
11
11
  export async function prerender({
12
12
  startConfig,
@@ -18,13 +18,10 @@ export async function prerender({
18
18
  const logger = createLogger('prerender')
19
19
  logger.info('Prerendering pages...')
20
20
 
21
- // If prerender is enabled
22
21
  if (startConfig.prerender?.enabled) {
23
- // default to root page if no pages are defined
24
22
  let pages = startConfig.pages.length ? startConfig.pages : [{ path: '/' }]
25
23
 
26
24
  if (startConfig.prerender.autoStaticPathsDiscovery ?? true) {
27
- // merge discovered static pages with user-defined pages
28
25
  const pagesMap = new Map(pages.map((item) => [item.path, item]))
29
26
  const discoveredPages = globalThis.TSS_PRERENDABLE_PATHS || []
30
27
 
@@ -43,7 +40,6 @@ export async function prerender({
43
40
  const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')
44
41
  const routerBaseUrl = new URL(routerBasePath, 'http://localhost')
45
42
 
46
- // Enforce that prerender page paths are relative/path-based (no protocol/host)
47
43
  startConfig.pages = validateAndNormalizePrerenderPages(
48
44
  startConfig.pages,
49
45
  routerBaseUrl,
@@ -69,13 +65,13 @@ export async function prerender({
69
65
  process.env.TSS_PRERENDERING = 'true'
70
66
  process.env.TSS_CLIENT_OUTPUT_DIR = outputDir
71
67
 
72
- // Start Vite preview server instead of importing module
73
68
  const previewServer = await startPreviewServer(serverEnv.config)
74
69
  const baseUrl = getResolvedUrl(previewServer)
75
70
 
76
71
  const isRedirectResponse = (res: Response) => {
77
72
  return res.status >= 300 && res.status < 400 && res.headers.get('location')
78
73
  }
74
+
79
75
  async function localFetch(
80
76
  path: string,
81
77
  options?: RequestInit,
@@ -90,16 +86,15 @@ export async function prerender({
90
86
  if (location.startsWith('http://localhost') || location.startsWith('/')) {
91
87
  const newUrl = location.replace('http://localhost', '')
92
88
  return localFetch(newUrl, options, maxRedirects - 1)
93
- } else {
94
- logger.warn(`Skipping redirect to external location: ${location}`)
95
89
  }
90
+
91
+ logger.warn(`Skipping redirect to external location: ${location}`)
96
92
  }
97
93
 
98
94
  return response
99
95
  }
100
96
 
101
97
  try {
102
- // Crawl all pages
103
98
  const pages = await prerenderPages({ outputDir })
104
99
 
105
100
  logger.info(`Prerendered ${pages.length} pages:`)
@@ -115,7 +110,7 @@ export async function prerender({
115
110
  function extractLinks(html: string): Array<string> {
116
111
  const linkRegex = /<a[^>]+href=["']([^"']+)["'][^>]*>/g
117
112
  const links: Array<string> = []
118
- let match
113
+ let match: RegExpExecArray | null
119
114
 
120
115
  while ((match = linkRegex.exec(html)) !== null) {
121
116
  const href = match[1]
@@ -136,7 +131,6 @@ export async function prerender({
136
131
  const queue = new Queue({ concurrency })
137
132
  const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')
138
133
 
139
- // Normalize discovered pages and enforce path-only entries
140
134
  const routerBaseUrl = new URL(routerBasePath, 'http://localhost')
141
135
  startConfig.pages = validateAndNormalizePrerenderPages(
142
136
  startConfig.pages,
@@ -155,36 +149,32 @@ export async function prerender({
155
149
  return Array.from(prerendered)
156
150
 
157
151
  function addCrawlPageTask(page: Page) {
158
- // Was the page already seen?
159
152
  if (seen.has(page.path)) return
160
153
 
161
- // Add the page to the seen set
162
154
  seen.add(page.path)
163
155
 
164
156
  if (page.fromCrawl) {
165
157
  startConfig.pages.push(page)
166
158
  }
167
159
 
168
- // If not enabled, skip
169
160
  if (!(page.prerender?.enabled ?? true)) return
170
161
 
171
- // If there is a filter link, check if the page should be prerendered
172
- if (startConfig.prerender?.filter && !startConfig.prerender.filter(page))
162
+ if (
163
+ startConfig.prerender?.filter &&
164
+ !startConfig.prerender.filter(page)
165
+ ) {
173
166
  return
167
+ }
174
168
 
175
- // Resolve the merged default and page-specific prerender options
176
169
  const prerenderOptions = {
177
170
  ...startConfig.prerender,
178
171
  ...page.prerender,
179
172
  }
180
173
 
181
- // Add the task
182
174
  queue.add(async () => {
183
175
  logger.info(`Crawling: ${page.path}`)
184
176
  const retries = retriesByPath.get(page.path) || 0
185
177
  try {
186
- // Fetch the route
187
-
188
178
  const res = await localFetch(
189
179
  withTrailingSlash(withBase(page.path, routerBasePath)),
190
180
  {
@@ -208,7 +198,6 @@ export async function prerender({
208
198
  prerenderOptions.outputPath || page.path
209
199
  ).split(/[?#]/)[0]!
210
200
 
211
- // Guess route type and populate fileName
212
201
  const contentType = res.headers.get('content-type') || ''
213
202
  const isImplicitHTML =
214
203
  !cleanPagePath.endsWith('.html') && contentType.includes('html')
@@ -222,17 +211,14 @@ export async function prerender({
222
211
 
223
212
  let htmlPath: string
224
213
  if (isSpaShell) {
225
- // For SPA shell, ignore autoSubfolderIndex option
226
214
  htmlPath = cleanPagePath + '.html'
215
+ } else if (
216
+ cleanPagePath.endsWith('/') ||
217
+ (prerenderOptions.autoSubfolderIndex ?? true)
218
+ ) {
219
+ htmlPath = joinURL(cleanPagePath, 'index.html')
227
220
  } else {
228
- if (
229
- cleanPagePath.endsWith('/') ||
230
- (prerenderOptions.autoSubfolderIndex ?? true)
231
- ) {
232
- htmlPath = joinURL(cleanPagePath, 'index.html')
233
- } else {
234
- htmlPath = cleanPagePath + '.html'
235
- }
221
+ htmlPath = cleanPagePath + '.html'
236
222
  }
237
223
 
238
224
  const filename = withoutBase(
@@ -258,7 +244,6 @@ export async function prerender({
258
244
  Object.assign(page, newPage)
259
245
  }
260
246
 
261
- // Find new links
262
247
  if (prerenderOptions.crawlLinks ?? true) {
263
248
  const links = extractLinks(html)
264
249
  for (const link of links) {
@@ -273,10 +258,8 @@ export async function prerender({
273
258
  )
274
259
  retriesByPath.set(page.path, retries + 1)
275
260
  addCrawlPageTask(page)
276
- } else {
277
- if (prerenderOptions.failOnError ?? true) {
278
- throw error
279
- }
261
+ } else if (prerenderOptions.failOnError ?? true) {
262
+ throw error
280
263
  }
281
264
  }
282
265
  })
@@ -317,11 +300,6 @@ function getResolvedUrl(previewServer: PreviewServer): URL {
317
300
  return new URL(baseUrl)
318
301
  }
319
302
 
320
- /**
321
- * Validates and normalizes prerender page paths to ensure they are relative
322
- * (no protocol/host) and returns normalized Page objects with cleaned paths.
323
- * Preserves unicode characters by decoding the pathname after URL validation.
324
- */
325
303
  function validateAndNormalizePrerenderPages(
326
304
  pages: Array<Page>,
327
305
  routerBaseUrl: URL,
@@ -340,9 +318,6 @@ function validateAndNormalizePrerenderPages(
340
318
  throw new Error(`prerender page path must be relative: ${page.path}`)
341
319
  }
342
320
 
343
- // Decode the pathname to preserve unicode characters (e.g., /대한민국)
344
- // The URL constructor encodes non-ASCII characters, but we want to keep
345
- // the original unicode form for filesystem paths
346
321
  const decodedPathname = decodeURIComponent(url.pathname)
347
322
 
348
323
  return {
@@ -2,9 +2,9 @@ import { pathToFileURL } from 'node:url'
2
2
  import { basename, extname, join } from 'pathe'
3
3
  import { NodeRequest, sendNodeResponse } from 'srvx/node'
4
4
  import { joinURL } from 'ufo'
5
- import { VITE_ENVIRONMENT_NAMES } from '../constants'
5
+ import { VITE_ENVIRONMENT_NAMES } from '../../constants'
6
6
  import { getServerOutputDirectory } from '../output-directory'
7
- import { getBundlerOptions } from '../utils'
7
+ import { getBundlerOptions } from '../../utils'
8
8
  import type { Plugin } from 'vite'
9
9
 
10
10
  export function previewServerPlugin(): Plugin {
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod'
2
+ import {
3
+ parseStartConfig as parseCoreStartConfig,
4
+ tanstackStartOptionsObjectSchema,
5
+ } from '../schema'
6
+ import type { CompileStartFrameworkOptions } from '../types'
7
+
8
+ export const tanstackStartViteOptionsSchema = tanstackStartOptionsObjectSchema
9
+ .extend({
10
+ vite: z
11
+ .object({ installDevServerMiddleware: z.boolean().optional() })
12
+ .optional(),
13
+ })
14
+ .optional()
15
+ .default({})
16
+
17
+ export function parseStartConfig(
18
+ opts: z.input<typeof tanstackStartViteOptionsSchema>,
19
+ corePluginOpts: { framework: CompileStartFrameworkOptions },
20
+ root: string,
21
+ ) {
22
+ const { vite: _vite, ...coreOptions } =
23
+ tanstackStartViteOptionsSchema.parse(opts)
24
+
25
+ return parseCoreStartConfig(coreOptions, corePluginOpts, root)
26
+ }
27
+
28
+ export type TanStackStartViteInputConfig = z.input<
29
+ typeof tanstackStartViteOptionsSchema
30
+ >
@@ -0,0 +1,69 @@
1
+ import { VIRTUAL_MODULES } from '@tanstack/start-server-core'
2
+ import {
3
+ EMPTY_SERIALIZATION_ADAPTERS_MODULE,
4
+ generateSerializationAdaptersModule,
5
+ } from '../serialization-adapters-module'
6
+ import { START_ENVIRONMENT_NAMES } from '../constants'
7
+ import { resolveViteId } from '../utils'
8
+ import type { SerializationAdapterConfig } from '../types'
9
+ import type { PluginOption } from 'vite'
10
+
11
+ // Encode '#' as '%23' in the resolved ID to avoid browser treating it as URL fragment.
12
+ // The browser requests /@id/__x00__%23tanstack-start-plugin-adapters instead of
13
+ // /@id/__x00__#tanstack-start-plugin-adapters (which would truncate at #).
14
+ const resolvedModuleId = resolveViteId(
15
+ VIRTUAL_MODULES.pluginAdapters.replace('#', '%23'),
16
+ )
17
+
18
+ function escapeRegex(str: string): string {
19
+ return str.replace(/[.*+?^${}()|[\]\\#]/g, '\\$&')
20
+ }
21
+
22
+ export function serializationAdaptersPlugin(opts: {
23
+ adapters: Array<SerializationAdapterConfig> | undefined
24
+ }): PluginOption {
25
+ return {
26
+ name: 'tanstack-start:plugin-adapters',
27
+ enforce: 'pre',
28
+ resolveId: {
29
+ filter: { id: new RegExp(escapeRegex(VIRTUAL_MODULES.pluginAdapters)) },
30
+ handler(id: string) {
31
+ if (id === VIRTUAL_MODULES.pluginAdapters) {
32
+ return resolvedModuleId
33
+ }
34
+ return undefined
35
+ },
36
+ },
37
+ load: {
38
+ filter: {
39
+ id: new RegExp(escapeRegex(resolvedModuleId)),
40
+ },
41
+ handler(this: { environment: { name: string } }, id: string) {
42
+ if (id !== resolvedModuleId) {
43
+ return undefined
44
+ }
45
+
46
+ const adapters = opts.adapters
47
+ if (!adapters || adapters.length === 0) {
48
+ return EMPTY_SERIALIZATION_ADAPTERS_MODULE
49
+ }
50
+
51
+ if (this.environment.name === START_ENVIRONMENT_NAMES.client) {
52
+ return generateSerializationAdaptersModule({
53
+ adapters,
54
+ runtime: 'client',
55
+ })
56
+ }
57
+
58
+ if (this.environment.name === START_ENVIRONMENT_NAMES.server) {
59
+ return generateSerializationAdaptersModule({
60
+ adapters,
61
+ runtime: 'server',
62
+ })
63
+ }
64
+
65
+ return EMPTY_SERIALIZATION_ADAPTERS_MODULE
66
+ },
67
+ },
68
+ }
69
+ }
@@ -0,0 +1,31 @@
1
+ import type { DevServerFnModuleSpecifierEncoder } from '../../start-compiler/types'
2
+
3
+ export function createViteDevServerFnModuleSpecifierEncoder(
4
+ root: string,
5
+ ): DevServerFnModuleSpecifierEncoder {
6
+ const rootWithTrailingSlash = root.endsWith('/') ? root : `${root}/`
7
+
8
+ return ({ extractedFilename }) => {
9
+ let file = extractedFilename
10
+
11
+ if (file.startsWith(rootWithTrailingSlash)) {
12
+ file = file.slice(rootWithTrailingSlash.length)
13
+ }
14
+
15
+ return `/@id/${file}`
16
+ }
17
+ }
18
+
19
+ export function decodeViteDevServerModuleSpecifier(
20
+ moduleSpecifier: string,
21
+ ): string {
22
+ let sourceFile = moduleSpecifier
23
+
24
+ if (sourceFile.startsWith('/@id/')) {
25
+ sourceFile = sourceFile.slice('/@id/'.length)
26
+ }
27
+
28
+ const queryIndex = sourceFile.indexOf('?')
29
+
30
+ return queryIndex === -1 ? sourceFile : sourceFile.slice(0, queryIndex)
31
+ }
@@ -0,0 +1,345 @@
1
+ import assert from 'node:assert'
2
+ import { VIRTUAL_MODULES } from '@tanstack/start-server-core'
3
+ import { resolve as resolvePath } from 'pathe'
4
+ import {
5
+ SERVER_FN_LOOKUP,
6
+ TRANSFORM_ID_REGEX,
7
+ VITE_ENVIRONMENT_NAMES,
8
+ } from '../../constants'
9
+ import { detectKindsInCode } from '../../start-compiler/compiler'
10
+ import { getTransformCodeFilterForEnv } from '../../start-compiler/config'
11
+ import {
12
+ createStartCompiler,
13
+ mergeServerFnsById,
14
+ } from '../../start-compiler/host'
15
+ import { loadModuleForViteCompiler } from '../../start-compiler/load-module'
16
+ import { generateServerFnResolverModule } from '../../start-compiler/server-fn-resolver-module'
17
+ import { cleanId } from '../../start-compiler/utils'
18
+ import {
19
+ createViteDevServerFnModuleSpecifierEncoder,
20
+ decodeViteDevServerModuleSpecifier,
21
+ } from './module-specifier'
22
+ import type { CompileStartFrameworkOptions } from '../../types'
23
+ import type {
24
+ GenerateFunctionIdFnOptional,
25
+ ServerFn,
26
+ } from '../../start-compiler/types'
27
+ import type { PluginOption } from 'vite'
28
+
29
+ // Re-export from shared constants for backwards compatibility
30
+ export { SERVER_FN_LOOKUP }
31
+
32
+ function resolveViteId(id: string) {
33
+ return `\0${id}`
34
+ }
35
+
36
+ const validateServerFnIdVirtualModule = `virtual:tanstack-start-validate-server-fn-id`
37
+
38
+ function getDevServerFnValidatorModule(): string {
39
+ return `
40
+ export async function getServerFnById(id, _access) {
41
+ const validateIdImport = ${JSON.stringify(validateServerFnIdVirtualModule)} + '?id=' + id
42
+ await import(/* @vite-ignore */ '/@id/__x00__' + validateIdImport)
43
+ const decoded = Buffer.from(id, 'base64url').toString('utf8')
44
+ const devServerFn = JSON.parse(decoded)
45
+ const mod = await import(/* @vite-ignore */ devServerFn.file)
46
+ return mod[devServerFn.export]
47
+ }
48
+ `
49
+ }
50
+
51
+ function parseIdQuery(id: string): {
52
+ filename: string
53
+ query: {
54
+ [k: string]: string
55
+ }
56
+ } {
57
+ if (!id.includes('?')) return { filename: id, query: {} }
58
+ const [filename, rawQuery] = id.split(`?`, 2) as [string, string]
59
+ const query = Object.fromEntries(new URLSearchParams(rawQuery))
60
+ return { filename, query }
61
+ }
62
+
63
+ export interface StartCompilerPluginOptions {
64
+ framework: CompileStartFrameworkOptions
65
+ environments: Array<{
66
+ name: string
67
+ type: 'client' | 'server'
68
+ getServerFnById?: string
69
+ }>
70
+ /**
71
+ * Custom function ID generator (optional).
72
+ */
73
+ generateFunctionId?: GenerateFunctionIdFnOptional
74
+ /**
75
+ * The Vite environment name for the server function provider.
76
+ */
77
+ providerEnvName: string
78
+ }
79
+
80
+ export function startCompilerPlugin(
81
+ opts: StartCompilerPluginOptions,
82
+ ): PluginOption {
83
+ const compilers = new Map<string, ReturnType<typeof createStartCompiler>>()
84
+
85
+ // Shared registry of server functions across all environments
86
+ const serverFnsById: Record<string, ServerFn> = {}
87
+
88
+ const onServerFnsById = (d: Record<string, ServerFn>) => {
89
+ mergeServerFnsById(serverFnsById, d)
90
+ }
91
+
92
+ let root = process.cwd()
93
+ const resolvedResolverVirtualImportId = resolveViteId(
94
+ VIRTUAL_MODULES.serverFnResolver,
95
+ )
96
+
97
+ // Determine which environments need the resolver (getServerFnById)
98
+ // SSR environment always needs the resolver for server-side calls
99
+ // Provider environment needs it for the actual implementation
100
+ const ssrEnvName = VITE_ENVIRONMENT_NAMES.server
101
+
102
+ // SSR is the provider when the provider environment is the default server environment
103
+ const ssrIsProvider = opts.providerEnvName === ssrEnvName
104
+
105
+ // Environments that need the resolver: SSR (for server calls) and provider (for implementation)
106
+ const appliedResolverEnvironments = new Set(
107
+ ssrIsProvider ? [opts.providerEnvName] : [ssrEnvName, opts.providerEnvName],
108
+ )
109
+
110
+ function perEnvServerFnPlugin(environment: {
111
+ name: string
112
+ type: 'client' | 'server'
113
+ }): PluginOption {
114
+ // Derive transform code filter from KindDetectionPatterns (single source of truth)
115
+ const transformCodeFilter = getTransformCodeFilterForEnv(environment.type)
116
+ return {
117
+ name: `tanstack-start-core::server-fn:${environment.name}`,
118
+ enforce: 'pre',
119
+ applyToEnvironment(env) {
120
+ return env.name === environment.name
121
+ },
122
+ configResolved(config) {
123
+ root = config.root
124
+ },
125
+ transform: {
126
+ filter: {
127
+ id: {
128
+ exclude: new RegExp(`${SERVER_FN_LOOKUP}$`),
129
+ include: TRANSFORM_ID_REGEX,
130
+ },
131
+ code: {
132
+ include: transformCodeFilter,
133
+ },
134
+ },
135
+ async handler(code, id) {
136
+ let compiler = compilers.get(this.environment.name)
137
+
138
+ if (!compiler) {
139
+ // Default to 'dev' mode for unknown environments (conservative: no caching)
140
+ const mode = this.environment.mode === 'build' ? 'build' : 'dev'
141
+
142
+ compiler = createStartCompiler({
143
+ env: environment.type,
144
+ envName: environment.name,
145
+ root,
146
+ mode,
147
+ framework: opts.framework,
148
+ providerEnvName: opts.providerEnvName,
149
+ generateFunctionId: opts.generateFunctionId,
150
+ onServerFnsById,
151
+ getKnownServerFns: () => serverFnsById,
152
+ encodeModuleSpecifierInDev:
153
+ mode === 'dev'
154
+ ? createViteDevServerFnModuleSpecifierEncoder(root)
155
+ : undefined,
156
+ loadModule: async (id: string) => {
157
+ await loadModuleForViteCompiler({
158
+ compiler: compiler!,
159
+ mode: this.environment.mode,
160
+ fetchModule:
161
+ this.environment.mode === 'dev'
162
+ ? this.environment.fetchModule.bind(this.environment)
163
+ : undefined,
164
+ loadModule: this.load.bind(this),
165
+ id,
166
+ })
167
+ },
168
+
169
+ resolveId: async (source: string, importer?: string) => {
170
+ const r = await this.resolve(source, importer)
171
+
172
+ if (r) {
173
+ if (!r.external) {
174
+ return cleanId(r.id)
175
+ }
176
+ }
177
+
178
+ return null
179
+ },
180
+ })
181
+
182
+ compilers.set(this.environment.name, compiler)
183
+ }
184
+
185
+ // Detect which kinds are present in this file before parsing
186
+ const detectedKinds = detectKindsInCode(code, environment.type)
187
+
188
+ const result = await compiler.compile({
189
+ id,
190
+ code,
191
+ detectedKinds,
192
+ })
193
+ return result
194
+ },
195
+ },
196
+
197
+ hotUpdate(ctx) {
198
+ const compiler = compilers.get(this.environment.name)
199
+
200
+ ctx.modules.forEach((m) => {
201
+ if (m.id) {
202
+ const deleted = compiler?.invalidateModule(m.id)
203
+ if (deleted) {
204
+ m.importers.forEach((importer) => {
205
+ if (importer.id) {
206
+ compiler?.invalidateModule(importer.id)
207
+ }
208
+ })
209
+ }
210
+ }
211
+ })
212
+ },
213
+ }
214
+ }
215
+
216
+ return [
217
+ ...opts.environments.map(perEnvServerFnPlugin),
218
+ {
219
+ name: 'tanstack-start-core:capture-server-fn-module-lookup',
220
+ // we only need this plugin in dev mode
221
+ apply: 'serve',
222
+ applyToEnvironment(env) {
223
+ return !!opts.environments.find((e) => e.name === env.name)
224
+ },
225
+ transform: {
226
+ filter: {
227
+ id: new RegExp(`${SERVER_FN_LOOKUP}$`),
228
+ },
229
+ handler(code, id) {
230
+ const compiler = compilers.get(this.environment.name)
231
+ compiler?.ingestModule({ code, id: cleanId(id) })
232
+ },
233
+ },
234
+ },
235
+ // Validate server function ID in dev mode
236
+ {
237
+ name: 'tanstack-start-core:validate-server-fn-id',
238
+ apply: 'serve',
239
+ load: {
240
+ filter: {
241
+ id: new RegExp(resolveViteId(validateServerFnIdVirtualModule)),
242
+ },
243
+ async handler(id) {
244
+ const parsed = parseIdQuery(id)
245
+ const fnId = parsed.query.id
246
+ if (fnId && serverFnsById[fnId]) {
247
+ return `export {}`
248
+ }
249
+
250
+ // ID not yet registered — the source file may not have been
251
+ // transformed in this dev session yet (e.g. cold restart with
252
+ // cached client). Try to decode the ID, discover the source
253
+ // file, trigger its compilation, and re-check.
254
+ if (fnId) {
255
+ try {
256
+ const decoded = JSON.parse(
257
+ Buffer.from(fnId, 'base64url').toString('utf8'),
258
+ )
259
+ if (
260
+ typeof decoded.file === 'string' &&
261
+ typeof decoded.export === 'string'
262
+ ) {
263
+ // Use the Vite strategy to decode the module specifier
264
+ // back to the original source file path.
265
+ const sourceFile = decodeViteDevServerModuleSpecifier(
266
+ decoded.file,
267
+ )
268
+
269
+ if (sourceFile) {
270
+ // Resolve to absolute path
271
+ const absPath = resolvePath(root, sourceFile)
272
+
273
+ // Trigger transform of the source file in this environment,
274
+ // which will compile createServerFn calls and populate
275
+ // serverFnsById as a side effect.
276
+ // This plugin only runs in dev (apply: 'serve'), so mode
277
+ // must be 'dev' — assert to narrow to DevEnvironment.
278
+ assert(this.environment.mode === 'dev')
279
+ await this.environment.fetchModule(absPath)
280
+
281
+ // Re-check after lazy compilation
282
+ if (serverFnsById[fnId]) {
283
+ return `export {}`
284
+ }
285
+ }
286
+ }
287
+ } catch {
288
+ // Decoding or fetching failed — fall through to error
289
+ }
290
+ }
291
+
292
+ this.error(`Invalid server function ID: ${fnId}`)
293
+ },
294
+ },
295
+ },
296
+ // Manifest plugin for server environments
297
+ {
298
+ name: 'tanstack-start-core:server-fn-resolver',
299
+ enforce: 'pre',
300
+ applyToEnvironment: (env) => {
301
+ return appliedResolverEnvironments.has(env.name)
302
+ },
303
+ configResolved(config) {
304
+ root = config.root
305
+ },
306
+ resolveId: {
307
+ filter: { id: new RegExp(VIRTUAL_MODULES.serverFnResolver) },
308
+ handler() {
309
+ return resolvedResolverVirtualImportId
310
+ },
311
+ },
312
+ load: {
313
+ filter: { id: new RegExp(resolvedResolverVirtualImportId) },
314
+ handler() {
315
+ if (this.environment.name !== opts.providerEnvName) {
316
+ const mod = opts.environments.find(
317
+ (e) => e.name === this.environment.name,
318
+ )?.getServerFnById
319
+ if (mod) {
320
+ return mod
321
+ } else {
322
+ this.error(
323
+ `No getServerFnById implementation found for caller environment: ${this.environment.name}`,
324
+ )
325
+ }
326
+ }
327
+
328
+ if (this.environment.mode !== 'build') {
329
+ return getDevServerFnValidatorModule()
330
+ }
331
+
332
+ // When SSR is the provider, server-only-referenced functions aren't in the manifest,
333
+ // so no isClientReferenced check is needed.
334
+ // When SSR is NOT the provider (custom provider env), server-only-referenced
335
+ // functions ARE in the manifest and need the isClientReferenced check to
336
+ // block direct client HTTP requests to server-only-referenced functions.
337
+ return generateServerFnResolverModule({
338
+ serverFnsById,
339
+ includeClientReferencedCheck: !ssrIsProvider,
340
+ })
341
+ },
342
+ },
343
+ },
344
+ ]
345
+ }