@tanstack/start-plugin-core 1.167.35 → 1.169.0

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 (187) hide show
  1. package/dist/esm/import-protection/adapterUtils.d.ts +27 -0
  2. package/dist/esm/import-protection/adapterUtils.js +31 -0
  3. package/dist/esm/import-protection/adapterUtils.js.map +1 -0
  4. package/dist/esm/import-protection/analysis.d.ts +36 -0
  5. package/dist/esm/import-protection/analysis.js +407 -0
  6. package/dist/esm/import-protection/analysis.js.map +1 -0
  7. package/dist/esm/{import-protection-plugin → import-protection}/ast.js +1 -1
  8. package/dist/esm/import-protection/ast.js.map +1 -0
  9. package/dist/esm/import-protection/constants.d.ts +11 -0
  10. package/dist/esm/{import-protection-plugin → import-protection}/constants.js +7 -2
  11. package/dist/esm/import-protection/constants.js.map +1 -0
  12. package/dist/esm/{import-protection-plugin → import-protection}/defaults.js +1 -1
  13. package/dist/esm/import-protection/defaults.js.map +1 -0
  14. package/dist/esm/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.js +2 -2
  15. package/dist/esm/import-protection/extensionlessAbsoluteIdResolver.js.map +1 -0
  16. package/dist/esm/{import-protection-plugin → import-protection}/matchers.js +1 -1
  17. package/dist/esm/import-protection/matchers.js.map +1 -0
  18. package/dist/esm/{import-protection-plugin/rewriteDeniedImports.d.ts → import-protection/rewrite.d.ts} +0 -4
  19. package/dist/esm/import-protection/rewrite.js +121 -0
  20. package/dist/esm/import-protection/rewrite.js.map +1 -0
  21. package/dist/esm/{import-protection-plugin → import-protection}/sourceLocation.d.ts +32 -3
  22. package/dist/esm/{import-protection-plugin → import-protection}/sourceLocation.js +65 -10
  23. package/dist/esm/import-protection/sourceLocation.js.map +1 -0
  24. package/dist/esm/{import-protection-plugin → import-protection}/trace.d.ts +0 -1
  25. package/dist/esm/{import-protection-plugin → import-protection}/trace.js +1 -1
  26. package/dist/esm/import-protection/trace.js.map +1 -0
  27. package/dist/esm/{import-protection-plugin → import-protection}/utils.d.ts +18 -1
  28. package/dist/esm/{import-protection-plugin → import-protection}/utils.js +13 -20
  29. package/dist/esm/import-protection/utils.js.map +1 -0
  30. package/dist/esm/import-protection/virtualModules.d.ts +25 -0
  31. package/dist/esm/{import-protection-plugin → import-protection}/virtualModules.js +5 -117
  32. package/dist/esm/import-protection/virtualModules.js.map +1 -0
  33. package/dist/esm/index.d.ts +1 -5
  34. package/dist/esm/index.js +2 -4
  35. package/dist/esm/post-build.d.ts +9 -0
  36. package/dist/esm/post-build.js +37 -0
  37. package/dist/esm/post-build.js.map +1 -0
  38. package/dist/esm/prerender.d.ts +11 -0
  39. package/dist/esm/prerender.js +159 -0
  40. package/dist/esm/prerender.js.map +1 -0
  41. package/dist/esm/rsbuild/dev-server.d.ts +21 -0
  42. package/dist/esm/rsbuild/dev-server.js +76 -0
  43. package/dist/esm/rsbuild/dev-server.js.map +1 -0
  44. package/dist/esm/rsbuild/import-protection.d.ts +10 -0
  45. package/dist/esm/rsbuild/import-protection.js +775 -0
  46. package/dist/esm/rsbuild/import-protection.js.map +1 -0
  47. package/dist/esm/rsbuild/index.d.ts +4 -0
  48. package/dist/esm/rsbuild/index.js +3 -0
  49. package/dist/esm/rsbuild/normalized-client-build.d.ts +18 -0
  50. package/dist/esm/rsbuild/normalized-client-build.js +207 -0
  51. package/dist/esm/rsbuild/normalized-client-build.js.map +1 -0
  52. package/dist/esm/rsbuild/planning.d.ts +52 -0
  53. package/dist/esm/rsbuild/planning.js +108 -0
  54. package/dist/esm/rsbuild/planning.js.map +1 -0
  55. package/dist/esm/rsbuild/plugin.d.ts +4 -0
  56. package/dist/esm/rsbuild/plugin.js +344 -0
  57. package/dist/esm/rsbuild/plugin.js.map +1 -0
  58. package/dist/esm/rsbuild/post-build.d.ts +6 -0
  59. package/dist/esm/rsbuild/post-build.js +57 -0
  60. package/dist/esm/rsbuild/post-build.js.map +1 -0
  61. package/dist/esm/rsbuild/schema.d.ts +3372 -0
  62. package/dist/esm/rsbuild/schema.js +12 -0
  63. package/dist/esm/rsbuild/schema.js.map +1 -0
  64. package/dist/esm/rsbuild/start-compiler-host.d.ts +20 -0
  65. package/dist/esm/rsbuild/start-compiler-host.js +150 -0
  66. package/dist/esm/rsbuild/start-compiler-host.js.map +1 -0
  67. package/dist/esm/rsbuild/start-router-plugin.d.ts +18 -0
  68. package/dist/esm/rsbuild/start-router-plugin.js +63 -0
  69. package/dist/esm/rsbuild/start-router-plugin.js.map +1 -0
  70. package/dist/esm/rsbuild/swc-rsc.d.ts +14 -0
  71. package/dist/esm/rsbuild/swc-rsc.js +93 -0
  72. package/dist/esm/rsbuild/swc-rsc.js.map +1 -0
  73. package/dist/esm/rsbuild/types.d.ts +17 -0
  74. package/dist/esm/rsbuild/types.js +0 -0
  75. package/dist/esm/rsbuild/virtual-modules.d.ts +53 -0
  76. package/dist/esm/rsbuild/virtual-modules.js +287 -0
  77. package/dist/esm/rsbuild/virtual-modules.js.map +1 -0
  78. package/dist/esm/schema.d.ts +43 -43
  79. package/dist/esm/start-compiler/compiler.d.ts +1 -1
  80. package/dist/esm/start-compiler/compiler.js +80 -9
  81. package/dist/esm/start-compiler/compiler.js.map +1 -1
  82. package/dist/esm/start-compiler/handleCreateServerFn.js +9 -0
  83. package/dist/esm/start-compiler/handleCreateServerFn.js.map +1 -1
  84. package/dist/esm/start-compiler/host.js +5 -1
  85. package/dist/esm/start-compiler/host.js.map +1 -1
  86. package/dist/esm/start-compiler/types.d.ts +1 -0
  87. package/dist/esm/utils.d.ts +1 -0
  88. package/dist/esm/utils.js +10 -1
  89. package/dist/esm/utils.js.map +1 -1
  90. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.js +41 -92
  91. package/dist/esm/vite/import-protection-plugin/plugin.js.map +1 -0
  92. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/types.d.ts +5 -5
  93. package/dist/esm/vite/import-protection-plugin/virtualModules.d.ts +8 -0
  94. package/dist/esm/vite/import-protection-plugin/virtualModules.js +49 -0
  95. package/dist/esm/vite/import-protection-plugin/virtualModules.js.map +1 -0
  96. package/dist/esm/vite/index.d.ts +5 -0
  97. package/dist/esm/vite/index.js +4 -0
  98. package/dist/esm/vite/plugin.js +1 -1
  99. package/dist/esm/vite/plugin.js.map +1 -1
  100. package/dist/esm/vite/post-server-build.js +14 -32
  101. package/dist/esm/vite/post-server-build.js.map +1 -1
  102. package/dist/esm/vite/prerender.d.ts +2 -2
  103. package/dist/esm/vite/prerender.js +17 -147
  104. package/dist/esm/vite/prerender.js.map +1 -1
  105. package/dist/esm/vite/schema.d.ts +23 -23
  106. package/dist/esm/vite/start-compiler-plugin/hot-update.d.ts +2 -0
  107. package/dist/esm/vite/start-compiler-plugin/hot-update.js +16 -0
  108. package/dist/esm/vite/start-compiler-plugin/hot-update.js.map +1 -0
  109. package/dist/esm/vite/start-compiler-plugin/module-specifier.js +9 -4
  110. package/dist/esm/vite/start-compiler-plugin/module-specifier.js.map +1 -1
  111. package/dist/esm/vite/start-compiler-plugin/plugin.js +86 -13
  112. package/dist/esm/vite/start-compiler-plugin/plugin.js.map +1 -1
  113. package/package.json +32 -4
  114. package/src/import-protection/INTERNALS.md +266 -0
  115. package/src/import-protection/adapterUtils.ts +94 -0
  116. package/src/import-protection/analysis.ts +853 -0
  117. package/src/{import-protection-plugin → import-protection}/constants.ts +7 -0
  118. package/src/import-protection/rewrite.ts +229 -0
  119. package/src/{import-protection-plugin → import-protection}/sourceLocation.ts +125 -9
  120. package/src/{import-protection-plugin → import-protection}/trace.ts +0 -1
  121. package/src/{import-protection-plugin → import-protection}/utils.ts +36 -21
  122. package/src/{import-protection-plugin → import-protection}/virtualModules.ts +30 -177
  123. package/src/index.ts +1 -8
  124. package/src/post-build.ts +64 -0
  125. package/src/prerender.ts +292 -0
  126. package/src/rsbuild/INTERNALS-import-protection.md +169 -0
  127. package/src/rsbuild/dev-server.ts +129 -0
  128. package/src/rsbuild/import-protection.ts +1599 -0
  129. package/src/rsbuild/index.ts +4 -0
  130. package/src/rsbuild/normalized-client-build.ts +346 -0
  131. package/src/rsbuild/planning.ts +234 -0
  132. package/src/rsbuild/plugin.ts +754 -0
  133. package/src/rsbuild/post-build.ts +96 -0
  134. package/src/rsbuild/schema.ts +31 -0
  135. package/src/rsbuild/start-compiler-host.ts +250 -0
  136. package/src/rsbuild/start-router-plugin.ts +86 -0
  137. package/src/rsbuild/swc-rsc.ts +166 -0
  138. package/src/rsbuild/types.ts +20 -0
  139. package/src/rsbuild/virtual-modules.ts +565 -0
  140. package/src/start-compiler/compiler.ts +153 -19
  141. package/src/start-compiler/handleCreateServerFn.ts +18 -0
  142. package/src/start-compiler/types.ts +1 -0
  143. package/src/utils.ts +14 -0
  144. package/src/vite/import-protection-plugin/INTERNALS.md +187 -0
  145. package/src/{import-protection-plugin → vite/import-protection-plugin}/plugin.ts +73 -158
  146. package/src/{import-protection-plugin → vite/import-protection-plugin}/types.ts +5 -5
  147. package/src/vite/import-protection-plugin/virtualModules.ts +122 -0
  148. package/src/vite/index.ts +8 -0
  149. package/src/vite/plugin.ts +1 -1
  150. package/src/vite/post-server-build.ts +14 -57
  151. package/src/vite/prerender.ts +19 -260
  152. package/src/vite/start-compiler-plugin/hot-update.ts +24 -0
  153. package/src/vite/start-compiler-plugin/module-specifier.ts +15 -5
  154. package/src/vite/start-compiler-plugin/plugin.ts +193 -18
  155. package/dist/esm/import-protection-plugin/ast.js.map +0 -1
  156. package/dist/esm/import-protection-plugin/constants.d.ts +0 -6
  157. package/dist/esm/import-protection-plugin/constants.js.map +0 -1
  158. package/dist/esm/import-protection-plugin/defaults.js.map +0 -1
  159. package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js.map +0 -1
  160. package/dist/esm/import-protection-plugin/matchers.js.map +0 -1
  161. package/dist/esm/import-protection-plugin/plugin.js.map +0 -1
  162. package/dist/esm/import-protection-plugin/postCompileUsage.d.ts +0 -13
  163. package/dist/esm/import-protection-plugin/postCompileUsage.js +0 -63
  164. package/dist/esm/import-protection-plugin/postCompileUsage.js.map +0 -1
  165. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +0 -205
  166. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +0 -1
  167. package/dist/esm/import-protection-plugin/sourceLocation.js.map +0 -1
  168. package/dist/esm/import-protection-plugin/trace.js.map +0 -1
  169. package/dist/esm/import-protection-plugin/utils.js.map +0 -1
  170. package/dist/esm/import-protection-plugin/virtualModules.d.ts +0 -78
  171. package/dist/esm/import-protection-plugin/virtualModules.js.map +0 -1
  172. package/dist/esm/start-compiler/load-module.d.ts +0 -14
  173. package/dist/esm/start-compiler/load-module.js +0 -18
  174. package/dist/esm/start-compiler/load-module.js.map +0 -1
  175. package/src/import-protection-plugin/INTERNALS.md +0 -700
  176. package/src/import-protection-plugin/postCompileUsage.ts +0 -100
  177. package/src/import-protection-plugin/rewriteDeniedImports.ts +0 -379
  178. package/src/start-compiler/load-module.ts +0 -31
  179. /package/dist/esm/{import-protection-plugin → import-protection}/ast.d.ts +0 -0
  180. /package/dist/esm/{import-protection-plugin → import-protection}/defaults.d.ts +0 -0
  181. /package/dist/esm/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.d.ts +0 -0
  182. /package/dist/esm/{import-protection-plugin → import-protection}/matchers.d.ts +0 -0
  183. /package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.d.ts +0 -0
  184. /package/src/{import-protection-plugin → import-protection}/ast.ts +0 -0
  185. /package/src/{import-protection-plugin → import-protection}/defaults.ts +0 -0
  186. /package/src/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.ts +0 -0
  187. /package/src/{import-protection-plugin → import-protection}/matchers.ts +0 -0
