-
Notifications
You must be signed in to change notification settings - Fork 91
/
server.js
159 lines (134 loc) · 4.81 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// @ts-ignore
const fs = require('fs').promises
const path = require('path')
const connect = require('connect')
const { createServer: createViteServer, resolveConfig } = require('vite')
const { getEntryPoint } = require('../config')
async function resolveHttpServer(app) {
const config = await resolveConfig(
{},
'serve',
process.env.NODE_ENV || 'development'
)
try {
// In order to have the same behavior as Vite dev itself,
// we need to create the HTTP server in the same way as Vite does.
// However, Vite does not expose its internal HTTP server creator function.
// This should find that function in Vite internals but might need to be
// adjusted from time to time if this is updated in Vite.
const vitePath = require.resolve('vite')
let tmp = await fs.readFile(vitePath, 'utf-8')
const [, chunk] = tmp.match(/require\('(\.\/chunks\/.+)'\)/)
tmp = null
const internals = require(path.resolve(path.dirname(vitePath), chunk))
return internals.resolveHttpServer(config.server, app)
} catch (error) {
console.warn(
'\nCould not import internal Vite module. This likely means Vite internals have been updated in a new version.\n'
)
throw error
}
}
function fixEntryPoint(vite, pluginName) {
// The plugin is redirecting to the entry-client for the SPA,
// but we need to reach the entry-server here. This trick
// replaces the plugin behavior in the config and seems
// to keep the entry-client for the SPA.
const alias = vite.config.resolve.alias.find(
(item) =>
typeof item.replacement === 'string' &&
(item.replacement || '').includes(pluginName)
)
alias.replacement = alias.replacement.replace('client', 'server')
}
async function createSsrServer(options = {}) {
const { plugin: pluginName = 'vite-ssr' } = options
const app = connect()
const httpServer = await resolveHttpServer(app)
const vite = await createViteServer({
base: options.base,
mode: options.mode,
configFile: options.config,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
server: {
...options,
middlewareMode: true,
},
})
app.use(vite.middlewares)
// Find Vite SSR options added by another plugin that uses internally (e.g. Vitedge).
options = Object.assign(
{},
(vite.config.plugins.find((plugin) => plugin.name === pluginName) || {})
.viteSsr || {},
options
)
const resolve = (p) => path.resolve(vite.config.root, p)
async function getIndexTemplate(url) {
// Template should be fresh in every request
const indexHtml = await fs.readFile(resolve('index.html'), 'utf-8')
return await vite.transformIndexHtml(url, indexHtml)
}
app.use(async (request, response, next) => {
if (request.method !== 'GET' || request.url === '/favicon.ico') {
return next()
}
fixEntryPoint(vite, pluginName)
try {
const template = await getIndexTemplate(request.url)
const entryPoint =
options.ssr || (await getEntryPoint(vite.config.root, template))
let resolvedEntryPoint = await vite.ssrLoadModule(resolve(entryPoint))
resolvedEntryPoint = resolvedEntryPoint.default || resolvedEntryPoint
const render = resolvedEntryPoint.render || resolvedEntryPoint
const protocol =
request.protocol ||
(request.headers.referer || '').split(':')[0] ||
'http'
const url = protocol + '://' + request.headers.host + request.url
// This context might contain initialState provided by other plugins
const context = options.getRenderContext
? await options.getRenderContext({
url,
request,
response,
resolvedEntryPoint,
})
: {}
const {
headTags,
body,
bodyAttrs,
htmlAttrs,
initialState,
} = await render(url, { request, response, ...context })
// These replacements should be similar to the build behavior
const html = template
.replace('<html', `<html ${htmlAttrs} `)
.replace('<body', `<body ${bodyAttrs} `)
.replace('</head>', `${headTags}\n</head>`)
.replace(
'<div id="app"></div>',
`<div id="app" data-server-rendered="true">${body}</div>\n\n<script>window.__INITIAL_STATE__=${initialState}</script>`
)
response.setHeader('Content-Type', 'text/html')
response.end(html)
} catch (e) {
vite.ssrFixStacktrace(e)
console.log(e.stack)
next(e)
}
})
// Add the custom server back to Vite in order
// to reuse its own terminal output style, etc.
vite.httpServer = httpServer
return {
async listen(port, host) {
globalThis.fetch = require('node-fetch')
await vite.listen(port, host)
vite.config.logger.info('\n -- SSR mode\n')
},
}
}
module.exports = createSsrServer