|
|
|
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'
|
|
|
|
import { RenderFunction } from './shared/ssr'
|
|
|
|
|
|
|
|
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
|
|
|
|
? `<meta property="og:title" content="${metadata.title}" />\n`
|
|
|
|
: '') +
|
|
|
|
(metadata.description
|
|
|
|
? `<meta property="og:description" content="${metadata.description}" />\n`
|
|
|
|
: '') +
|
|
|
|
`<meta property="og:url" content="${fullUrl}" />\n`
|
|
|
|
|
|
|
|
res.send(
|
|
|
|
transformedTemplate
|
|
|
|
.replace('<!-- INJECT META TAGS -->', metaTagsHtml)
|
|
|
|
.replace('<!-- SSR OUTLET -->', html)
|
|
|
|
)
|
|
|
|
} catch (error: any) {
|
|
|
|
vite.ssrFixStacktrace(error)
|
|
|
|
next(error)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
r.use('/', express.static('dist/entry-client'))
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
res.send(transformedTemplate.replace('<!-- SSR OUTLET -->', 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()
|