|
|
|
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 ? `<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 = `https://${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(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()
|