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'
// Load ".env"
dotenv.config()
const HTML_ROUTES = ['/', '/login', '/problem/:id', '/u/:uid', '/admin', '/profile', '/scores']
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)
}
}
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)
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: any) {
vite.ssrFixStacktrace(error)
next(error)
}
})
r.use('*', (_req, res) => {
res.redirect('/')
})
return r
}
async function createProductionRouter() {
// 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()
mountIndexHtmlRoutes(r, async (req, res) => {
const transformedTemplate = await fs.readFile(path.resolve('./dist/entry-client/index.html'), 'utf-8')
const { html, metadata } = render(req.originalUrl)
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))
})
r.use('/', express.static('dist/entry-client'))
r.use('*', (_req, res) => {
res.redirect('/')
})
return r
}
async function main() {
const app = express()
app.use(morgan('[Request] :method :url :status :response-time ms - :res[content-length]'))
app.use('/', await createApiRouter())
if (config.isDevelopment) {
app.use('/', await createDevRouter())
} else {
app.use('/', await createProductionRouter())
}
app.listen(config.port, () => {
console.log(`[Server] Listening on port ${config.port}...`)
})
}
main()