import path from 'path' import fs from 'fs/promises' import express, { Handler, Router } from 'express' import morgan from 'morgan' import { createServer as createViteServer } from 'vite' import { createApiRouter } from './server/routes.js' import { RenderFunction } from './shared/ssr.js' const HTML_ROUTES = ['/', '/login', '/problem/:id', '/admin', '/profile'] const config = { isDevelopment: process.env.MODE === 'development', port: process.env.PORT || 3000, } if (config.isDevelopment) { console.log(`[Config] Running in development mode`) console.log(`[Config] PORT = ${config.port}`) } function mountIndexHtmlRoutes(r: Router, serveIndexHtml: Handler) { for (const route of HTML_ROUTES) { console.log(`[Server] Mounted index html for "${route}"`) r.get(route, serveIndexHtml) } r.use('*', (_req, res) => { res.redirect('/') }) } async function createDevRouter() { const r = express.Router() const vite = await createViteServer({ server: { middlewareMode: true }, appType: 'custom', }) r.use(vite.middlewares) mountIndexHtmlRoutes(r, async (req, res, next) => { try { const indexHtml = await fs.readFile(path.resolve('./index.html'), 'utf-8') const transformedTemplate = await vite.transformIndexHtml(req.originalUrl, indexHtml) // Load (to be bundled) entry point for server side rendering const render: RenderFunction = (await vite.ssrLoadModule('./client/entry-server.tsx')) .default const { html, metadata } = render(req.originalUrl) process.stdout.write('[Metadata] ') console.dir(metadata) const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}` const metaTagsHtml = '' + (metadata.title ? `\n` : '') + (metadata.description ? `\n` : '') + `\n` res.send( transformedTemplate .replace('', metaTagsHtml) .replace('', html) ) } catch (error) { vite.ssrFixStacktrace(error) next(error) } }) return r } async function createProductionRouter() { // Load bundled entry point for server side rendering const render: RenderFunction = ((await import('./dist/entry-server/entry-server.js')) as any) .default const r = express.Router() r.use('/', express.static('dist/client')) mountIndexHtmlRoutes(r, async (req, res) => { const transformedTemplate = await fs.readFile( path.resolve('./dist/client/index.html'), 'utf-8' ) const { html, metadata } = render(req.originalUrl) res.send(transformedTemplate.replace('', html)) }) return r } async function main() { const app = express() app.use(morgan('dev')) app.use('/', await createApiRouter()) if (config.isDevelopment) { app.use('/', await createDevRouter()) } else { app.use('/', await createProductionRouter()) } app.listen(config.port, () => { console.log(`Listening on port ${config.port}...`) }) } main()