@@ -3,10 +3,16 @@ import {
33 copyPublicAssets ,
44 createNitro ,
55 prepare ,
6+ type $Fetch ,
7+ type Nitro ,
68 type NitroConfig ,
79} from 'nitropack'
810import type { PresetName } from 'nitropack/presets'
11+ import path from 'node:path'
12+ import { pathToFileURL } from 'node:url'
913import type { Plugin , ViteBuilder } from 'vite'
14+ import { joinURL , withBase , withoutBase } from 'ufo'
15+ import fsp from 'node:fs/promises'
1016
1117// Using Nitro as post-build to target deployment platform. Inspired by Tanstack Start's approach.
1218// https://github.com/TanStack/router/blob/5fd079e482b1252b8b11a936f1524a0dee368cae/packages/start-plugin-core/src/nitro-plugin/plugin.ts
@@ -18,6 +24,7 @@ type NitroPluginOptions = {
1824 preset : PresetName
1925 clientDir : string
2026 serverEntry : string
27+ prerender ?: string [ ]
2128}
2229
2330export function nitroPlugin ( nitroPluginOptions : NitroPluginOptions ) : Plugin [ ] {
@@ -121,10 +128,93 @@ export default defineEventHandler((event) => handler(toWebRequest(event)))
121128 const nitro = await createNitro ( nitroConfig )
122129 await prepare ( nitro )
123130 await copyPublicAssets ( nitro )
124- await prerender ( )
131+ if ( nitroPluginOptions . prerender ?. length ) {
132+ await prerender ( nitro , nitroPluginOptions . prerender )
133+ }
125134 await build ( nitro )
126135 await nitro . close ( )
136+ }
137+
138+ // TODO
139+ // https://github.com/TanStack/router/blob/5fd079e482b1252b8b11a936f1524a0dee368cae/packages/start-plugin-core/src/nitro-plugin/prerender.ts#L53
140+ // https://github.com/nitrojs/nitro/blob/c468de271cff8d56361c3b09ea1071ed545a550f/src/prerender/prerender.ts#L62-L74
141+ async function prerender ( nitro : Nitro , pages : string [ ] ) {
142+ const nodeNitro = await createNitro ( {
143+ ...nitro . options . _config ,
144+ preset : 'nitro-prerender' ,
145+ // logLevel: 0,
146+ output : {
147+ dir : 'dist/nitro/prerender' ,
148+ // serverDir: path.resolve(prerenderOutputDir, 'server'),
149+ // publicDir: path.resolve(prerenderOutputDir, 'public'),
150+ } ,
151+ } )
152+ await build ( nodeNitro )
153+
154+ const serverEntrypoint = pathToFileURL (
155+ path . resolve ( nodeNitro . options . output . serverDir , 'index.mjs' ) ,
156+ ) . toString ( )
157+
158+ const { closePrerenderer, localFetch } = ( await import ( serverEntrypoint ) ) as {
159+ closePrerenderer : ( ) => void
160+ localFetch : $Fetch
161+ }
162+
163+ for ( const page of pages ) {
164+ await prerenderPage ( { path : page } )
165+ }
166+ closePrerenderer ( )
167+ await nodeNitro . close ( )
168+
169+ async function prerenderPage ( page : { path : string } ) {
170+ const encodedRoute = encodeURI ( page . path )
171+
172+ const res = await localFetch < Response > (
173+ withBase ( encodedRoute , nodeNitro . options . baseURL ) ,
174+ {
175+ headers : { 'x-nitro-prerender' : encodedRoute } ,
176+ } ,
177+ )
178+
179+ if ( ! res . ok ) {
180+ throw new Error ( `Failed to fetch ${ page . path } : ${ res . statusText } ` , {
181+ cause : res ,
182+ } )
183+ }
184+
185+ // const cleanPagePath = (prerenderOptions.outputPath || page.path).split(
186+ // /[?#]/,
187+ // )[0]!
188+ const cleanPagePath = page . path
189+
190+ // Guess route type and populate fileName
191+ const contentType = res . headers . get ( 'content-type' ) || ''
192+ const isImplicitHTML =
193+ ! cleanPagePath . endsWith ( '.html' ) && contentType . includes ( 'html' )
194+ // &&
195+ // !JsonSigRx.test(dataBuff.subarray(0, 32).toString('utf8'))
196+ const routeWithIndex = cleanPagePath . endsWith ( '/' )
197+ ? cleanPagePath + 'index'
198+ : cleanPagePath
199+
200+ const htmlPath = cleanPagePath . endsWith ( '/' )
201+ ? // || prerenderOptions.autoSubfolderIndex
202+ joinURL ( cleanPagePath , 'index.html' )
203+ : cleanPagePath + '.html'
127204
128- // TODO
129- async function prerender ( ) { }
205+ const filename = withoutBase (
206+ isImplicitHTML ? htmlPath : routeWithIndex ,
207+ nitro . options . baseURL ,
208+ )
209+
210+ const html = await res . text ( )
211+
212+ const filepath = path . join ( nitro . options . output . publicDir , filename )
213+
214+ await fsp . mkdir ( path . dirname ( filepath ) , {
215+ recursive : true ,
216+ } )
217+
218+ await fsp . writeFile ( filepath , html )
219+ }
130220}
0 commit comments