@tanstack/start-plugin-core 1.121.0-alpha.2 → 1.121.0-alpha.21

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 (137) hide show
  1. package/dist/cjs/constants.cjs +4 -0
  2. package/dist/cjs/constants.cjs.map +1 -1
  3. package/dist/cjs/constants.d.cts +2 -0
  4. package/dist/cjs/{extractHtmlScripts.cjs → dev-server-plugin/extract-html-scripts.cjs} +1 -1
  5. package/dist/cjs/dev-server-plugin/extract-html-scripts.cjs.map +1 -0
  6. package/dist/cjs/{nitro/dev-server-plugin.cjs → dev-server-plugin/plugin.cjs} +2 -2
  7. package/dist/cjs/dev-server-plugin/plugin.cjs.map +1 -0
  8. package/dist/cjs/index.cjs +2 -0
  9. package/dist/cjs/index.cjs.map +1 -1
  10. package/dist/cjs/index.d.cts +1 -0
  11. package/dist/cjs/load-env-plugin/plugin.cjs +34 -0
  12. package/dist/cjs/load-env-plugin/plugin.cjs.map +1 -0
  13. package/dist/cjs/load-env-plugin/plugin.d.cts +3 -0
  14. package/dist/cjs/nitro-plugin/build-sitemap.cjs +138 -0
  15. package/dist/cjs/nitro-plugin/build-sitemap.cjs.map +1 -0
  16. package/dist/cjs/nitro-plugin/build-sitemap.d.cts +31 -0
  17. package/dist/cjs/{nitro/nitro-plugin.cjs → nitro-plugin/plugin.cjs} +77 -54
  18. package/dist/cjs/nitro-plugin/plugin.cjs.map +1 -0
  19. package/dist/cjs/{prerender.cjs → nitro-plugin/prerender.cjs} +12 -17
  20. package/dist/cjs/nitro-plugin/prerender.cjs.map +1 -0
  21. package/dist/cjs/{prerender.d.cts → nitro-plugin/prerender.d.cts} +1 -1
  22. package/dist/cjs/nitro-plugin/queue.cjs.map +1 -0
  23. package/dist/cjs/plugin.cjs +38 -27
  24. package/dist/cjs/plugin.cjs.map +1 -1
  25. package/dist/cjs/plugin.d.cts +25 -3064
  26. package/dist/cjs/resolve-virtual-entries-plugin/plugin.cjs +66 -0
  27. package/dist/cjs/resolve-virtual-entries-plugin/plugin.cjs.map +1 -0
  28. package/dist/cjs/resolve-virtual-entries-plugin/plugin.d.cts +3 -0
  29. package/dist/cjs/schema.cjs +6 -4
  30. package/dist/cjs/schema.cjs.map +1 -1
  31. package/dist/cjs/schema.d.cts +530 -1656
  32. package/dist/cjs/start-compiler-plugin.cjs +2 -2
  33. package/dist/cjs/start-compiler-plugin.cjs.map +1 -1
  34. package/dist/cjs/start-compiler-plugin.d.cts +1 -1
  35. package/dist/cjs/{routesManifestPlugin.cjs → start-routes-manifest-plugin/plugin.cjs} +86 -44
  36. package/dist/cjs/start-routes-manifest-plugin/plugin.cjs.map +1 -0
  37. package/dist/cjs/start-routes-manifest-plugin/plugin.d.cts +3 -0
  38. package/dist/cjs/start-server-routes-plugin/plugin.cjs +4 -2
  39. package/dist/cjs/start-server-routes-plugin/plugin.cjs.map +1 -1
  40. package/dist/cjs/utils.cjs +18 -0
  41. package/dist/cjs/utils.cjs.map +1 -0
  42. package/dist/cjs/utils.d.cts +8 -0
  43. package/dist/esm/constants.d.ts +2 -0
  44. package/dist/esm/constants.js +4 -0
  45. package/dist/esm/constants.js.map +1 -1
  46. package/dist/esm/{extractHtmlScripts.js → dev-server-plugin/extract-html-scripts.js} +1 -1
  47. package/dist/esm/dev-server-plugin/extract-html-scripts.js.map +1 -0
  48. package/dist/esm/{nitro/dev-server-plugin.js → dev-server-plugin/plugin.js} +2 -2
  49. package/dist/esm/dev-server-plugin/plugin.js.map +1 -0
  50. package/dist/esm/index.d.ts +1 -0
  51. package/dist/esm/index.js +3 -1
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/load-env-plugin/plugin.d.ts +3 -0
  54. package/dist/esm/load-env-plugin/plugin.js +17 -0
  55. package/dist/esm/load-env-plugin/plugin.js.map +1 -0
  56. package/dist/esm/nitro-plugin/build-sitemap.d.ts +31 -0
  57. package/dist/esm/nitro-plugin/build-sitemap.js +138 -0
  58. package/dist/esm/nitro-plugin/build-sitemap.js.map +1 -0
  59. package/dist/esm/nitro-plugin/plugin.js +181 -0
  60. package/dist/esm/nitro-plugin/plugin.js.map +1 -0
  61. package/dist/esm/{prerender.d.ts → nitro-plugin/prerender.d.ts} +1 -1
  62. package/dist/esm/{prerender.js → nitro-plugin/prerender.js} +12 -17
  63. package/dist/esm/nitro-plugin/prerender.js.map +1 -0
  64. package/dist/esm/nitro-plugin/queue.js.map +1 -0
  65. package/dist/esm/plugin.d.ts +25 -3064
  66. package/dist/esm/plugin.js +39 -28
  67. package/dist/esm/plugin.js.map +1 -1
  68. package/dist/esm/resolve-virtual-entries-plugin/plugin.d.ts +3 -0
  69. package/dist/esm/resolve-virtual-entries-plugin/plugin.js +49 -0
  70. package/dist/esm/resolve-virtual-entries-plugin/plugin.js.map +1 -0
  71. package/dist/esm/schema.d.ts +530 -1656
  72. package/dist/esm/schema.js +6 -4
  73. package/dist/esm/schema.js.map +1 -1
  74. package/dist/esm/start-compiler-plugin.d.ts +1 -1
  75. package/dist/esm/start-compiler-plugin.js +2 -2
  76. package/dist/esm/start-compiler-plugin.js.map +1 -1
  77. package/dist/esm/start-routes-manifest-plugin/plugin.d.ts +3 -0
  78. package/dist/esm/{routesManifestPlugin.js → start-routes-manifest-plugin/plugin.js} +87 -45
  79. package/dist/esm/start-routes-manifest-plugin/plugin.js.map +1 -0
  80. package/dist/esm/start-server-routes-plugin/plugin.js +4 -2
  81. package/dist/esm/start-server-routes-plugin/plugin.js.map +1 -1
  82. package/dist/esm/utils.d.ts +8 -0
  83. package/dist/esm/utils.js +18 -0
  84. package/dist/esm/utils.js.map +1 -0
  85. package/package.json +6 -6
  86. package/src/constants.ts +3 -0
  87. package/src/{nitro/dev-server-plugin.ts → dev-server-plugin/plugin.ts} +11 -1
  88. package/src/index.ts +1 -0
  89. package/src/load-env-plugin/plugin.ts +17 -0
  90. package/src/nitro-plugin/build-sitemap.ts +213 -0
  91. package/src/nitro-plugin/plugin.ts +244 -0
  92. package/src/{prerender.ts → nitro-plugin/prerender.ts} +14 -21
  93. package/src/plugin.ts +57 -29
  94. package/src/resolve-virtual-entries-plugin/plugin.ts +63 -0
  95. package/src/schema.ts +11 -9
  96. package/src/start-compiler-plugin.ts +1 -1
  97. package/src/{routesManifestPlugin.ts → start-routes-manifest-plugin/plugin.ts} +111 -45
  98. package/src/start-server-routes-plugin/plugin.ts +4 -2
  99. package/src/utils.ts +14 -0
  100. package/dist/cjs/extractHtmlScripts.cjs.map +0 -1
  101. package/dist/cjs/nitro/build-nitro.cjs +0 -18
  102. package/dist/cjs/nitro/build-nitro.cjs.map +0 -1
  103. package/dist/cjs/nitro/build-nitro.d.cts +0 -2
  104. package/dist/cjs/nitro/build-sitemap.d.cts +0 -9
  105. package/dist/cjs/nitro/dev-server-plugin.cjs.map +0 -1
  106. package/dist/cjs/nitro/nitro-plugin.cjs.map +0 -1
  107. package/dist/cjs/prerender.cjs.map +0 -1
  108. package/dist/cjs/queue.cjs.map +0 -1
  109. package/dist/cjs/routesManifestPlugin.cjs.map +0 -1
  110. package/dist/cjs/routesManifestPlugin.d.cts +0 -3
  111. package/dist/esm/extractHtmlScripts.js.map +0 -1
  112. package/dist/esm/nitro/build-nitro.d.ts +0 -2
  113. package/dist/esm/nitro/build-nitro.js +0 -18
  114. package/dist/esm/nitro/build-nitro.js.map +0 -1
  115. package/dist/esm/nitro/build-sitemap.d.ts +0 -9
  116. package/dist/esm/nitro/dev-server-plugin.js.map +0 -1
  117. package/dist/esm/nitro/nitro-plugin.js +0 -158
  118. package/dist/esm/nitro/nitro-plugin.js.map +0 -1
  119. package/dist/esm/prerender.js.map +0 -1
  120. package/dist/esm/queue.js.map +0 -1
  121. package/dist/esm/routesManifestPlugin.d.ts +0 -3
  122. package/dist/esm/routesManifestPlugin.js.map +0 -1
  123. package/src/nitro/build-nitro.ts +0 -27
  124. package/src/nitro/build-sitemap.ts +0 -79
  125. package/src/nitro/nitro-plugin.ts +0 -199
  126. /package/dist/cjs/{extractHtmlScripts.d.cts → dev-server-plugin/extract-html-scripts.d.cts} +0 -0
  127. /package/dist/cjs/{nitro/dev-server-plugin.d.cts → dev-server-plugin/plugin.d.cts} +0 -0
  128. /package/dist/cjs/{nitro/nitro-plugin.d.cts → nitro-plugin/plugin.d.cts} +0 -0
  129. /package/dist/cjs/{queue.cjs → nitro-plugin/queue.cjs} +0 -0
  130. /package/dist/cjs/{queue.d.cts → nitro-plugin/queue.d.cts} +0 -0
  131. /package/dist/esm/{extractHtmlScripts.d.ts → dev-server-plugin/extract-html-scripts.d.ts} +0 -0
  132. /package/dist/esm/{nitro/dev-server-plugin.d.ts → dev-server-plugin/plugin.d.ts} +0 -0
  133. /package/dist/esm/{nitro/nitro-plugin.d.ts → nitro-plugin/plugin.d.ts} +0 -0
  134. /package/dist/esm/{queue.d.ts → nitro-plugin/queue.d.ts} +0 -0
  135. /package/dist/esm/{queue.js → nitro-plugin/queue.js} +0 -0
  136. /package/src/{extractHtmlScripts.ts → dev-server-plugin/extract-html-scripts.ts} +0 -0
  137. /package/src/{queue.ts → nitro-plugin/queue.ts} +0 -0
