brf/server/plugins/vite/development.ts
2025-12-13 21:12:08 +01:00

136 lines
3.8 KiB
TypeScript

import type { FastifyInstance, RouteHandler } from 'fastify'
import fmiddie from '@fastify/middie'
import { createServer } from 'vite'
import StatusError from '../../lib/status_error.ts'
import type { Route } from '../../../shared/types.ts'
import type { Entry, ParsedConfig, Renderer, RenderFunction } from './types.ts'
export default async function viteDevelopment(fastify: FastifyInstance, config: ParsedConfig) {
const devServer = await createServer({
server: { middlewareMode: true },
appType: 'custom',
})
fastify.decorate('devServer', devServer)
fastify.decorateReply('ctx', null)
await fastify.register(fmiddie)
fastify.use(devServer.middlewares)
for (const entry of Object.values(config.entries)) {
fastify.register((fastify, _, done) => setupEntry(fastify, entry).then(() => done()), {
prefix: entry.path,
})
}
}
async function setupEntry(fastify: FastifyInstance, entry: Entry) {
const renderer = createRenderer(fastify, entry)
const handler = createHandler(renderer)
if (entry.preHandler || entry.createPreHandler) {
fastify.addHook('onRequest', (_request, reply, done) => {
reply.ctx = Object.create(null)
done()
})
}
if (entry.preHandler) {
fastify.addHook('preHandler', entry.preHandler)
}
const routes: Route[] = entry.routes || (await fastify.devServer.ssrLoadModule(`/${entry.name}/server.ts`)).routes
for (const route of routes.flatMap((route) => route.routes || route)) {
// const preHandler = entry.preHandler || entry.createPreHandler?.(route, entry)
const preHandler = entry.createPreHandler?.(route, entry)
if (route.locales?.length) {
const locales = [{ hostname: entry.hostname, path: route.path }, ...route.locales]
for (const locale of locales) {
fastify.route({
method: 'GET',
url: locale.path,
onRequest(request, reply, done) {
if (request.hostname !== locale.hostname) {
// TODO should probably redirect to correct path on request.hostname, not to locale.hostname
return reply.redirect('http://' + locale.hostname + request.url)
}
done()
},
handler,
preHandler,
})
}
} else {
fastify.route({
method: 'GET',
url: route.path,
handler,
preHandler,
})
}
}
fastify.setNotFoundHandler(() => {
throw new StatusError(404)
})
if (entry.createErrorHandler) {
fastify.setErrorHandler(
entry.createErrorHandler(async (error, request, reply) => {
reply.type('text/html').status((error as StatusError).status || 500)
return renderer(
request.url,
Object.assign(
{
error,
},
reply.ctx,
),
)
}),
)
}
}
function createRenderer(fastify: FastifyInstance, entry: Entry): Renderer {
return async (url, ctx) => {
ctx = Object.assign({ url }, entry.ctx, ctx)
const { render } = (await fastify.devServer.ssrLoadModule(`/${entry.name}/server.ts`)) as {
render?: RenderFunction
}
const renderPromise = render && entry.ssr !== false ? render(ctx) : null
return fastify.devServer.transformIndexHtml(
url,
await entry
.template({
env: 'development',
preloads: [],
script: `/${entry.name}/client.ts`,
styles: [],
content: renderPromise?.then((result) => result.html),
head: renderPromise?.then((result) => result.head),
state: renderPromise?.then((result) => result.state) || Promise.resolve(ctx),
})
.text(),
)
}
}
function createHandler(renderer: Renderer): RouteHandler {
return async function handler(request, reply) {
reply.type('text/html')
return renderer(request.url, reply.ctx)
}
}