server.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import fs from 'node:fs/promises'
  2. import express from 'express'
  3. import { Transform } from 'node:stream'
  4. // Constants
  5. const isProduction = process.env.NODE_ENV === 'production'
  6. const port = process.env.PORT || 5173
  7. const base = process.env.BASE || '/'
  8. const ABORT_DELAY = 10000
  9. // Cached production assets
  10. const templateHtml = isProduction
  11. ? await fs.readFile('./dist/client/index.html', 'utf-8')
  12. : ''
  13. // Create http server
  14. const app = express()
  15. // Add Vite or respective production middlewares
  16. /** @type {import('vite').ViteDevServer | undefined} */
  17. let vite
  18. if (!isProduction) {
  19. const { createServer } = await import('vite')
  20. vite = await createServer({
  21. server: { middlewareMode: true },
  22. appType: 'custom',
  23. base,
  24. })
  25. app.use(vite.middlewares)
  26. } else {
  27. const compression = (await import('compression')).default
  28. const sirv = (await import('sirv')).default
  29. app.use(compression())
  30. app.use(base, sirv('./dist/client', { extensions: [] }))
  31. }
  32. // Serve HTML
  33. app.use('*all', async (req, res) => {
  34. try {
  35. const url = req.originalUrl.replace(base, '')
  36. /** @type {string} */
  37. let template
  38. /** @type {import('./src/entry-server.ts').render} */
  39. let render
  40. if (!isProduction) {
  41. // Always read fresh template in development
  42. template = await fs.readFile('./index.html', 'utf-8')
  43. template = await vite.transformIndexHtml(url, template)
  44. render = (await vite.ssrLoadModule('/src/entry-server.tsx')).render
  45. } else {
  46. template = templateHtml
  47. render = (await import('./dist/server/entry-server.js')).render
  48. }
  49. let didError = false
  50. const { pipe, abort } = render(url, {
  51. onShellError() {
  52. res.status(500)
  53. res.set({ 'Content-Type': 'text/html' })
  54. res.send('<h1>Something went wrong</h1>')
  55. },
  56. onShellReady() {
  57. res.status(didError ? 500 : 200)
  58. res.set({ 'Content-Type': 'text/html' })
  59. const transformStream = new Transform({
  60. transform(chunk, encoding, callback) {
  61. res.write(chunk, encoding)
  62. callback()
  63. },
  64. })
  65. const [htmlStart, htmlEnd] = template.split(`<!--app-html-->`)
  66. res.write(htmlStart)
  67. transformStream.on('finish', () => {
  68. res.end(htmlEnd)
  69. })
  70. pipe(transformStream)
  71. },
  72. onError(error) {
  73. didError = true
  74. console.error(error)
  75. },
  76. })
  77. setTimeout(() => {
  78. abort()
  79. }, ABORT_DELAY)
  80. } catch (e) {
  81. vite?.ssrFixStacktrace(e)
  82. console.log(e.stack)
  83. res.status(500).end(e.stack)
  84. }
  85. })
  86. // Start http server
  87. app.listen(port, () => {
  88. console.log(`Server started at http://localhost:${port}`)
  89. })