@@ -1,100 +1,15 @@
1
- import { resolveViteId } from '../utils'
2
- import { VITE_ENVIRONMENT_NAMES } from '../constants'
3
- import { isValidExportName } from './rewriteDeniedImports'
1
+ import {
2
+ MARKER_PREFIX,
3
+ MOCK_BUILD_PREFIX,
4
+ MOCK_EDGE_PREFIX,
5
+ MOCK_MODULE_ID,
6
+ MOCK_RUNTIME_PREFIX,
7
+ } from './constants'
8
+ import { isValidExportName } from './analysis'
4
9
  import { CLIENT_ENV_SUGGESTIONS } from './trace'
5
- import { VITE_BROWSER_VIRTUAL_PREFIX } from './constants'
6
10
  import { relativizePath } from './utils'
7
11
  import type { ViolationInfo } from './trace'
8
12
 
9
- export const MOCK_MODULE_ID = 'tanstack-start-import-protection:mock'
10
- const RESOLVED_MOCK_MODULE_ID = resolveViteId(MOCK_MODULE_ID)
11
-
12
- /**
13
- * Per-violation mock prefix used in build+error mode.
14
- * Each deferred violation gets a unique ID so we can check which ones
15
- * survived tree-shaking in `generateBundle`.
16
- */
17
- export const MOCK_BUILD_PREFIX = 'tanstack-start-import-protection:mock:build:'
18
- const RESOLVED_MOCK_BUILD_PREFIX = resolveViteId(MOCK_BUILD_PREFIX)
19
-
20
- export const MOCK_EDGE_PREFIX = 'tanstack-start-import-protection:mock-edge:'
21
- const RESOLVED_MOCK_EDGE_PREFIX = resolveViteId(MOCK_EDGE_PREFIX)
22
-
23
- export const MOCK_RUNTIME_PREFIX =
24
- 'tanstack-start-import-protection:mock-runtime:'
25
- const RESOLVED_MOCK_RUNTIME_PREFIX = resolveViteId(MOCK_RUNTIME_PREFIX)
26
-
27
- const MARKER_PREFIX = 'tanstack-start-import-protection:marker:'
28
- const RESOLVED_MARKER_PREFIX = resolveViteId(MARKER_PREFIX)
29
-
30
- const RESOLVED_MARKER_SERVER_ONLY = resolveViteId(`${MARKER_PREFIX}server-only`)
31
- const RESOLVED_MARKER_CLIENT_ONLY = resolveViteId(`${MARKER_PREFIX}client-only`)
32
-
33
- export function resolvedMarkerVirtualModuleId(
34
- kind: 'server' | 'client',
35
- ): string {
36
- return kind === 'server'
37
- ? RESOLVED_MARKER_SERVER_ONLY
38
- : RESOLVED_MARKER_CLIENT_ONLY
39
- }
40
-
41
- /**
42
- * Convenience list for plugin `load` filters/handlers.
43
- *
44
- * Vite/Rollup call `load(id)` with the *resolved* virtual id (prefixed by `\0`).
45
- * `resolveId(source)` sees the *unresolved* id/prefix (without `\0`).
46
- */
47
- export function getResolvedVirtualModuleMatchers(): ReadonlyArray<string> {
48
- return RESOLVED_VIRTUAL_MODULE_MATCHERS
49
- }
50
-
51
- const RESOLVED_VIRTUAL_MODULE_MATCHERS = [
52
- RESOLVED_MOCK_MODULE_ID,
53
- RESOLVED_MOCK_BUILD_PREFIX,
54
- RESOLVED_MOCK_EDGE_PREFIX,
55
- RESOLVED_MOCK_RUNTIME_PREFIX,
56
- RESOLVED_MARKER_PREFIX,
57
- ] as const
58
-
59
- const RESOLVE_PREFIX_PAIRS = [
60
- [MOCK_EDGE_PREFIX, RESOLVED_MOCK_EDGE_PREFIX],
61
- [MOCK_RUNTIME_PREFIX, RESOLVED_MOCK_RUNTIME_PREFIX],
62
- [MOCK_BUILD_PREFIX, RESOLVED_MOCK_BUILD_PREFIX],
63
- [MARKER_PREFIX, RESOLVED_MARKER_PREFIX],
64
- ] as const
65
-
66
- /**
67
- * Resolve import-protection's internal virtual module IDs.
68
- *
69
- * `resolveId(source)` sees *unresolved* ids/prefixes (no `\0`).
70
- * Returning a resolved id (with `\0`) ensures Vite/Rollup route it to `load`.
71
- */
72
- export function resolveInternalVirtualModuleId(
73
- source: string,
74
- ): string | undefined {
75
- if (source.startsWith(VITE_BROWSER_VIRTUAL_PREFIX)) {
76
- return resolveInternalVirtualModuleId(
77
- `\0${source.slice(VITE_BROWSER_VIRTUAL_PREFIX.length)}`,
78
- )
79
- }
80
-
81
- if (source === MOCK_MODULE_ID || source === RESOLVED_MOCK_MODULE_ID) {
82
- return RESOLVED_MOCK_MODULE_ID
83
- }
84
-
85
- for (const [unresolvedPrefix, resolvedPrefix] of RESOLVE_PREFIX_PAIRS) {
86
- if (source.startsWith(unresolvedPrefix)) {
87
- return resolveViteId(source)
88
- }
89
-
90
- if (source.startsWith(resolvedPrefix)) {
91
- return source
92
- }
93
- }
94
-
95
- return undefined
96
- }
97
-
98
13
  function toBase64Url(input: string): string {
99
14
  return Buffer.from(input, 'utf8').toString('base64url')
100
15
  }
