Skip to content

Commit

Permalink
feat: support using vite as a middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 13, 2021
1 parent 54ff2b4 commit 960b420
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 55 deletions.
28 changes: 28 additions & 0 deletions docs/guide/api-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,34 @@ const { createServer } = require('vite')
})()
```

### Using the Vite Server as a Middleware

Vite can be used as a middleware in an existing raw Node.js http server or frameworks that are comaptible with the `(req, res, next) => {}` style middlewares. For example with `express`:

```js
const vite = require('vite')
const express = require('express')
;(async () => {
const app = express()
// create vite dev server in middelware mode
// so vite creates the hmr websocket server on its own.
// the ws server will be listening at port 24678 by default, and can be
// configured via server.hmr.port
const viteServer = await vite.createServer({
server: {
middlewareMode: true
}
})
// use vite's connect instance as middleware
app.use(viteServer.app)
app.listen(3000)
})()
```

## `InlineConfig`

The `InlineConfig` interface extends `UserConfig` with additional properties:
Expand Down
11 changes: 10 additions & 1 deletion packages/vite/src/node/plugins/clientInjections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,16 @@ export function clientInjectionsPlugin(config: ResolvedConfig): Plugin {
const protocol = options.protocol || null
const timeout = options.timeout || 30000
const overlay = options.overlay !== false
let port = String(options.port || config.server.port!)
let port
if (config.server.middlewareMode) {
port = String(
typeof config.server.hmr === 'object'
? config.server.hmr.port
: 24678
)
} else {
port = String(options.port || config.server.port!)
}
if (options.path) {
port = `${port}/${options.path}`
}
Expand Down
71 changes: 43 additions & 28 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import {
EsbuildTransformResult
} from '../plugins/esbuild'
import { TransformOptions as EsbuildTransformOptions } from 'esbuild'
import { createLogger } from '../logger'
import { DepOptimizationMetadata, optimizeDeps } from '../optimizer'

export interface ServerOptions {
Expand Down Expand Up @@ -102,6 +101,10 @@ export interface ServerOptions {
* If enabled, vite will exit if specified port is already in use
*/
strictPort?: boolean
/**
* Create Vite dev server to be used as a middleware in an existing server
*/
middlewareMode?: boolean
}

/**
Expand Down Expand Up @@ -139,8 +142,9 @@ export interface ViteDevServer {
app: Connect.Server
/**
* native Node http server instance
* will be null in middleware mode
*/
httpServer: http.Server
httpServer: http.Server | null
/**
* chokidar watcher instance
* https://github.com/paulmillr/chokidar#api
Expand Down Expand Up @@ -193,12 +197,13 @@ export async function createServer(
): Promise<ViteDevServer> {
const config = await resolveConfig(inlineConfig, 'serve', 'development')
const root = config.root
const logger = createLogger(config.logLevel)
const serverConfig = config.server || {}

const app = connect() as Connect.Server
const httpServer = await resolveHttpServer(serverConfig, app)
const ws = createWebSocketServer(httpServer, logger)
const httpServer = serverConfig.middlewareMode
? null
: await resolveHttpServer(serverConfig, app)
const ws = createWebSocketServer(httpServer, config)

const watchOptions = serverConfig.watch || {}
const watcher = chokidar.watch(root, {
Expand Down Expand Up @@ -339,30 +344,32 @@ export async function createServer(
// error handler
app.use(errorMiddleware(server))

// overwrite listen to run optimizer before server start
const listen = httpServer.listen.bind(httpServer)
httpServer.listen = (async (port: number, ...args: any[]) => {
await container.buildStart({})

if (config.optimizeCacheDir) {
// run optimizer
await optimizeDeps(config)
// after optimization, read updated optimization metadata
const dataPath = path.resolve(config.optimizeCacheDir, 'metadata.json')
if (fs.existsSync(dataPath)) {
server.optimizeDepsMetadata = JSON.parse(
fs.readFileSync(dataPath, 'utf-8')
)
if (httpServer) {
// overwrite listen to run optimizer before server start
const listen = httpServer.listen.bind(httpServer)
httpServer.listen = (async (port: number, ...args: any[]) => {
await container.buildStart({})

if (config.optimizeCacheDir) {
// run optimizer
await optimizeDeps(config)
// after optimization, read updated optimization metadata
const dataPath = path.resolve(config.optimizeCacheDir, 'metadata.json')
if (fs.existsSync(dataPath)) {
server.optimizeDepsMetadata = JSON.parse(
fs.readFileSync(dataPath, 'utf-8')
)
}
}
}

return listen(port, ...args)
}) as any
return listen(port, ...args)
}) as any

httpServer.once('listening', () => {
// update actual port since this may be different from initial value
serverConfig.port = (httpServer.address() as AddressInfo).port
})
httpServer.once('listening', () => {
// update actual port since this may be different from initial value
serverConfig.port = (httpServer.address() as AddressInfo).port
})
}

return server
}
Expand Down Expand Up @@ -396,11 +403,15 @@ async function startServer(
server: ViteDevServer,
inlinePort?: number
): Promise<ViteDevServer> {
const httpServer = server.httpServer
if (!httpServer) {
throw new Error('Cannot call server.listen in middleware mode.')
}

const options = server.config.server || {}
let port = inlinePort || options.port || 3000
let hostname = options.host || 'localhost'
const protocol = options.https ? 'https' : 'http'
const httpServer = server.httpServer
const info = server.config.logger.info

return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -485,7 +496,11 @@ async function startServer(
})
}

function createSeverCloseFn(server: http.Server) {
function createSeverCloseFn(server: http.Server | null) {
if (!server) {
return () => {}
}

const openSockets = new Set<net.Socket>()

server.on('connection', (socket) => {
Expand Down
30 changes: 16 additions & 14 deletions packages/vite/src/node/server/middlewares/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,25 @@ export function proxyMiddleware({
proxies[context] = [proxy, { ...opts }]
})

httpServer.on('upgrade', (req, socket, head) => {
const url = req.url!
for (const context in proxies) {
if (url.startsWith(context)) {
const [proxy, opts] = proxies[context]
if (
(opts.ws || opts.target?.toString().startsWith('ws:')) &&
req.headers['sec-websocket-protocol'] !== HMR_HEADER
) {
if (opts.rewrite) {
req.url = opts.rewrite(url)
if (httpServer) {
httpServer.on('upgrade', (req, socket, head) => {
const url = req.url!
for (const context in proxies) {
if (url.startsWith(context)) {
const [proxy, opts] = proxies[context]
if (
(opts.ws || opts.target?.toString().startsWith('ws:')) &&
req.headers['sec-websocket-protocol'] !== HMR_HEADER
) {
if (opts.rewrite) {
req.url = opts.rewrite(url)
}
proxy.ws(req, socket, head)
}
proxy.ws(req, socket, head)
}
}
}
})
})
}

return (req, res, next) => {
const url = req.url!
Expand Down
35 changes: 23 additions & 12 deletions packages/vite/src/node/server/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import chalk from 'chalk'
import { Server } from 'http'
import WebSocket from 'ws'
import { ErrorPayload, HMRPayload } from 'types/hmrPayload'
import { Logger } from '../logger'
import { ResolvedConfig } from '..'

export const HMR_HEADER = 'vite-hmr'

Expand All @@ -12,18 +12,29 @@ export interface WebSocketServer {
}

export function createWebSocketServer(
server: Server,
logger: Logger
server: Server | null,
config: ResolvedConfig
): WebSocketServer {
const wss = new WebSocket.Server({ noServer: true })
const wss = new WebSocket.Server(
server
? { noServer: true }
: {
port:
typeof config.server.hmr === 'object'
? config.server.hmr.port
: 24678
}
)

server.on('upgrade', (req, socket, head) => {
if (req.headers['sec-websocket-protocol'] === HMR_HEADER) {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req)
})
}
})
if (server) {
server.on('upgrade', (req, socket, head) => {
if (req.headers['sec-websocket-protocol'] === HMR_HEADER) {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req)
})
}
})
}

wss.on('connection', (socket) => {
socket.send(JSON.stringify({ type: 'connected' }))
Expand All @@ -35,7 +46,7 @@ export function createWebSocketServer(

wss.on('error', (e: Error & { code: string }) => {
if (e.code !== 'EADDRINUSE') {
logger.error(
config.logger.error(
chalk.red(`WebSocket server error:\n${e.stack || e.message}`)
)
}
Expand Down

0 comments on commit 960b420

Please sign in to comment.