This commit is contained in:
2025-05-24 01:47:40 +09:00
commit 09d97cbb0b
1594 changed files with 184634 additions and 0 deletions

129
server/generate.ts Normal file
View File

@@ -0,0 +1,129 @@
// Pre-render the app into static HTML.
// run `pnpm ssg:build` and then `dist` can be served as a static site.
import fs from 'node:fs'
import fsp from 'node:fs/promises'
import path from 'node:path'
import { type ResolvedConfig, type InlineConfig, resolveConfig } from 'vite'
import colors from 'picocolors'
import { env } from 'std-env'
import { createRenderer } from './generate/renderer'
import { buildApp } from './generate/builder'
import { scanRoutes } from './generate/scan'
import { populateRouteParams } from './generate/populate'
import { renderToFile } from './generate/render-to-file'
async function build() {
const mode = env.MODE || env.NODE_ENV || 'production'
const viteConfig: InlineConfig = {}
const config = await resolveConfig(viteConfig, 'build', mode)
const cwd = process.cwd()
const root = config.root || cwd
const outDir = config.build.outDir || 'dist'
const out = path.isAbsolute(outDir) ? outDir : path.join(root, outDir)
const outStatic = out
const outServer = path.join(out, '.server')
if (fs.existsSync(out)) {
await fsp.rm(out, { recursive: true })
}
// scan base routes from src/pages
const routes = await scanRoutes(cwd)
// build client and server vite apps
await buildApp({
config,
viteConfig,
outStatic,
outServer,
})
// load renderer from server build
const {
manifest,
template,
render,
} = await createRenderer({
outServer,
outStatic,
})
// generate urls for pre-rendering depending on static config
const pages = await populateRouteParams({
config,
routes,
})
// pre-render each page sequentially
for (const page of pages) {
const start = performance.now()
const file = await renderToFile(render, {
url: page.url,
outStatic,
manifest,
template,
})
const duration = performance.now() - start
const formattedDuration = duration.toFixed(2).padStart(5) + 'ms'
config.logger.info(
colors.dim(`[${page.logPrefix}] ${colors.green(page.url)} - ${colors.cyan(file)} - ${formattedDuration}`),
)
}
// delete server build
await fsp.rm(path.join(outServer), { recursive: true, force: true })
// regenerate PWA service worker with updated files
await generatePWA({
config,
outStatic,
})
config.logger.info(
[
`Pre-rendering done. You can now serve the ${colors.cyan(
out.replace(cwd, '.'),
)} directory with a static file server.`,
`Example:`,
` ${colors.green('npx serve dist -p 3000')}`,
].join('\n'),
)
process.exit(0)
}
(async () => {
try {
await build()
}
catch (e) {
console.error(e)
process.exit(1)
}
})()
async function generatePWA({
config,
outStatic,
}: {
config: ResolvedConfig
outStatic: string
}) {
const pwaPlugin = config.plugins.find(plugin => plugin.name === 'vite-plugin-pwa')
?.api
if (pwaPlugin && !pwaPlugin.disabled && pwaPlugin.generateSW) {
config.logger.info(colors.green('[SSG] Regenerate PWA...'))
await pwaPlugin.generateSW()
// update sw.js to replace /index.html with nothing so that it can be served from /
const swPath = path.join(outStatic, 'sw.js')
const swContent = await fsp.readFile(swPath, 'utf-8')
await fsp.writeFile(swPath, swContent.replace(/\/index\.html/g, ''), 'utf-8')
}
}