import path from 'path' import fs from 'fs/promises' import dotenv from 'dotenv' import express, { Handler, Router } from 'express' import morgan from 'morgan' import { createServer as createViteServer } from 'vite' import { createApiRouter } from './server/routes' import { RenderFunction } from './shared/ssr' import { DatabaseConnection } from './shared/database' // Load ".env" dotenv.config() 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}`) } async function createDevRouter(db: DatabaseConnection) { const r = express.Router() const vite = await createViteServer({ server: { middlewareMode: true }, appType: 'custom', }) r.use(vite.middlewares) r.use('*', 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, asyncCallbacks } = render(req.originalUrl, db) await Promise.all(asyncCallbacks.map(fn => fn())) const fullUrl = `https://${req.get('host')}${req.originalUrl}` const metaTagsHtml = '' + (metadata.title ? `\n` : '') + (metadata.description ? ` \n` : '') + `\n` res.send(transformedTemplate.replace('', metaTagsHtml).replace('', html)) } catch (error: any) { vite.ssrFixStacktrace(error) next(error) } }) return r } async function createProductionRouter(db: DatabaseConnection) { // Load bundled entry point for server side rendering // @ts-ignore const render: RenderFunction = (await import('./dist/entry-server/entry-server.js')).default const r = express.Router() const handleSSR = async (req, res) => { const transformedTemplate = await fs.readFile(path.resolve('./dist/entry-client/index.html'), 'utf-8') const { html, metadata, asyncCallbacks } = render(req.originalUrl, db) await Promise.all(asyncCallbacks.map(fn => fn())) const fullUrl = `https://${req.get('host')}${req.originalUrl}` const metaTagsHtml = '' + (metadata.title ? `\n` : `\n`) + (metadata.description ? ` \n` : ` \n`) + `\n` const finalHtml = transformedTemplate.replace('', metaTagsHtml).replace('', html) console.dir(finalHtml, { depth: null }) res.send(finalHtml) } r.get('/', handleSSR) r.use('/', express.static('dist/entry-client')) r.use('*', handleSSR) return r } async function main() { const app = express() app.use(morgan('[Request] :method :url :status :response-time ms - :res[content-length]')) const [r, db] = await createApiRouter() app.use(process.env.BASE_URL ?? '/', r) if (config.isDevelopment) { app.use(process.env.BASE_URL ?? '/', await createDevRouter(db)) } else { app.use(process.env.BASE_URL ?? '/', await createProductionRouter(db)) } app.listen(config.port, () => { console.log(`[Server] Listening on port ${config.port}...`) }) } main()