diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index ff4df305991a8b..d6410fab6d1693 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -190,6 +190,7 @@ cli .option('--port ', `[number] specify port`) .option('--https', `[boolean] use TLS + HTTP/2`) .option('--open [path]', `[boolean | string] open browser on startup`) + .option('--strictPort', `[boolean] exit if specified port is already in use`) .action( async ( root: string, @@ -198,6 +199,7 @@ cli port?: number https?: boolean open?: boolean | string + strictPort?: boolean } & GlobalCLIOptions ) => { try { @@ -208,7 +210,8 @@ cli configFile: options.config, logLevel: options.logLevel, server: { - open: options.open + open: options.open, + strictPort: options.strictPort } }, 'serve', diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index d5ebda8dc366f0..cb171bdba2b16d 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -5,7 +5,11 @@ import connect from 'connect' import compression from 'compression' import { ResolvedConfig } from '.' import { Connect } from 'types/connect' -import { resolveHttpsConfig, resolveHttpServer } from './server/http' +import { + resolveHttpsConfig, + resolveHttpServer, + httpServerStart +} from './server/http' import { openBrowser } from './server/openBrowser' import corsMiddleware from 'cors' import { proxyMiddleware } from './server/middlewares/proxy' @@ -53,17 +57,26 @@ export async function preview( const logger = config.logger const base = config.base - httpServer.listen(port, hostname.host, () => { - logger.info( - chalk.cyan(`\n vite v${require('vite/package.json').version}`) + - chalk.green(` build preview server running at:\n`) - ) + const serverPort = await httpServerStart(httpServer, { + port, + strictPort: options.strictPort, + host: hostname.host, + logger + }) - printServerUrls(hostname, protocol, port, base, logger.info) + logger.info( + chalk.cyan(`\n vite v${require('vite/package.json').version}`) + + chalk.green(` build preview server running at:\n`) + ) - if (options.open) { - const path = typeof options.open === 'string' ? options.open : base - openBrowser(`${protocol}://${hostname.name}:${port}${path}`, true, logger) - } - }) + printServerUrls(hostname, protocol, serverPort, base, logger.info) + + if (options.open) { + const path = typeof options.open === 'string' ? options.open : base + openBrowser( + `${protocol}://${hostname.name}:${serverPort}${path}`, + true, + logger + ) + } } diff --git a/packages/vite/src/node/server/http.ts b/packages/vite/src/node/server/http.ts index 8bfe418a02bcf1..7284eefb4f5570 100644 --- a/packages/vite/src/node/server/http.ts +++ b/packages/vite/src/node/server/http.ts @@ -4,6 +4,7 @@ import { Server as HttpServer } from 'http' import { ServerOptions as HttpsServerOptions } from 'https' import { ResolvedConfig, ServerOptions } from '..' import { Connect } from 'types/connect' +import { Logger } from '../logger' export async function resolveHttpServer( { proxy }: ServerOptions, @@ -159,3 +160,39 @@ async function getCertificate(config: ResolvedConfig) { return content } } + +export async function httpServerStart( + httpServer: HttpServer, + serverOptions: { + port: number + strictPort: boolean | undefined + host: string | undefined + logger: Logger + } +): Promise { + return new Promise((resolve, reject) => { + let { port, strictPort, host, logger } = serverOptions + + 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) + } + } + + httpServer.on('error', onError) + + httpServer.listen(port, host, () => { + httpServer.removeListener('error', onError) + resolve(port) + }) + }) +} diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 5f7ab12765a169..fefe995ca0ccc5 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -8,7 +8,7 @@ import corsMiddleware from 'cors' import chalk from 'chalk' import { AddressInfo } from 'net' import chokidar from 'chokidar' -import { resolveHttpsConfig, resolveHttpServer } from './http' +import { resolveHttpsConfig, resolveHttpServer, httpServerStart } from './http' import { resolveConfig, InlineConfig, ResolvedConfig } from '../config' import { createPluginContainer, @@ -587,85 +587,67 @@ async function startServer( } const options = server.config.server - let port = inlinePort || options.port || 3000 + const port = inlinePort || options.port || 3000 const hostname = resolveHostname(options.host) const protocol = options.https ? 'https' : 'http' const info = server.config.logger.info const base = server.config.base - return new Promise((resolve, reject) => { - const onError = (e: Error & { code?: string }) => { - if (e.code === 'EADDRINUSE') { - if (options.strictPort) { - httpServer.removeListener('error', onError) - reject(new Error(`Port ${port} is already in use`)) - } else { - info(`Port ${port} is in use, trying another one...`) - httpServer.listen(++port, hostname.host) - } - } else { - httpServer.removeListener('error', onError) - reject(e) - } - } + const serverPort = await httpServerStart(httpServer, { + port, + strictPort: options.strictPort, + host: hostname.host, + logger: server.config.logger + }) - httpServer.on('error', onError) + info( + chalk.cyan(`\n vite v${require('vite/package.json').version}`) + + chalk.green(` dev server running at:\n`), + { + clear: !server.config.logger.hasWarned + } + ) - httpServer.listen(port, hostname.host, () => { - httpServer.removeListener('error', onError) + printServerUrls(hostname, protocol, serverPort, base, info) - info( - chalk.cyan(`\n vite v${require('vite/package.json').version}`) + - chalk.green(` dev server running at:\n`), - { - clear: !server.config.logger.hasWarned - } + // @ts-ignore + if (global.__vite_start_time) { + info( + chalk.cyan( + // @ts-ignore + `\n ready in ${Date.now() - global.__vite_start_time}ms.\n` ) + ) + } - printServerUrls(hostname, protocol, port, base, info) - - // @ts-ignore - if (global.__vite_start_time) { + // @ts-ignore + const profileSession = global.__vite_profile_session + if (profileSession) { + profileSession.post('Profiler.stop', (err: any, { profile }: any) => { + // Write profile to disk, upload, etc. + if (!err) { + const outPath = path.resolve('./vite-profile.cpuprofile') + fs.writeFileSync(outPath, JSON.stringify(profile)) info( - chalk.cyan( - // @ts-ignore - `\n ready in ${Date.now() - global.__vite_start_time}ms.\n` - ) + chalk.yellow(` CPU profile written to ${chalk.white.dim(outPath)}\n`) ) + } else { + throw err } + }) + } - // @ts-ignore - const profileSession = global.__vite_profile_session - if (profileSession) { - profileSession.post('Profiler.stop', (err: any, { profile }: any) => { - // Write profile to disk, upload, etc. - if (!err) { - const outPath = path.resolve('./vite-profile.cpuprofile') - fs.writeFileSync(outPath, JSON.stringify(profile)) - info( - chalk.yellow( - ` CPU profile written to ${chalk.white.dim(outPath)}\n` - ) - ) - } else { - throw err - } - }) - } - - if (options.open && !isRestart) { - const path = typeof options.open === 'string' ? options.open : base - openBrowser( - `${protocol}://${hostname.name}:${port}${path}`, - true, - server.config.logger - ) - } + if (options.open && !isRestart) { + const path = typeof options.open === 'string' ? options.open : base + openBrowser( + `${protocol}://${hostname.name}:${serverPort}${path}`, + true, + server.config.logger + ) + } - resolve(server) - }) - }) + return server } function createServerCloseFn(server: http.Server | null) {