vite
Version:
Native-ESM powered web dev build tool
130 lines (115 loc) • 3.76 kB
text/typescript
import chalk from 'chalk'
import { Server, STATUS_CODES } from 'http'
import {
createServer as createHttpsServer,
ServerOptions as HttpsServerOptions
} from 'https'
import WebSocket from 'ws'
import { ErrorPayload, HMRPayload } from 'types/hmrPayload'
import { ResolvedConfig } from '..'
export const HMR_HEADER = 'vite-hmr'
export interface WebSocketServer {
send(payload: HMRPayload): void
close(): Promise<void>
}
export function createWebSocketServer(
server: Server | null,
config: ResolvedConfig,
httpsOptions?: HttpsServerOptions
): WebSocketServer {
let wss: WebSocket.Server
let httpsServer: Server | undefined = undefined
const hmr = typeof config.server.hmr === 'object' && config.server.hmr
const wsServer = (hmr && hmr.server) || server
if (wsServer) {
wss = new WebSocket.Server({ noServer: true })
wsServer.on('upgrade', (req, socket, head) => {
if (req.headers['sec-websocket-protocol'] === HMR_HEADER) {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req)
})
}
})
} else {
const websocketServerOptions: WebSocket.ServerOptions = {}
const port = (hmr && hmr.port) || 24678
if (httpsOptions) {
// if we're serving the middlewares over https, the ws library doesn't support automatically creating an https server, so we need to do it ourselves
// create an inline https server and mount the websocket server to it
httpsServer = createHttpsServer(httpsOptions, (req, res) => {
const statusCode = 426
const body = STATUS_CODES[statusCode]
if (!body)
throw new Error(
`No body text found for the ${statusCode} status code`
)
res.writeHead(statusCode, {
'Content-Length': body.length,
'Content-Type': 'text/plain'
})
res.end(body)
})
httpsServer.listen(port)
websocketServerOptions.server = httpsServer
} else {
// we don't need to serve over https, just let ws handle its own server
websocketServerOptions.port = port
}
// vite dev server in middleware mode
wss = new WebSocket.Server(websocketServerOptions)
}
wss.on('connection', (socket) => {
socket.send(JSON.stringify({ type: 'connected' }))
if (bufferedError) {
socket.send(JSON.stringify(bufferedError))
bufferedError = null
}
})
wss.on('error', (e: Error & { code: string }) => {
if (e.code !== 'EADDRINUSE') {
config.logger.error(
chalk.red(`WebSocket server error:\n${e.stack || e.message}`)
)
}
})
// On page reloads, if a file fails to compile and returns 500, the server
// sends the error payload before the client connection is established.
// If we have no open clients, buffer the error and send it to the next
// connected client.
let bufferedError: ErrorPayload | null = null
return {
send(payload: HMRPayload) {
if (payload.type === 'error' && !wss.clients.size) {
bufferedError = payload
return
}
const stringified = JSON.stringify(payload)
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(stringified)
}
})
},
close() {
return new Promise((resolve, reject) => {
wss.close((err) => {
if (err) {
reject(err)
} else {
if (httpsServer) {
httpsServer.close((err) => {
if (err) {
reject(err)
} else {
resolve()
}
})
} else {
resolve()
}
}
})
})
}
}
}