第一次提交
This commit is contained in:
141
server.js
Normal file
141
server.js
Normal file
@@ -0,0 +1,141 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import express from 'express'
|
||||
import cookieParser from 'cookie-parser'
|
||||
import { compress } from 'compress-json'
|
||||
|
||||
const isProd = process.env.NODE_ENV !== 'development'
|
||||
const port = process.env.PORT || 5174
|
||||
const base = process.env.BASE || '/'
|
||||
|
||||
const MESSAGE = {
|
||||
404: 'Not Found',
|
||||
0: 'Unknown'
|
||||
}
|
||||
|
||||
const templateHtml = isProd
|
||||
? await fs.readFile('./dist/client/index.html', 'utf-8')
|
||||
: ''
|
||||
|
||||
const app = express()
|
||||
app.set('trust proxy', true)
|
||||
app.use(cookieParser())
|
||||
|
||||
let vite
|
||||
if (!isProd) {
|
||||
const { createServer } = await import('vite')
|
||||
vite = await createServer({
|
||||
server: { middlewareMode: true },
|
||||
appType: 'custom',
|
||||
base,
|
||||
})
|
||||
app.use(vite.middlewares)
|
||||
} else {
|
||||
const sirv = (await import('sirv')).default
|
||||
app.use(base, sirv('./dist/client', { extensions: [] }))
|
||||
}
|
||||
|
||||
function generateHead({ title, metas }) {
|
||||
const heads = [`<title>${title}</title>`]
|
||||
for (const meta of metas) {
|
||||
const attrs = Object.entries(meta).map(([k, v]) => `${k}="${v}"`).join(' ')
|
||||
heads.push(`<meta ${attrs}>`)
|
||||
}
|
||||
return heads.join('')
|
||||
}
|
||||
|
||||
function injectHTML(template, { head, state }) {
|
||||
return template
|
||||
.replace('<!--app-head-->', head)
|
||||
.replace('<!--app-state-->', `<script>window.__PINIA_STATE__=${state}</script>`)
|
||||
}
|
||||
|
||||
app.use('*all', async (req, res) => {
|
||||
try {
|
||||
const url = req.originalUrl.replace(base, '')
|
||||
console.log(`${req.ip} /${url} "${req.get('User-Agent')}"`)
|
||||
|
||||
let template, render, getRoute
|
||||
if (!isProd) {
|
||||
template = await fs.readFile('./index.html', 'utf-8')
|
||||
template = await vite.transformIndexHtml(url, template)
|
||||
const mod = await vite.ssrLoadModule('/src/entry-server.js')
|
||||
render = mod.render
|
||||
getRoute = mod.getRoute
|
||||
} else {
|
||||
template = templateHtml
|
||||
const mod = await import('./dist/server/entry-server.js')
|
||||
render = mod.render
|
||||
getRoute = mod.getRoute
|
||||
}
|
||||
|
||||
const { router, code, title, metas, meta } = await getRoute(url)
|
||||
|
||||
if (code !== 200 && !req.accepts('html')) {
|
||||
res.status(code).type('text').send(MESSAGE[code] || MESSAGE[0])
|
||||
return
|
||||
}
|
||||
|
||||
const { stream, piniaState, headState } = await render(router, req.cookies, req.headers.host)
|
||||
const [htmlStart, htmlEnd] = template.split('<!--app-html-->')
|
||||
|
||||
if (meta) {
|
||||
const buffer = []
|
||||
let headSent = false
|
||||
|
||||
for await (const chunk of stream) {
|
||||
if (res.closed) break
|
||||
|
||||
if (headSent) {
|
||||
res.write(chunk)
|
||||
} else {
|
||||
if (headState.ready) {
|
||||
const head = generateHead({
|
||||
title: headState.title || title,
|
||||
metas: [...metas, ...headState.meta],
|
||||
})
|
||||
res.status(headState.code || code).type('html')
|
||||
res.write(htmlStart.replace('<!--app-head-->', head))
|
||||
buffer.forEach(c => res.write(c))
|
||||
res.write(chunk)
|
||||
headSent = true
|
||||
} else {
|
||||
buffer.push(chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!headState.ready) {
|
||||
console.warn('[WARN] meta not ready, falling back to default meta')
|
||||
const head = generateHead({ title, metas })
|
||||
res.status(code).type('html')
|
||||
res.write(htmlStart.replace('<!--app-head-->', head))
|
||||
for await (const chunk of buffer) {
|
||||
if (res.closed) break
|
||||
res.write(chunk)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const head = generateHead({ title, metas })
|
||||
res.status(code).type('html')
|
||||
res.write(htmlStart.replace('<!--app-head-->', head))
|
||||
for await (const chunk of stream) {
|
||||
if (res.closed) break
|
||||
res.write(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
const stateScript = JSON.stringify(compress(piniaState))
|
||||
res.write(htmlEnd.replace('<!--app-state-->', `<script>window.__PINIA_STATE__=${stateScript}</script>`))
|
||||
res.end()
|
||||
|
||||
} catch (err) {
|
||||
vite?.ssrFixStacktrace(err)
|
||||
console.error('[ERROR]', err.stack || err)
|
||||
res.status(500).end(err.stack)
|
||||
}
|
||||
})
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`🚀 Server running at http://localhost:${port} in mode ${isProd ? 'production' : 'development'}`)
|
||||
})
|
||||
|
Reference in New Issue
Block a user