diff --git a/packages/playground/preview/__tests__/preview.spec.ts b/packages/playground/preview/__tests__/preview.spec.ts new file mode 100644 index 00000000000000..fea79e01b84058 --- /dev/null +++ b/packages/playground/preview/__tests__/preview.spec.ts @@ -0,0 +1,45 @@ +import { httpServerStart } from 'vite/src/node/http' +import { resolveConfig } from 'vite/src/node' +import { createServer } from 'http' + +describe('start preview server', () => { + test('[strictPort=false, host=127.0.0.1] should use another port when already start a server on localhost.', async () => { + const originalPort = 5000 + const server = createServer() + server.listen(originalPort, 'localhost') + + const testServer = createServer() + const config = await resolveConfig({}, 'serve', 'production') + const port = await httpServerStart(testServer, { + port: originalPort, + strictPort: false, + host: '127.0.0.1', + logger: config.logger + }) + + expect(port).not.toEqual(originalPort) + + server.close() + testServer.close() + }) + + test('[strictPort=true, host=127.0.0.1] should use another port when already start a server on localhost.', async () => { + const originalPort = 5000 + const server = createServer() + server.listen(originalPort, 'localhost') + + const config = await resolveConfig({}, 'serve', 'production') + const testServer = createServer() + await expect( + httpServerStart(testServer, { + port: originalPort, + strictPort: true, + host: '127.0.0.1', + logger: config.logger + }) + ).rejects.toEqual(new Error(`Port ${originalPort} is already in use`)) + + server.close() + testServer.close() + }) +}) diff --git a/packages/playground/preview/index.html b/packages/playground/preview/index.html new file mode 100644 index 00000000000000..6fada566b4aa8d --- /dev/null +++ b/packages/playground/preview/index.html @@ -0,0 +1,12 @@ + + + + + + Preview + + +

Preview

+
+ + diff --git a/packages/playground/preview/package.json b/packages/playground/preview/package.json new file mode 100644 index 00000000000000..77b6cf4aa1212b --- /dev/null +++ b/packages/playground/preview/package.json @@ -0,0 +1,9 @@ +{ + "name": "preview", + "version": "0.0.0", + "scripts": { + "dev": "vite --force", + "build": "vite build", + "preview": "vite preview" + } +} diff --git a/packages/vite/src/node/http.ts b/packages/vite/src/node/http.ts index becc2191efac7f..96ed55e758e662 100644 --- a/packages/vite/src/node/http.ts +++ b/packages/vite/src/node/http.ts @@ -183,6 +183,39 @@ async function getCertificate(cacheDir?: string) { } } +function checkPortOccupied( + port: number, + host?: string +): Promise<{ port: number; occupied: boolean }> { + return new Promise((resolve, reject) => { + const tempServer = require('http').createServer() + + const onError = (e: Error & { code?: string }) => { + if (e.code === 'EADDRINUSE') { + checkPortOccupied(++port, host) + .then((r) => { + resolve({ + port: r.port, + occupied: true + }) + }) + .catch(reject) + } else { + reject(e) + } + tempServer.removeListener('error', onError) + tempServer.close() + } + + tempServer.on('error', onError) + + tempServer.listen(port, host, () => { + tempServer.removeListener('error', onError) + tempServer.close(() => resolve({ port, occupied: false })) + }) + }) +} + export async function httpServerStart( httpServer: HttpServer, serverOptions: { @@ -192,28 +225,39 @@ export async function httpServerStart( logger: Logger } ): Promise { + const { strictPort, host, logger } = serverOptions + // Check the port usage of serverOptions.host + let checkInfo = await checkPortOccupied(serverOptions.port, host) + + if (host === '127.0.0.1') { + // Check the port usage of localhost + checkInfo = await checkPortOccupied(serverOptions.port, 'localhost') + } + return new Promise((resolve, reject) => { - let { port, strictPort, host, logger } = serverOptions + const { port, occupied } = checkInfo - const onError = (e: Error & { code?: string }) => { - if (e.code === 'EADDRINUSE') { - if (strictPort) { - httpServer.removeListener('error', onError) - reject(new Error(`Port ${port} is already in use`)) - } else { - logger.info(`Port ${port} is in use, trying another one...`) - httpServer.listen(++port, host) - } - } else { - httpServer.removeListener('error', onError) - reject(e) - } + if (occupied && strictPort) { + reject(new Error(`Port ${serverOptions.port} is already in use`)) + return + } + + const onError = (e: Error) => { + httpServer.removeListener('error', onError) + reject(e) } httpServer.on('error', onError) httpServer.listen(port, host, () => { httpServer.removeListener('error', onError) + + if (occupied) { + logger.info( + `Port ${serverOptions.port} is in use, trying another one...` + ) + } + resolve(port) }) })