@@ -0,0 +1,18 @@
1
+ function resolveViteId(id) {
2
+ return `\0${id}`;
3
+ }
4
+ function createLogger(prefix) {
5
+ const label = `[${prefix}]`;
6
+ return {
7
+ log: (...args) => console.log(label, ...args),
8
+ debug: (...args) => console.debug(label, ...args),
9
+ info: (...args) => console.info(label, ...args),
10
+ warn: (...args) => console.warn(label, ...args),
11
+ error: (...args) => console.error(label, ...args)
12
+ };
13
+ }
14
+ export {
15
+ createLogger,
16
+ resolveViteId
17
+ };
18
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["export function resolveViteId(id: string) {\n return `\\0${id}`\n}\n\nexport function createLogger(prefix: string) {\n const label = `[${prefix}]`\n return {\n log: (...args: any) => console.log(label, ...args),\n debug: (...args: any) => console.debug(label, ...args),\n info: (...args: any) => console.info(label, ...args),\n warn: (...args: any) => console.warn(label, ...args),\n error: (...args: any) => console.error(label, ...args),\n }\n}\n"],"names":[],"mappings":"AAAO,SAAS,cAAc,IAAY;AACxC,SAAO,KAAK,EAAE;AAChB;AAEO,SAAS,aAAa,QAAgB;AACrC,QAAA,QAAQ,IAAI,MAAM;AACjB,SAAA;AAAA,IACL,KAAK,IAAI,SAAc,QAAQ,IAAI,OAAO,GAAG,IAAI;AAAA,IACjD,OAAO,IAAI,SAAc,QAAQ,MAAM,OAAO,GAAG,IAAI;AAAA,IACrD,MAAM,IAAI,SAAc,QAAQ,KAAK,OAAO,GAAG,IAAI;AAAA,IACnD,MAAM,IAAI,SAAc,QAAQ,KAAK,OAAO,GAAG,IAAI;AAAA,IACnD,OAAO,IAAI,SAAc,QAAQ,MAAM,OAAO,GAAG,IAAI;AAAA,EACvD;AACF;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/start-plugin-core",
3
- "version": "1.121.0-alpha.2",
3
+ "version": "1.121.0-alpha.21",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -58,16 +58,16 @@
58
58
  "babel-dead-code-elimination": "^1.0.9",
