mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 09:25:54 +00:00
feat(nitro): improve dev worker stability (#1303)
This commit is contained in:
parent
34910f1218
commit
9d40a271ee
@ -1,14 +1,26 @@
|
||||
import '#polyfill'
|
||||
import { Server } from 'http'
|
||||
import { parentPort } from 'worker_threads'
|
||||
import type { AddressInfo } from 'net'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { mkdirSync } from 'fs'
|
||||
import { threadId, parentPort } from 'worker_threads'
|
||||
import { handle } from '../server'
|
||||
|
||||
const server = new Server(handle)
|
||||
|
||||
const netServer = server.listen(0, () => {
|
||||
parentPort.postMessage({
|
||||
event: 'listen',
|
||||
port: (netServer.address() as AddressInfo).port
|
||||
})
|
||||
function createSocket () {
|
||||
const isWin = process.platform === 'win32'
|
||||
const socketName = `worker-${process.pid}-${threadId}.sock`
|
||||
if (isWin) {
|
||||
return join('\\\\.\\pipe\\nitro', socketName)
|
||||
} else {
|
||||
const socketDir = join(tmpdir(), 'nitro')
|
||||
mkdirSync(socketDir, { recursive: true })
|
||||
return join(socketDir, socketName)
|
||||
}
|
||||
}
|
||||
|
||||
const socketPath = createSocket()
|
||||
server.listen(socketPath, () => {
|
||||
parentPort.postMessage({ event: 'listen', address: { socketPath } })
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Worker } from 'worker_threads'
|
||||
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import { promises as fsp } from 'fs'
|
||||
import { existsSync, promises as fsp } from 'fs'
|
||||
import { loading as loadingTemplate } from '@nuxt/design'
|
||||
import chokidar, { FSWatcher } from 'chokidar'
|
||||
import debounce from 'p-debounce'
|
||||
@ -15,44 +15,59 @@ import connect from 'connect'
|
||||
import type { NitroContext } from '../context'
|
||||
import { handleVfs } from './vfs'
|
||||
|
||||
export interface NitroWorker {
|
||||
worker: Worker,
|
||||
address: string
|
||||
}
|
||||
|
||||
function initWorker (filename): Promise<NitroWorker> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker(filename)
|
||||
worker.once('exit', (code) => {
|
||||
if (code) {
|
||||
reject(new Error('[worker] exited with code: ' + code))
|
||||
}
|
||||
})
|
||||
worker.on('error', (err) => {
|
||||
err.message = '[worker] ' + err.message
|
||||
reject(err)
|
||||
})
|
||||
worker.on('message', (event) => {
|
||||
if (event && event.address) {
|
||||
resolve({
|
||||
worker,
|
||||
address: event.address
|
||||
} as NitroWorker)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function killWorker (worker: NitroWorker) {
|
||||
await worker.worker.terminate()
|
||||
worker.worker = null
|
||||
if (worker.address && existsSync(worker.address)) {
|
||||
await fsp.rm(worker.address).catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
export function createDevServer (nitroContext: NitroContext) {
|
||||
// Worker
|
||||
const workerEntry = resolve(nitroContext.output.dir, nitroContext.output.serverDir, 'index.mjs')
|
||||
let pendingWorker: Worker | null
|
||||
let activeWorker: Worker
|
||||
let workerAddress: string | null
|
||||
|
||||
let currentWorker: NitroWorker
|
||||
|
||||
async function reload () {
|
||||
if (pendingWorker) {
|
||||
await pendingWorker.terminate()
|
||||
workerAddress = null
|
||||
pendingWorker = null
|
||||
// Create a new worker
|
||||
const newWorker = await initWorker(workerEntry)
|
||||
|
||||
// Kill old worker in background
|
||||
if (currentWorker) {
|
||||
killWorker(currentWorker).catch(err => console.error(err))
|
||||
}
|
||||
if (!(await fsp.stat(workerEntry)).isFile) {
|
||||
throw new Error('Entry not found: ' + workerEntry)
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = pendingWorker = new Worker(workerEntry)
|
||||
worker.once('exit', (code) => {
|
||||
if (code) {
|
||||
reject(new Error('[worker] exited with code: ' + code))
|
||||
}
|
||||
})
|
||||
worker.on('error', (err) => {
|
||||
err.message = '[worker] ' + err.message
|
||||
reject(err)
|
||||
})
|
||||
worker.on('message', (event) => {
|
||||
if (event && event.port) {
|
||||
workerAddress = 'http://localhost:' + event.port
|
||||
if (activeWorker) {
|
||||
activeWorker.terminate()
|
||||
}
|
||||
activeWorker = worker
|
||||
pendingWorker = null
|
||||
resolve(workerAddress)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Replace new worker as current
|
||||
currentWorker = newWorker
|
||||
}
|
||||
|
||||
// App
|
||||
@ -76,11 +91,13 @@ export function createDevServer (nitroContext: NitroContext) {
|
||||
|
||||
// SSR Proxy
|
||||
const proxy = httpProxy.createProxy()
|
||||
const proxyHandle = promisifyHandle((req: IncomingMessage, res: ServerResponse) => proxy.web(req, res, { target: workerAddress }, (_err: unknown) => {
|
||||
// console.error('[proxy]', err)
|
||||
}))
|
||||
const proxyHandle = promisifyHandle((req: IncomingMessage, res: ServerResponse) => {
|
||||
proxy.web(req, res, { target: currentWorker.address }, (error: unknown) => {
|
||||
console.error('[proxy]', error)
|
||||
})
|
||||
})
|
||||
app.use((req, res) => {
|
||||
if (workerAddress) {
|
||||
if (currentWorker?.address) {
|
||||
// Workaround to pass legacy req.spa to proxy
|
||||
// @ts-ignore
|
||||
if (req.spa) {
|
||||
@ -119,11 +136,8 @@ export function createDevServer (nitroContext: NitroContext) {
|
||||
if (watcher) {
|
||||
await watcher.close()
|
||||
}
|
||||
if (activeWorker) {
|
||||
await activeWorker.terminate()
|
||||
}
|
||||
if (pendingWorker) {
|
||||
await pendingWorker.terminate()
|
||||
if (currentWorker) {
|
||||
await killWorker(currentWorker)
|
||||
}
|
||||
await Promise.all(listeners.map(l => l.close()))
|
||||
listeners = []
|
||||
|
Loading…
Reference in New Issue
Block a user