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)
})
})