59
59
  "cheerio": "^1.0.0",
60
60
  "h3": "1.13.0",
61
- "nitropack": "^2.11.8",
61
+ "nitropack": "^2.11.12",
62
62
  "pathe": "^2.0.3",
63
63
  "ufo": "^1.5.4",
64
64
  "xmlbuilder2": "^3.1.1",
65
65
  "zod": "^3.24.2",
66
- "@tanstack/router-core": "^1.121.0-alpha.1",
67
- "@tanstack/router-generator": "^1.121.0-alpha.1",
68
- "@tanstack/router-plugin": "^1.121.0-alpha.2",
66
+ "@tanstack/router-core": "^1.121.0-alpha.14",
67
+ "@tanstack/router-generator": "^1.121.0-alpha.14",
69
68
  "@tanstack/router-utils": "^1.121.0-alpha.2",
70
- "@tanstack/server-functions-plugin": "^1.121.0-alpha.2"
69
+ "@tanstack/server-functions-plugin": "^1.121.0-alpha.8",
70
+ "@tanstack/router-plugin": "^1.121.0-alpha.14"
71
71
  },
72
72
  "devDependencies": {
73
73
  "vite": "^6.0.0"
package/src/constants.ts CHANGED
@@ -4,3 +4,6 @@ export const VITE_ENVIRONMENT_NAMES = {
4
4
  server: 'ssr',
5
5
  client: 'client',
6
6
  } as const
7
+
8
+ export const CLIENT_DIST_DIR = '.tanstack-start/build/client-dist'
9
+ export const SSR_ENTRY_FILE = 'ssr.mjs'
@@ -1,7 +1,7 @@
1
1
  import { createEvent, getHeader, sendWebResponse } from 'h3'
2
2
  import { isRunnableDevEnvironment } from 'vite'
3
- import { extractHtmlScripts } from '../extractHtmlScripts'
4
3
  import { VITE_ENVIRONMENT_NAMES } from '../constants'
4
+ import { extractHtmlScripts } from './extract-html-scripts'
5
5
  import type { Connect, DevEnvironment, Plugin, ViteDevServer } from 'vite'
6
6
 
7
7
  declare global {
@@ -29,8 +29,12 @@ export function devServerPlugin(): Plugin {
29
29
  return () => {
30
30
  remove_html_middlewares(viteDevServer.middlewares)
31
31
  let cachedScripts: string | undefined
32
+
32
33
  viteDevServer.middlewares.use(async (req, res) => {
34
+ // Create an H3Event to have it passed into the server entry
35
+ // i.e: event => defineEventHandler(event)
33
36
  const event = createEvent(req, res)
37
+
34
38
  const serverEnv = viteDevServer.environments[
35
39
  VITE_ENVIRONMENT_NAMES.server
36
40
  ] as DevEnvironment | undefined
@@ -41,11 +45,14 @@ export function devServerPlugin(): Plugin {
41
45
  `Server environment ${VITE_ENVIRONMENT_NAMES.server} not found`,
42
46
  )
43
47
  }
48
+
44
49
  if (!isRunnableDevEnvironment(serverEnv)) {
45
50
  throw new Error(
46
51
  `Expected server environment ${VITE_ENVIRONMENT_NAMES.server} to be a RunnableDevEnvironment. This can be caused by multiple vite versions being installed in the project.`,
47
52
  )
48
53
  }
54
+
55
+ // Extract the scripts that Vite plugins would inject into the initial HTML
49
56
  if (cachedScripts === undefined) {
50
57
  const templateHtml = `<html><head></head><body></body></html>`
51
58
  const transformedHtml = await viteDevServer.transformIndexHtml(
@@ -57,6 +64,9 @@ export function devServerPlugin(): Plugin {
57
64
  .map((script) => script.content ?? '')
58
65
  .join(';')
59
66
  }
67
+
68
+ // Import and resolve the request by running the server entry point
69
+ // i.e export default defineEventHandler((event) => { ... })
60
70
  const serverEntry = await serverEnv.runner.import(
61
71
  '/~start/server-entry',
62
72
  )
package/src/index.ts CHANGED
@@ -5,3 +5,4 @@ export {
5
5
  } from './schema'
6
6
 
7
7
  export { TanStackStartVitePluginCore } from './plugin'
8
+ export { resolveViteId } from './utils'
@@ -0,0 +1,17 @@
1
+ import * as vite from 'vite'
2
+ import type { TanStackStartOutputConfig } from '../plugin'
3
+
4
+ export function loadEnvPlugin(
5
+ startOpts: TanStackStartOutputConfig,
6
+ ): vite.Plugin {
7
+ return {
8
+ name: 'tanstack-vite-plugin-nitro-load-env',
9
+ enforce: 'pre',
10
+ config(userConfig, envConfig) {
11
+ Object.assign(
12
+ process.env,
13
+ vite.loadEnv(envConfig.mode, userConfig.root ?? startOpts.root, ''),
14
+ )
15
+ },
16
+ }
17
+ }
@@ -0,0 +1,213 @@
1
+ import { writeFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { create } from 'xmlbuilder2'
4
+ import { createLogger } from '../utils'
5
+ import type { TanStackStartOutputConfig } from '../plugin'
6
+ import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces'
7
+
8
+ export type SitemapUrl = {
9
+ loc: string
10
+ lastmod: string
11
+ priority?: number
12
+ changefreq?:
13
+ | 'always'
14
+ | 'hourly'
15
+ | 'daily'
16
+ | 'weekly'
17
+ | 'monthly'
18
+ | 'yearly'
19
+ | 'never'
20
+ alternateRefs?: Array<{
21
+ href: string
22
+ hreflang?: string
23
+ }>
24
+ images?: Array<{
25
+ loc: string
26
+ title?: string
27
+ caption?: string
28
+ }>
29
+ news?: {
30
+ publication: {
31
+ name: string
32
+ language: string
33
+ }
34
+ publicationDate: string | Date
35
+ title: string
36
+ }
37
+ }
38
+
39
+ export type SitemapData = {
40
+ urls: Array<SitemapUrl>
41
+ }
42
+
43
+ function buildSitemapJson(
44
+ pages: TanStackStartOutputConfig['pages'],
45
+ host: string,
46
+ ): SitemapData {
47
+ const slash = checkSlash(host)
48
+
49
+ const urls: Array<SitemapUrl> = pages
50
+ .filter((page) => {
51
+ return page.sitemap?.exclude !== true
52
+ })
53
+ .map((page) => ({
54
+ loc: `${host}${slash}${page.path.replace(/^\/+/g, '')}`,
55
+ lastmod: page.sitemap?.lastmod
56
+ ? new Date(page.sitemap.lastmod).toISOString().split('T')[0]!
57
+ : new Date().toISOString().split('T')[0]!,
58
+ priority: page.sitemap?.priority,
59
+ changefreq: page.sitemap?.changefreq,
60
+ alternateRefs: page.sitemap?.alternateRefs,
61
+ images: page.sitemap?.images,
62
+ news: page.sitemap?.news,
63
+ }))
64
+
65
+ return { urls }
66
+ }
67
+
68
+ function jsonToXml(sitemapData: SitemapData): string {
69
+ const sitemap = createXml('urlset')
70
+
71
+ for (const item of sitemapData.urls) {
72
+ const page = sitemap.ele('url')
73
+ page.ele('loc').txt(item.loc)
74
+ page.ele('lastmod').txt(item.lastmod)
75
+
76
+ if (item.priority !== undefined) {
77
+ page.ele('priority').txt(item.priority.toString())
78
+ }
79
+ if (item.changefreq) {
80
+ page.ele('changefreq').txt(item.changefreq)
81
+ }
82
+
83
+ // Add alternate references
84
+ if (item.alternateRefs?.length) {
85
+ for (const ref of item.alternateRefs) {
86
+ const alternateRef = page.ele('xhtml:link')
87
+ alternateRef.att('rel', 'alternate')
88
+ alternateRef.att('href', ref.href)
89
+ if (ref.hreflang) {
90
+ alternateRef.att('hreflang', ref.hreflang)
91
+ }
92
+ }
93
+ }
94
+
95
+ // Add images
96
+ if (item.images?.length) {
97
+ for (const image of item.images) {
98
+ const imageElement = page.ele('image:image')
99
+ imageElement.ele('image:loc').txt(image.loc)
100
+ if (image.title) {
101
+ imageElement.ele('image:title').txt(image.title)
102
+ }
103
+ if (image.caption) {
104
+ imageElement.ele('image:caption').txt(image.caption)
105
+ }
106
+ }
107
+ }
108
+
109
+ // Add news
110
+ if (item.news) {
111
+ const newsElement = page.ele('news:news')
112
+ const publication = newsElement.ele('news:publication')
113
+ publication.ele('news:name').txt(item.news.publication.name)
114
+ publication.ele('news:language').txt(item.news.publication.language)
115
+ newsElement
116
+ .ele('news:publication_date')
117
+ .txt(new Date(item.news.publicationDate).toISOString().split('T')[0]!)
118
+ newsElement.ele('news:title').txt(item.news.title)
119
+ }
120
+ }
121
+
122
+ return sitemap.end({ prettyPrint: true })
123
+ }
124
+
125
+ export function buildSitemap({
126
+ options,
127
+ publicDir,
128
+ }: {
129
+ options: TanStackStartOutputConfig
130
+ publicDir: string
131
+ }) {
132
+ const logger = createLogger('sitemap')
133
+
134
+ let sitemapOptions = options.sitemap
135
+
136
+ if (!sitemapOptions && options.pages.length) {
137
+ sitemapOptions = { enabled: true, outputPath: 'sitemap.xml' }
138
+ }
139
+
140
+ if (!sitemapOptions?.enabled) {
141
+ throw new Error('Sitemap is not enabled')
142
+ }
143
+
144
+ const { host, outputPath } = sitemapOptions
145
+
146
+ if (!host) {
147
+ if (!options.sitemap) {
148
+ logger.info(
149
+ 'Hint: Pages found, but no sitemap host has been set. To enable sitemap generation, set the `sitemap.host` option.',
150
+ )
151
+ return
152
+ }
153
+ throw new Error(
154
+ 'Sitemap host is not set and required to build the sitemap.',
155
+ )
156
+ }
157
+
158
+ if (!outputPath) {
159
+ throw new Error('Sitemap output path is not set')
160
+ }
161
+
162
+ const { pages } = options
163
+
164
+ if (!pages.length) {
165
+ logger.info('No pages were found to build the sitemap. Skipping...')
166
+ return
167
+ }
168
+
169
+ logger.info('Building Sitemap...')
170
+
171
+ // Build the sitemap data
172
+ const sitemapData = buildSitemapJson(pages, host)
173
+
174
+ // Generate output paths
175
+ const xmlOutputPath = path.join(publicDir, outputPath)
176
+ const pagesOutputPath = path.join(publicDir, 'pages.json')
177
+
178
+ try {
179
+ // Write XML sitemap
180
+ logger.info(`Writing sitemap XML at ${xmlOutputPath}`)
181
+ writeFileSync(xmlOutputPath, jsonToXml(sitemapData))
182
+
183
+ // Write pages data for runtime use
184
+ logger.info(`Writing pages data at ${pagesOutputPath}`)
185
+ writeFileSync(
186
+ pagesOutputPath,
187
+ JSON.stringify(
188
+ {
189
+ pages,
190
+ host,
191
+ lastBuilt: new Date().toISOString(),
192
+ },
193
+ null,
194
+ 2,
195
+ ),
196
+ )
197
+ } catch (e) {
198
+ logger.error(`Unable to write sitemap files`, e)
199
+ }
200
+ }
201
+
202
+ function createXml(elementName: 'urlset' | 'sitemapindex'): XMLBuilder {
203
+ return create({ version: '1.0', encoding: 'UTF-8' })
204
+ .ele(elementName, {
205
+ xmlns: 'https://www.sitemaps.org/schemas/sitemap/0.9',
206
+ })
207
+ .com(`This file was automatically generated by TanStack Start.`)
208
+ }
209
+
210
+ function checkSlash(host: string): string {
211
+ const finalChar = host.slice(-1)
212
+ return finalChar === '/' ? '' : '/'
213
+ }
@@ -0,0 +1,244 @@
1
+ import path from 'node:path'
2
+ import { rmSync } from 'node:fs'
3
+ import { build, copyPublicAssets, createNitro, prepare } from 'nitropack'
4
+ import { dirname, resolve } from 'pathe'
5
+ import {
6
+ CLIENT_DIST_DIR,
7
+ SSR_ENTRY_FILE,
8
+ VITE_ENVIRONMENT_NAMES,
9
+ } from '../constants'
10
+ import { buildSitemap } from './build-sitemap'
11
+ import { prerender } from './prerender'
12
+ import type {
13
+ EnvironmentOptions,
14
+ PluginOption,
15
+ Rollup,
16
+ ViteBuilder,
17
+ } from 'vite'
18
+ import type { Nitro, NitroConfig } from 'nitropack'
19
+ import type { TanStackStartOutputConfig } from '../plugin'
20
+
21
+ export function nitroPlugin(
22
+ options: TanStackStartOutputConfig,
23
+ getSsrBundle: () => Rollup.OutputBundle,
24
+ ): Array<PluginOption> {
25
+ const buildPreset =
26
+ process.env['START_TARGET'] ?? (options.target as string | undefined)
27
+ return [
28
+ {
29
+ name: 'tanstack-vite-plugin-nitro',
30
+ configEnvironment(name) {
31
+ if (name === VITE_ENVIRONMENT_NAMES.server) {
32
+ return {
33
+ build: {
34
+ commonjsOptions: {
35
+ include: [],
36
+ },
37
+ ssr: true,
38
+ sourcemap: true,
39
+ rollupOptions: {
40
+ input: '/~start/server-entry',
41
+ },
42
+ },
43
+ } satisfies EnvironmentOptions
44
+ }
45
+
46
+ return null
47
+ },
48
+ config() {
49
+ return {
50
+ builder: {
51
+ sharedPlugins: true,
52
+ async buildApp(builder) {
53
+ const client = builder.environments[VITE_ENVIRONMENT_NAMES.client]
54
+ const server = builder.environments[VITE_ENVIRONMENT_NAMES.server]
55
+
56
+ if (!client) {
57
+ throw new Error('Client environment not found')
58
+ }
59
+
60
+ if (!server) {
61
+ throw new Error('SSR environment not found')
62
+ }
63
+
64
+ // Build the client bundle
65
+ // i.e client entry file with `hydrateRoot(...)`
66
+ const clientOutputDir = resolve(options.root, CLIENT_DIST_DIR)
67
+ rmSync(clientOutputDir, { recursive: true, force: true })
68
+ await builder.build(client)
69
+
70
+ // Build the SSR bundle
71
+ await builder.build(server)
72
+
73
+ const nitroConfig: NitroConfig = {
74
+ dev: false,
75
+ // TODO: do we need this? should this be made configurable?
76
+ compatibilityDate: '2024-11-19',
77
+ logLevel: 3,
78
+ preset: buildPreset,
79
+ baseURL: globalThis.TSS_APP_BASE,
80
+ publicAssets: [
81
+ {
82
+ dir: path.resolve(options.root, CLIENT_DIST_DIR),
83
+ baseURL: '/',
84
+ maxAge: 31536000, // 1 year
85
+ },
86
+ ],
87
+ typescript: {
88
+ generateTsConfig: false,
89
+ },
90
+ prerender: undefined,
91
+ renderer: SSR_ENTRY_FILE,
92
+ plugins: [], // Nitro's plugins
93
+ appConfigFiles: [],
94
+ scanDirs: [],
95
+ imports: false, // unjs/unimport for global/magic imports
96
+ rollupConfig: {
97
+ plugins: [virtualBundlePlugin(getSsrBundle())],
98
+ },
99
+ virtual: {
100
+ // This is Nitro's way of defining virtual modules
101
+ // Should we define the ones for TanStack Start's here as well?
102
+ },
103
+ }
104
+
105
+ const nitro = await createNitro(nitroConfig)
106
+
107
+ await buildNitroApp(builder, nitro, options)
108
+ },
109
+ },
110
+ }
111
+ },
112
+ },
113
+ ]
114
+ }
115
+
116
+ /**
117
+ * Correctly co-ordinates the nitro app build process to make sure that the
118
+ * app is built, while also correctly handling the prerendering and sitemap
119
+ * generation and including their outputs in the final build.
120
+ */
121
+ async function buildNitroApp(
122
+ builder: ViteBuilder,
123
+ nitro: Nitro,
124
+ options: TanStackStartOutputConfig,
125
+ ) {
126
+ // Cleans the public and server directories for a fresh build
127
+ // i.e the `.output/public` and `.output/server` directories
128
+ await prepare(nitro)
129
+
130
+ // Creates the `.output/public` directory and copies the public assets
131
+ await copyPublicAssets(nitro)
132
+
133
+ // If the user has not set a prerender option, we need to set it to true
134
+ // if the pages array is not empty and has sub options requiring for prerendering
135
+ if (options.prerender?.enabled !== false) {
136
+ options.prerender = {
137
+ ...options.prerender,
138
+ enabled: options.pages.some((d) =>
139
+ typeof d === 'string' ? false : !!d.prerender?.enabled,
140
+ ),
141
+ }
142
+ }
143
+
144
+ // Setup the options for prerendering the SPA shell (i.e `src/routes/__root.tsx`)
145
+ if (options.spa?.enabled) {
146
+ options.prerender = {
147
+ ...options.prerender,
148
+ enabled: true,
149
+ }
150
+
151
+ const maskUrl = new URL(options.spa.maskPath, 'http://localhost')
152
+
153
+ maskUrl.searchParams.set('__TSS_SHELL', 'true')
154
+
155
+ options.pages.push({
156
+ path: maskUrl.toString().replace('http://localhost', ''),
157
+ prerender: options.spa.prerender,
158
+ sitemap: {
159
+ exclude: true,
160
+ },
161
+ })
162
+ }
163
+
164
+ // Run the prerendering process
165
+ if (options.prerender.enabled) {
166
+ await prerender({
167
+ options,
168
+ nitro,
169
+ builder,
170
+ })
171
+ }
172
+
173
+ // Run the sitemap build process
174
+ if (options.pages.length) {
175
+ buildSitemap({
176
+ options,
177
+ publicDir: nitro.options.output.publicDir,
178
+ })
179
+ }
180
+
181
+ // Build the nitro app
182
+ // We only build the nitro app, once we've prepared the public assets,
183
+ // prerendered the pages and built the sitemap.
184
+ // If we try to do this earlier, then the public assets may not be available
185
+ // in the production build.
186
+ await build(nitro)
187
+
188
+ // Close the nitro instance
189
+ await nitro.close()
190
+ nitro.logger.success(
191
+ 'Client and Server bundles for TanStack Start have been successfully built.',
192
+ )
193
+ }
194
+
195
+ type NitroRollupPluginOption = NonNullable<
196
+ NitroConfig['rollupConfig']
197
+ >['plugins']
198
+
199
+ function virtualBundlePlugin(
200
+ ssrBundle: Rollup.OutputBundle,
201
+ ): NitroRollupPluginOption {
202
+ type VirtualModule = { code: string; map: string | null }
203
+ const _modules = new Map<string, VirtualModule>()
204
+
205
+ // group chunks and source maps
206
+ for (const [fileName, content] of Object.entries(ssrBundle)) {
207
+ if (content.type === 'chunk') {
208
+ const virtualModule: VirtualModule = {
209
+ code: content.code,
210
+ map: null,
211
+ }
212
+ const maybeMap = ssrBundle[`${fileName}.map`]
213
+ if (maybeMap && maybeMap.type === 'asset') {
214
+ virtualModule.map = maybeMap.source as string
215
+ }
216
+ _modules.set(fileName, virtualModule)
217
+ _modules.set(resolve(fileName), virtualModule)
218
+ }
219
+ }
220
+
221
+ return {
222
+ name: 'virtual-bundle',
223
+ resolveId(id, importer) {
224
+ if (_modules.has(id)) {
225
+ return resolve(id)
226
+ }
227
+
228
+ if (importer) {
229
+ const resolved = resolve(dirname(importer), id)
230
+ if (_modules.has(resolved)) {
231
+ return resolved
232
+ }
233
+ }
234
+ return null
235
+ },
236
+ load(id) {
237
+ const m = _modules.get(id)
238
+ if (!m) {
239
+ return null
240
+ }
241
+ return m
242
+ },
243
+ }
244
+ }
@@ -4,13 +4,13 @@ import path from 'node:path'
4
4
  import { getRollupConfig } from 'nitropack/rollup'
5
5
  import { build as buildNitro, createNitro } from 'nitropack'
6
6
  import { joinURL, withBase, withoutBase } from 'ufo'
7
+ import { VITE_ENVIRONMENT_NAMES } from '../constants'
8
+ import { createLogger } from '../utils'
7
9
  import { Queue } from './queue'
8
- import { buildNitroEnvironment } from './nitro/build-nitro'
9
- import { VITE_ENVIRONMENT_NAMES } from './constants'
10
10
  import type { ViteBuilder } from 'vite'
11
11
  import type { $Fetch, Nitro } from 'nitropack'
12
- import type { TanStackStartOutputConfig } from './plugin'
13
- import type { Page } from './schema'
12
+ import type { TanStackStartOutputConfig } from '../plugin'
13
+ import type { Page } from '../schema'
14
14
 
15
15
  export async function prerender({
16
16
  options,
@@ -21,7 +21,8 @@ export async function prerender({
21
21
  nitro: Nitro
22
22
  builder: ViteBuilder
23
23
  }) {
24
- console.info('Prendering pages...')
24
+ const logger = createLogger('prerender')
25
+ logger.info('Prendering pages...')
25
26
 
26
27
  // If prerender is enabled but no pages are provided, default to prerendering the root page
27
28
  if (options.prerender?.enabled && !options.pages.length) {
@@ -72,7 +73,7 @@ export async function prerender({
72
73
  },
73
74
  }
74
75
 
75
- await buildNitroEnvironment(nodeNitro, () => buildNitro(nodeNitro))
76
+ await buildNitro(nodeNitro)
76
77
 
77
78
  // Import renderer entry
78
79
  const serverFilename =
@@ -93,14 +94,14 @@ export async function prerender({
93
94
  // Crawl all pages
94
95
  const pages = await prerenderPages()
95
96
 
96
- console.info(`Prerendered ${pages.length} pages:`)
97
+ logger.info(`Prerendered ${pages.length} pages:`)
97
98
  pages.forEach((page) => {
98
- console.info(`- ${page}`)
99
+ logger.info(`- ${page}`)
99
100
  })
100
101
 
101
102
  // TODO: Write the prerendered pages to the output directory
102
103
  } catch (error) {
103
- console.error(error)
104
+ logger.error(error)
104
105
  } finally {
105
106
  // Ensure server is always closed
106
107
  // server.process.kill()
@@ -126,18 +127,10 @@ export async function prerender({
126
127
  const seen = new Set<string>()
127
128
  const retriesByPath = new Map<string, number>()
128
129
  const concurrency = options.prerender?.concurrency ?? os.cpus().length
129
- console.info(`Concurrency: ${concurrency}`)
130
+ logger.info(`Concurrency: ${concurrency}`)
130
131
  const queue = new Queue({ concurrency })
131
132
 
132
- options.pages.forEach((_page) => {
133
- let page = _page as Page
134
-
135
- if (typeof _page === 'string') {
136
- page = { path: _page }
137
- }
138
-
139
- addCrawlPageTask(page)
140
- })
133
+ options.pages.forEach((page) => addCrawlPageTask(page))
141
134
 
142
135
  await queue.start()
143
136
 
@@ -168,7 +161,7 @@ export async function prerender({
168
161
 
169
162
  // Add the task
170
163
  queue.add(async () => {
171
- console.info(`Crawling: ${page.path}`)
164
+ logger.info(`Crawling: ${page.path}`)
172
165
  const retries = retriesByPath.get(page.path) || 0
173
166
  try {
174
167
  // Fetch the route
@@ -236,7 +229,7 @@ export async function prerender({
236
229
  }
237
230
  } catch (error) {
238
231
  if (retries < (prerenderOptions.retryCount ?? 0)) {
239
- console.warn(`Encountered error, retrying: ${page.path} in 500ms`)
232
+ logger.warn(`Encountered error, retrying: ${page.path} in 500ms`)
240
233
  await new Promise((resolve) =>
241
234
  setTimeout(resolve, prerenderOptions.retryDelay),
242
235
  )