@@ -105,10 +20,6 @@ function fromBase64Url(input: string): string {
105
20
 
106
21
  type MockAccessMode = 'error' | 'warn' | 'off'
107
22
 
108
- /**
109
- * Compact runtime suggestion text for browser console, derived from
110
- * {@link CLIENT_ENV_SUGGESTIONS} so there's a single source of truth.
111
- */
112
23
  export const RUNTIME_SUGGESTION_TEXT =
113
24
  'Fix: ' +
114
25
  CLIENT_ENV_SUGGESTIONS.join('. ') +
@@ -120,7 +31,7 @@ export function mockRuntimeModuleIdFromViolation(
120
31
  root: string,
121
32
  ): string {
122
33
  if (mode === 'off') return MOCK_MODULE_ID
123
- if (info.env !== VITE_ENVIRONMENT_NAMES.client) return MOCK_MODULE_ID
34
+ if (info.env !== 'client') return MOCK_MODULE_ID
124
35
 
125
36
  const rel = (p: string) => relativizePath(p, root)
126
37
  const trace = info.trace.map((s) => {
@@ -136,6 +47,7 @@ export function mockRuntimeModuleIdFromViolation(
136
47
  trace,
137
48
  mode,
138
49
  }
50
+
139
51
  return `${MOCK_RUNTIME_PREFIX}${toBase64Url(JSON.stringify(payload))}`
140
52
  }
141
53
 
@@ -147,17 +59,6 @@ export function makeMockEdgeModuleId(
147
59
  return `${MOCK_EDGE_PREFIX}${toBase64Url(JSON.stringify(payload))}`
148
60
  }
149
61
 
150
- /**
151
- * Generate a recursive Proxy-based mock module.
152
- *
153
- * When `diagnostics` is provided, the generated code includes a `__report`
154
- * function that logs runtime warnings/errors when the mock is actually used
155
- * (property access for primitive coercion, calls, construction, sets).
156
- *
157
- * When `diagnostics` is omitted, the mock is completely silent — suitable
158
- * for base mock modules (e.g. `MOCK_MODULE_ID` or per-violation build mocks)
159
- * that are consumed by mock-edge modules providing explicit named exports.
160
- */
161
62
  function generateMockCode(diagnostics?: {
162
63
  meta: {
163
64
  env: string
@@ -203,7 +104,6 @@ function __report(action, accessPath) {
203
104
  `
204
105
  : ''
205
106
 
206
- // Diagnostic-only traps for primitive coercion, set
207
107
  const diagGetTraps = hasDiag
208
108
  ? `
209
109
  if (prop === Symbol.toPrimitive) {
@@ -276,19 +176,10 @@ export function loadSilentMockModule(): { code: string } {
276
176
  return { code: generateMockCode() }
277
177
  }
278
178
 
279
- /**
280
- * Filter export names to valid, non-default names.
281
- */
282
179
  function filterExportNames(exports: ReadonlyArray<string>): Array<string> {
283
180
  return exports.filter((n) => n.length > 0 && n !== 'default')
284
181
  }
285
182
 
286
- /**
287
- * Generate ESM export lines that re-export named properties from `mock`.
288
- *
289
- * Produces `export const foo = mock.foo;` for valid identifiers and
290
- * string-keyed re-exports for non-identifier names.
291
- */
292
183
  function generateExportLines(names: ReadonlyArray<string>): Array<string> {
293
184
  const lines: Array<string> = []
294
185
  const stringExports: Array<{ alias: string; name: string }> = []
@@ -314,17 +205,6 @@ function generateExportLines(names: ReadonlyArray<string>): Array<string> {
314
205
  return lines
315
206
  }
316
207
 
317
- /**
318
- * Generate a self-contained mock module with explicit named exports.
319
- *
320
- * Used by the transform hook's "self-denial" check: when a denied file
321
- * (e.g. `.server.ts` in the client environment) is transformed, its entire
322
- * content is replaced with this mock module. This avoids returning virtual
323
- * module IDs from `resolveId`, which prevents cross-environment cache
324
- * contamination from third-party resolver plugins.
325
- *
326
- * The generated code is side-effect-free and tree-shakeable.
327
- */
328
208
  export function generateSelfContainedMockModule(exportNames: Array<string>): {
329
209
  code: string
330
210
  } {
@@ -338,18 +218,6 @@ ${exportLines.join('\n')}
338
218
  }
339
219
  }
340
220
 
341
- /**
342
- * Generate a dev-mode mock module for self-denial transforms.
343
- *
344
- * Similar to `loadMockEdgeModule` but takes export names and a runtime ID
345
- * directly (instead of parsing them from a base64url-encoded payload).
346
- * Used by the transform hook when a denied file (e.g. `.server.ts` in
347
- * the client environment) is replaced in dev mode.
348
- *
349
- * The generated module imports mock-runtime for runtime diagnostics
350
- * (error/warn on property access) and re-exports explicit named exports
351
- * so that `import { foo } from './denied.server'` works.
352
- */
353
221
  export function generateDevSelfDenialModule(
354
222
  exportNames: Array<string>,
355
223
  runtimeId: string,
@@ -367,18 +235,18 @@ export default mock;
367
235
 
368
236
  export function loadMockEdgeModule(encodedPayload: string): { code: string } {
369
237
  let payload: { exports?: Array<string>; runtimeId?: string }
238
+
370
239
  try {
371
240
  payload = JSON.parse(fromBase64Url(encodedPayload)) as typeof payload
372
241
  } catch {
373
242
  payload = { exports: [] }
374
243
  }
375
- const names = filterExportNames(payload.exports ?? [])
376
244
 
377
- const runtimeId: string =
245
+ const names = filterExportNames(payload.exports ?? [])
246
+ const runtimeId =
378
247
  typeof payload.runtimeId === 'string' && payload.runtimeId.length > 0
379
248
  ? payload.runtimeId
380
249
  : MOCK_MODULE_ID
381
-
382
250
  const exportLines = generateExportLines(names)
383
251
 
384
252
  return {
@@ -399,6 +267,7 @@ export function loadMockRuntimeModule(encodedPayload: string): {
399
267
  specifier?: string
400
268
  trace?: Array<unknown>
401
269
  }
270
+
402
271
  try {
403
272
  payload = JSON.parse(fromBase64Url(encodedPayload)) as typeof payload
404
273
  } catch {
@@ -408,14 +277,17 @@ export function loadMockRuntimeModule(encodedPayload: string): {
408
277
  const mode: 'error' | 'warn' | 'off' =
409
278
  payload.mode === 'warn' || payload.mode === 'off' ? payload.mode : 'error'
410
279
 
411
- const meta = {
412
- env: String(payload.env ?? ''),
413
- importer: String(payload.importer ?? ''),
414
- specifier: String(payload.specifier ?? ''),
415
- trace: Array.isArray(payload.trace) ? payload.trace : [],
280
+ return {
281
+ code: generateMockCode({
282
+ meta: {
283
+ env: String(payload.env ?? ''),
284
+ importer: String(payload.importer ?? ''),
285
+ specifier: String(payload.specifier ?? ''),
286
+ trace: Array.isArray(payload.trace) ? payload.trace : [],
287
+ },
288
+ mode,
289
+ }),
416
290
  }
417
-
418
- return { code: generateMockCode({ meta, mode }) }
419
291
  }
420
292
 
421
293
  const MARKER_MODULE_RESULT = { code: 'export {}' } as const
@@ -424,29 +296,10 @@ export function loadMarkerModule(): { code: string } {
424
296
  return MARKER_MODULE_RESULT
425
297
  }
426
298
 
427
- export function loadResolvedVirtualModule(
428
- id: string,
429
- ): { code: string } | undefined {
430
- if (id === RESOLVED_MOCK_MODULE_ID) {
431
- return loadSilentMockModule()
432
- }
433
-
434
- // Per-violation build mock modules — same silent mock code
435
- if (id.startsWith(RESOLVED_MOCK_BUILD_PREFIX)) {
436
- return loadSilentMockModule()
437
- }
438
-
439
- if (id.startsWith(RESOLVED_MOCK_EDGE_PREFIX)) {
440
- return loadMockEdgeModule(id.slice(RESOLVED_MOCK_EDGE_PREFIX.length))
441
- }
442
-
443
- if (id.startsWith(RESOLVED_MOCK_RUNTIME_PREFIX)) {
444
- return loadMockRuntimeModule(id.slice(RESOLVED_MOCK_RUNTIME_PREFIX.length))
445
- }
446
-
447
- if (id.startsWith(RESOLVED_MARKER_PREFIX)) {
448
- return loadMarkerModule()
449
- }
450
-
451
- return undefined
299
+ export {
300
+ MARKER_PREFIX,
301
+ MOCK_BUILD_PREFIX,
302
+ MOCK_EDGE_PREFIX,
303
+ MOCK_MODULE_ID,
304
+ MOCK_RUNTIME_PREFIX,
452
305
  }
package/src/index.ts CHANGED
@@ -1,10 +1,3 @@
1
1
  export type { TanStackStartInputConfig } from './schema'
2
2
  export type { TanStackStartCoreOptions } from './types'
3
- export type {
4
- TanStackStartVitePluginCoreOptions,
5
- ViteRscForwardSsrResolverStrategy,
6
- } from './vite/types'
7
- export type { TanStackStartViteInputConfig } from './vite/schema'
8
- export { START_ENVIRONMENT_NAMES, VITE_ENVIRONMENT_NAMES } from './constants'
9
- export { createVirtualModule } from './vite/createVirtualModule'
10
- export { tanStackStartVite } from './vite/plugin'
3
+ export { START_ENVIRONMENT_NAMES } from './constants'
@@ -0,0 +1,64 @@
1
+ import { HEADERS } from '@tanstack/start-server-core'
2
+ import { buildSitemap } from './build-sitemap'
3
+ import type { TanStackStartOutputConfig } from './schema'
4
+
5
+ export interface StartPostBuildAdapter {
6
+ getClientOutputDirectory: () => string
7
+ prerender: (startConfig: TanStackStartOutputConfig) => Promise<void>
8
+ }
9
+
10
+ export async function postBuild({
11
+ startConfig,
12
+ adapter,
13
+ }: {
14
+ startConfig: TanStackStartOutputConfig
15
+ adapter: StartPostBuildAdapter
16
+ }) {
17
+ if (startConfig.prerender?.enabled !== false) {
18
+ startConfig.prerender = {
19
+ ...startConfig.prerender,
20
+ enabled:
21
+ startConfig.prerender?.enabled ??
22
+ startConfig.pages.some((d) =>
23
+ typeof d === 'string' ? false : !!d.prerender?.enabled,
24
+ ),
25
+ }
26
+ }
27
+
28
+ if (startConfig.spa?.enabled) {
29
+ startConfig.prerender = {
30
+ ...startConfig.prerender,
31
+ enabled: true,
32
+ }
33
+
34
+ const maskUrl = new URL(startConfig.spa.maskPath, 'http://localhost')
35
+ if (maskUrl.origin !== 'http://localhost') {
36
+ throw new Error('spa.maskPath must be a path (no protocol/host)')
37
+ }
38
+
39
+ startConfig.pages.push({
40
+ path: maskUrl.toString().replace('http://localhost', ''),
41
+ prerender: {
42
+ ...startConfig.spa.prerender,
43
+ headers: {
44
+ ...startConfig.spa.prerender.headers,
45
+ [HEADERS.TSS_SHELL]: 'true',
46
+ },
47
+ },
48
+ sitemap: {
49
+ exclude: true,
50
+ },
51
+ })
52
+ }
53
+
54
+ if (startConfig.prerender.enabled) {
55
+ await adapter.prerender(startConfig)
56
+ }
57
+
58
+ if (startConfig.sitemap?.enabled) {
59
+ buildSitemap({
60
+ startConfig,
61
+ publicDir: adapter.getClientOutputDirectory(),
62
+ })
63
+ }
64
+ }
@@ -0,0 +1,292 @@
1
+ import { promises as fsp } from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'pathe'
4
+ import { joinURL, withBase, withTrailingSlash, withoutBase } from 'ufo'
5
+ import { createLogger } from './utils'
6
+ import { Queue } from './queue'
7
+ import type { Page, TanStackStartOutputConfig } from './schema'
8
+
9
+ const DEFAULT_RETRY_DELAY = 500
10
+
11
+ export interface PrerenderHandler {
12
+ getClientOutputDirectory: () => string
13
+ request: (path: string, options?: RequestInit) => Promise<Response>
14
+ close?: () => Promise<void>
15
+ }
16
+
17
+ export async function prerender({
18
+ startConfig,
19
+ handler,
20
+ }: {
21
+ startConfig: TanStackStartOutputConfig
22
+ handler: PrerenderHandler
23
+ }) {
24
+ const logger = createLogger('prerender')
25
+ logger.info('Prerendering pages...')
26
+
27
+ if (startConfig.prerender?.enabled) {
28
+ let pages = startConfig.pages.length ? startConfig.pages : [{ path: '/' }]
29
+
30
+ if (startConfig.prerender.autoStaticPathsDiscovery ?? true) {
31
+ const pagesMap = new Map(pages.map((item) => [item.path, item]))
32
+ const discoveredPages = globalThis.TSS_PRERENDABLE_PATHS || []
33
+
34
+ for (const page of discoveredPages) {
35
+ if (!pagesMap.has(page.path)) {
36
+ pagesMap.set(page.path, page)
37
+ }
38
+ }
39
+
40
+ pages = Array.from(pagesMap.values())
41
+ }
42
+
43
+ startConfig.pages = pages
44
+ }
45
+
46
+ const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')
47
+ const routerBaseUrl = new URL(routerBasePath, 'http://localhost')
48
+
49
+ startConfig.pages = validateAndNormalizePrerenderPages(
50
+ startConfig.pages,
51
+ routerBaseUrl,
52
+ )
53
+
54
+ try {
55
+ const pages = await prerenderPages({
56
+ outputDir: handler.getClientOutputDirectory(),
57
+ })
58
+
59
+ logger.info(`Prerendered ${pages.length} pages:`)
60
+ pages.forEach((page) => {
61
+ logger.info(`- ${page}`)
62
+ })
63
+ } catch (error) {
64
+ logger.error(error)
65
+ throw error
66
+ } finally {
67
+ await handler.close?.()
68
+ }
69
+
70
+ function extractLinks(html: string): Array<string> {
71
+ const linkRegex = /<a[^>]+href=["']([^"']+)["'][^>]*>/g
72
+ const links: Array<string> = []
73
+ let match: RegExpExecArray | null
74
+
75
+ while ((match = linkRegex.exec(html)) !== null) {
76
+ const href = match[1]
77
+ if (href && (href.startsWith('/') || href.startsWith('./'))) {
78
+ links.push(href)
79
+ }
80
+ }
81
+
82
+ return links
83
+ }
84
+
85
+ async function prerenderPages({ outputDir }: { outputDir: string }) {
86
+ const seen = new Set<string>()
87
+ const prerendered = new Set<string>()
88
+ const retriesByPath = new Map<string, number>()
89
+ const concurrency = startConfig.prerender?.concurrency ?? os.cpus().length
90
+ logger.info(`Concurrency: ${concurrency}`)
91
+ const queue = new Queue({ concurrency })
92
+ const routerBasePath = joinURL('/', startConfig.router.basepath ?? '')
93
+ const routerBaseUrl = new URL(routerBasePath, 'http://localhost')
94
+
95
+ startConfig.pages = validateAndNormalizePrerenderPages(
96
+ startConfig.pages,
97
+ routerBaseUrl,
98
+ )
99
+
100
+ startConfig.pages.forEach((page) => addCrawlPageTask(page))
101
+
102
+ if (queue.isSettled()) {
103
+ logger.info('No pages matched prerender filter; skipping.')
104
+ return Array.from(prerendered)
105
+ }
106
+
107
+ await queue.start()
108
+
109
+ return Array.from(prerendered)
110
+
111
+ function addCrawlPageTask(page: Page) {
112
+ if (seen.has(page.path)) return
113
+
114
+ seen.add(page.path)
115
+
116
+ if (page.fromCrawl) {
117
+ startConfig.pages.push(page)
118
+ }
119
+
120
+ if (!(page.prerender?.enabled ?? true)) return
121
+
122
+ if (
123
+ startConfig.prerender?.filter &&
124
+ !startConfig.prerender.filter(page)
125
+ ) {
126
+ return
127
+ }
128
+
129
+ const prerenderOptions = {
130
+ ...startConfig.prerender,
131
+ ...page.prerender,
132
+ }
133
+
134
+ queue.add(async () => {
135
+ logger.info(`Crawling: ${page.path}`)
136
+ const retries = retriesByPath.get(page.path) || 0
137
+
138
+ try {
139
+ const res = await requestWithRedirects(
140
+ withTrailingSlash(withBase(page.path, routerBasePath)),
141
+ {
142
+ headers: {
143
+ ...(prerenderOptions.headers ?? {}),
144
+ },
145
+ },
146
+ prerenderOptions.maxRedirects,
147
+ )
148
+
149
+ if (!res.ok) {
150
+ if (isRedirectResponse(res)) {
151
+ logger.warn(`Max redirects reached for ${page.path}`)
152
+ }
153
+
154
+ throw new Error(`Failed to fetch ${page.path}: ${res.statusText}`, {
155
+ cause: res,
156
+ })
157
+ }
158
+
159
+ const cleanPagePath = (
160
+ prerenderOptions.outputPath || page.path
161
+ ).split(/[?#]/)[0]!
162
+
163
+ const contentType = res.headers.get('content-type') || ''
164
+ const isImplicitHTML =
165
+ !cleanPagePath.endsWith('.html') && contentType.includes('html')
166
+
167
+ const routeWithIndex = cleanPagePath.endsWith('/')
168
+ ? cleanPagePath + 'index'
169
+ : cleanPagePath
170
+
171
+ const isSpaShell =
172
+ startConfig.spa?.prerender.outputPath === cleanPagePath
173
+
174
+ let htmlPath: string
175
+ if (isSpaShell) {
176
+ htmlPath = cleanPagePath + '.html'
177
+ } else if (
178
+ cleanPagePath.endsWith('/') ||
179
+ (prerenderOptions.autoSubfolderIndex ?? true)
180
+ ) {
181
+ htmlPath = joinURL(cleanPagePath, 'index.html')
182
+ } else {
183
+ htmlPath = cleanPagePath + '.html'
184
+ }
185
+
186
+ const filename = withoutBase(
187
+ isImplicitHTML ? htmlPath : routeWithIndex,
188
+ routerBasePath,
189
+ )
190
+
191
+ const html = await res.text()
192
+ const filepath = path.join(outputDir, filename)
193
+
194
+ await fsp.mkdir(path.dirname(filepath), {
195
+ recursive: true,
196
+ })
197
+
198
+ await fsp.writeFile(filepath, html)
199
+
200
+ prerendered.add(page.path)
201
+
202
+ const newPage = await prerenderOptions.onSuccess?.({ page, html })
203
+
204
+ if (newPage) {
205
+ Object.assign(page, newPage)
206
+ }
207
+
208
+ if (prerenderOptions.crawlLinks ?? true) {
209
+ const links = extractLinks(html)
210
+ for (const link of links) {
211
+ addCrawlPageTask({ path: link, fromCrawl: true })
212
+ }
213
+ }
214
+ } catch (error) {
215
+ if (retries < (prerenderOptions.retryCount ?? 0)) {
216
+ const retryDelay = normalizeRetryDelay(prerenderOptions.retryDelay)
217
+ logger.warn(
218
+ `Encountered error, retrying: ${page.path} in ${retryDelay}ms`,
219
+ )
220
+ await new Promise((resolve) => setTimeout(resolve, retryDelay))
221
+ retriesByPath.set(page.path, retries + 1)
222
+ addCrawlPageTask(page)
223
+ } else if (prerenderOptions.failOnError ?? true) {
224
+ throw error
225
+ }
226
+ }
227
+ })
228
+ }
229
+ }
230
+
231
+ function normalizeRetryDelay(value: number | undefined): number {
232
+ const retryDelay = Number(value)
233
+
234
+ if (!Number.isFinite(retryDelay) || retryDelay < 0) {
235
+ return DEFAULT_RETRY_DELAY
236
+ }
237
+
238
+ return Math.trunc(retryDelay)
239
+ }
240
+
241
+ async function requestWithRedirects(
242
+ path: string,
243
+ options?: RequestInit,
244
+ maxRedirects: number = 5,
245
+ ): Promise<Response> {
246
+ const response = await handler.request(path, options)
247
+
248
+ if (isRedirectResponse(response) && maxRedirects > 0) {
249
+ const location = response.headers.get('location')!
250
+
251
+ if (location.startsWith('http://localhost') || location.startsWith('/')) {
252
+ const nextPath = location.replace('http://localhost', '')
253
+ return requestWithRedirects(nextPath, options, maxRedirects - 1)
254
+ }
255
+
256
+ logger.warn(`Skipping redirect to external location: ${location}`)
257
+ }
258
+
259
+ return response
260
+ }
261
+ }
262
+
263
+ function isRedirectResponse(res: Response) {
264
+ return res.status >= 300 && res.status < 400 && res.headers.get('location')
265
+ }
266
+
267
+ export function validateAndNormalizePrerenderPages(
268
+ pages: Array<Page>,
269
+ routerBaseUrl: URL,
270
+ ): Array<Page> {
271
+ return pages.map((page) => {
272
+ let url: URL
273
+ try {
274
+ url = new URL(page.path, routerBaseUrl)
275
+ } catch (err) {
276
+ throw new Error(`prerender page path must be relative: ${page.path}`, {
277
+ cause: err,
278
+ })
279
+ }
280
+
281
+ if (url.origin !== 'http://localhost') {
282
+ throw new Error(`prerender page path must be relative: ${page.path}`)
283
+ }
284
+
285
+ const decodedPathname = decodeURIComponent(url.pathname)
286
+
287
+ return {
288
+ ...page,
289
+ path: decodedPathname + url.search + url.hash,
290
+ }
291
+ })
292
+ }