Skip to content

Commit 496fac3

Browse files
authored
fix: make sitemap accessible via public assets (#4235)
1 parent fad6b0b commit 496fac3

File tree

5 files changed

+128
-100
lines changed

5 files changed

+128
-100
lines changed

packages/start-plugin-core/src/build-sitemap.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { writeFileSync } from 'node:fs'
22
import path from 'node:path'
33
import { create } from 'xmlbuilder2'
4+
import { createLogger } from './utils'
45
import type { TanStackStartOutputConfig } from './plugin'
56
import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces'
67

@@ -121,13 +122,15 @@ function jsonToXml(sitemapData: SitemapData): string {
121122
return sitemap.end({ prettyPrint: true })
122123
}
123124

124-
export async function buildSitemap({
125+
export function buildSitemap({
125126
options,
126127
publicDir,
127128
}: {
128129
options: TanStackStartOutputConfig
129130
publicDir: string
130131
}) {
132+
const logger = createLogger('sitemap')
133+
131134
let sitemapOptions = options.sitemap
132135

133136
if (!sitemapOptions && options.pages.length) {
@@ -142,7 +145,7 @@ export async function buildSitemap({
142145

143146
if (!host) {
144147
if (!options.sitemap) {
145-
console.info(
148+
logger.info(
146149
'Hint: Pages found, but no sitemap host has been set. To enable sitemap generation, set the `sitemap.host` option.',
147150
)
148151
return
@@ -159,11 +162,11 @@ export async function buildSitemap({
159162
const { pages } = options
160163

161164
if (!pages.length) {
162-
console.log('No pages were found to build the sitemap. Skipping...')
165+
logger.info('No pages were found to build the sitemap. Skipping...')
163166
return
164167
}
165168

166-
console.log('Building Sitemap...')
169+
logger.info('Building Sitemap...')
167170

168171
// Build the sitemap data
169172
const sitemapData = buildSitemapJson(pages, host)
@@ -174,11 +177,11 @@ export async function buildSitemap({
174177

175178
try {
176179
// Write XML sitemap
177-
console.log(`Writing sitemap XML at ${xmlOutputPath}`)
180+
logger.info(`Writing sitemap XML at ${xmlOutputPath}`)
178181
writeFileSync(xmlOutputPath, jsonToXml(sitemapData))
179182

180183
// Write pages data for runtime use
181-
console.log(`Writing pages data at ${pagesOutputPath}`)
184+
logger.info(`Writing pages data at ${pagesOutputPath}`)
182185
writeFileSync(
183186
pagesOutputPath,
184187
JSON.stringify(
@@ -192,7 +195,7 @@ export async function buildSitemap({
192195
),
193196
)
194197
} catch (e) {
195-
console.error(`Unable to write sitemap files`, e)
198+
logger.error(`Unable to write sitemap files`, e)
196199
}
197200
}
198201

packages/start-plugin-core/src/nitro/build-nitro.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

packages/start-plugin-core/src/nitro/nitro-plugin.ts

Lines changed: 97 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import path from 'node:path'
22
import { rmSync } from 'node:fs'
3-
import { build, createNitro } from 'nitropack'
3+
import * as fsp from 'node:fs/promises'
4+
import { build, copyPublicAssets, createNitro, prepare } from 'nitropack'
45
import { dirname, resolve } from 'pathe'
56
import { clientDistDir, ssrEntryFile } from '../plugin'
67
import { prerender } from '../prerender'
78
import { VITE_ENVIRONMENT_NAMES } from '../constants'
89
import { buildSitemap } from '../build-sitemap'
910
import { devServerPlugin } from './dev-server-plugin'
10-
import { buildNitroEnvironment } from './build-nitro'
11-
import type { EnvironmentOptions, PluginOption, Rollup } from 'vite'
12-
import type { NitroConfig } from 'nitropack'
11+
import type {
12+
EnvironmentOptions,
13+
PluginOption,
14+
Rollup,
15+
ViteBuilder,
16+
} from 'vite'
17+
import type { Nitro, NitroConfig } from 'nitropack'
1318
import type { TanStackStartOutputConfig } from '../plugin'
1419

1520
export function nitroPlugin(
@@ -88,59 +93,7 @@ export function nitroPlugin(
8893

8994
const nitro = await createNitro(nitroConfig)
9095

91-
await buildNitroEnvironment(nitro, () => build(nitro))
92-
93-
// If the user has not set a prerender option, we need to set it to true
94-
// if the pages array is not empty and has sub options requiring for prerendering
95-
if (options.prerender?.enabled !== false) {
96-
options.prerender = {
97-
...options.prerender,
98-
enabled: options.pages.some((d) =>
99-
typeof d === 'string' ? false : !!d.prerender?.enabled,
100-
),
101-
}
102-
}
103-
104-
// Setup the options for prerendering the SPA shell (i.e `src/routes/__root.tsx`)
105-
if (options.spa?.enabled) {
106-
options.prerender = {
107-
...options.prerender,
108-
enabled: true,
109-
}
110-
111-
const maskUrl = new URL(
112-
options.spa.maskPath,
113-
'http://localhost',
114-
)
115-
116-
maskUrl.searchParams.set('__TSS_SHELL', 'true')
117-
118-
options.pages.push({
119-
path: maskUrl.toString().replace('http://localhost', ''),
120-
prerender: options.spa.prerender,
121-
sitemap: {
122-
exclude: true,
123-
},
124-
})
125-
}
126-
127-
// Start prerendering!!!
128-
if (options.prerender.enabled) {
129-
await prerender({
130-
options,
131-
nitro,
132-
builder,
133-
})
134-
}
135-
136-
if (options.pages.length) {
137-
await buildSitemap({
138-
options,
139-
publicDir: nitro.options.output.publicDir,
140-
})
141-
}
142-
143-
console.log(`\n✅ Client and server bundles successfully built.`)
96+
await buildNitroApp(builder, nitro, options)
14497
},
14598
},
14699
}
@@ -149,6 +102,93 @@ export function nitroPlugin(
149102
]
150103
}
151104

105+
/**
106+
* Correctly co-ordinates the nitro app build process to make sure that the
107+
* app is built, while also correctly handling the prerendering and sitemap
108+
* generation and including their outputs in the final build.
109+
*/
110+
async function buildNitroApp(
111+
builder: ViteBuilder,
112+
nitro: Nitro,
113+
options: TanStackStartOutputConfig,
114+
) {
115+
// Cleans the public and server directories for a fresh build
116+
// i.e the `.output/public` and `.output/server` directories
117+
await prepare(nitro)
118+
119+
// Creates the `.output/public` directory and copies the public assets
120+
await copyPublicAssets(nitro)
121+
122+
// If the user has not set a prerender option, we need to set it to true
123+
// if the pages array is not empty and has sub options requiring for prerendering
124+
if (options.prerender?.enabled !== false) {
125+
options.prerender = {
126+
...options.prerender,
127+
enabled: options.pages.some((d) =>
128+
typeof d === 'string' ? false : !!d.prerender?.enabled,
129+
),
130+
}
131+
}
132+
133+
// Setup the options for prerendering the SPA shell (i.e `src/routes/__root.tsx`)
134+
if (options.spa?.enabled) {
135+
options.prerender = {
136+
...options.prerender,
137+
enabled: true,
138+
}
139+
140+
const maskUrl = new URL(options.spa.maskPath, 'http://localhost')
141+
142+
maskUrl.searchParams.set('__TSS_SHELL', 'true')
143+
144+
options.pages.push({
145+
path: maskUrl.toString().replace('http://localhost', ''),
146+
prerender: options.spa.prerender,
147+
sitemap: {
148+
exclude: true,
149+
},
150+
})
151+
}
152+
153+
// Run the prerendering process
154+
if (options.prerender.enabled) {
155+
await prerender({
156+
options,
157+
nitro,
158+
builder,
159+
})
160+
}
161+
162+
// Run the sitemap build process
163+
if (options.pages.length) {
164+
buildSitemap({
165+
options,
166+
publicDir: nitro.options.output.publicDir,
167+
})
168+
}
169+
170+
// Build the nitro app
171+
await build(nitro)
172+
173+
// Cleanup the vite public directory
174+
// As a part of the build process, a `.vite/` directory
175+
// is copied over from `.tanstack-start/build/client-dist/`
176+
// to the nitro `publicDir` (e.g. `.output/public/`).
177+
// This directory (and its contents including the vite client manifest)
178+
// should not be included in the final build, so we remove it.
179+
const nitroPublicDir = nitro.options.output.publicDir
180+
const viteDir = path.resolve(nitroPublicDir, '.vite')
181+
if (await fsp.stat(viteDir).catch(() => false)) {
182+
await fsp.rm(viteDir, { recursive: true, force: true })
183+
}
184+
185+
// Close the nitro instance
186+
await nitro.close()
187+
nitro.logger.success(
188+
'Client and Server bundles for TanStack Start have been successfully built.',
189+
)
190+
}
191+
152192
function virtualBundlePlugin(ssrBundle: Rollup.OutputBundle): Rollup.Plugin {
153193
type VirtualModule = { code: string; map: string | null }
154194
const _modules = new Map<string, VirtualModule>()

packages/start-plugin-core/src/prerender.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { getRollupConfig } from 'nitropack/rollup'
55
import { build as buildNitro, createNitro } from 'nitropack'
66
import { joinURL, withBase, withoutBase } from 'ufo'
77
import { Queue } from './queue'
8-
import { buildNitroEnvironment } from './nitro/build-nitro'
98
import { VITE_ENVIRONMENT_NAMES } from './constants'
9+
import { createLogger } from './utils'
1010
import type { ViteBuilder } from 'vite'
1111
import type { $Fetch, Nitro } from 'nitropack'
1212
import type { TanStackStartOutputConfig } from './plugin'
@@ -21,7 +21,8 @@ export async function prerender({
2121
nitro: Nitro
2222
builder: ViteBuilder
2323
}) {
24-
console.info('Prendering pages...')
24+
const logger = createLogger('prerender')
25+
logger.info('Prendering pages...')
2526

2627
// If prerender is enabled but no pages are provided, default to prerendering the root page
2728
if (options.prerender?.enabled && !options.pages.length) {
@@ -72,7 +73,7 @@ export async function prerender({
7273
},
7374
}
7475

75-
await buildNitroEnvironment(nodeNitro, () => buildNitro(nodeNitro))
76+
await buildNitro(nodeNitro)
7677

7778
// Import renderer entry
7879
const serverFilename =
@@ -93,14 +94,14 @@ export async function prerender({
9394
// Crawl all pages
9495
const pages = await prerenderPages()
9596

96-
console.info(`Prerendered ${pages.length} pages:`)
97+
logger.info(`Prerendered ${pages.length} pages:`)
9798
pages.forEach((page) => {
98-
console.info(`- ${page}`)
99+
logger.info(`- ${page}`)
99100
})
100101

101102
// TODO: Write the prerendered pages to the output directory
102103
} catch (error) {
103-
console.error(error)
104+
logger.error(error)
104105
} finally {
105106
// Ensure server is always closed
106107
// server.process.kill()
@@ -126,7 +127,7 @@ export async function prerender({
126127
const seen = new Set<string>()
127128
const retriesByPath = new Map<string, number>()
128129
const concurrency = options.prerender?.concurrency ?? os.cpus().length
129-
console.info(`Concurrency: ${concurrency}`)
130+
logger.info(`Concurrency: ${concurrency}`)
130131
const queue = new Queue({ concurrency })
131132

132133
options.pages.forEach((page) => addCrawlPageTask(page))
@@ -160,7 +161,7 @@ export async function prerender({
160161

161162
// Add the task
162163
queue.add(async () => {
163-
console.info(`Crawling: ${page.path}`)
164+
logger.info(`Crawling: ${page.path}`)
164165
const retries = retriesByPath.get(page.path) || 0
165166
try {
166167
// Fetch the route
@@ -228,7 +229,7 @@ export async function prerender({
228229
}
229230
} catch (error) {
230231
if (retries < (prerenderOptions.retryCount ?? 0)) {
231-
console.warn(`Encountered error, retrying: ${page.path} in 500ms`)
232+
logger.warn(`Encountered error, retrying: ${page.path} in 500ms`)
232233
await new Promise((resolve) =>
233234
setTimeout(resolve, prerenderOptions.retryDelay),
234235
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
11
export function resolveViteId(id: string) {
22
return `\0${id}`
33
}
4+
5+
export function createLogger(prefix: string) {
6+
const label = `[${prefix}]`
7+
return {
8+
log: (...args: any) => console.log(label, ...args),
9+
debug: (...args: any) => console.debug(label, ...args),
10+
info: (...args: any) => console.info(label, ...args),
11+
warn: (...args: any) => console.warn(label, ...args),
12+
error: (...args: any) => console.error(label, ...args),
13+
}
14+
}

0 commit comments

Comments
 (0)