136 lines
3.8 KiB
TypeScript
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)
|
|
}
|
|
}
|