You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

130 lines
4.2 KiB
TypeScript

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 = `${req.protocol}://${req.get('host')}${req.originalUrl}`
const metaTagsHtml =
'' +
(metadata.title ? `<meta property="og:title" content="${metadata.title ?? 'PHC | Problemi'}" />\n` : '') +
(metadata.description
? `<meta property="og:description" content="${
metadata.description ?? 'Bacheca di problemi di matematica creato dal PHC'
}" />\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(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 = `${req.protocol}://${req.get('host')}${req.originalUrl}`
const metaTagsHtml =
'' +
(metadata.title
? `<meta property="og:title" content="${metadata.title}" />\n`
: `<meta property="og:title" content="PHC | Problemi" />\n`) +
(metadata.description
? `<meta property="og:description" content="${metadata.description}" />\n`
: `<meta property="og:description" content="Bacheca di problemi di matematica creato dal PHC" />\n`) +
`<meta property="og:url" content="${fullUrl}" />\n`
const finalHtml = transformedTemplate.replace('<!-- INJECT META TAGS -->', metaTagsHtml).replace('<!-- SSR OUTLET -->', 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('/', r)
if (config.isDevelopment) {
app.use('/', await createDevRouter(db))
} else {
app.use('/', await createProductionRouter(db))
}
app.listen(config.port, () => {
console.log(`[Server] Listening on port ${config.port}...`)
})
}
main()