From af4417f93e831f5947c85dfa075ef022315eca9a Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 8 Jul 2022 13:06:32 +0800 Subject: [PATCH 1/7] feat: expose server resolved urls --- docs/guide/migration.md | 2 - .../vite/src/node/__tests__/utils.spec.ts | 15 +-- packages/vite/src/node/logger.ts | 109 +++--------------- packages/vite/src/node/preview.ts | 23 +++- packages/vite/src/node/server/index.ts | 45 ++++++-- packages/vite/src/node/utils.ts | 63 +++++++++- 6 files changed, 130 insertions(+), 127 deletions(-) diff --git a/docs/guide/migration.md b/docs/guide/migration.md index 3153c856ce2e61..faefbdce9897d1 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -144,8 +144,6 @@ Also there are other breaking changes which only affect few users. - `server.force` option was removed in favor of `optimizeDeps.force` option. - [[#8550] fix: dont handle sigterm in middleware mode](https://github.com/vitejs/vite/pull/8550) - When running in middleware mode, Vite no longer kills process on `SIGTERM`. -- [[#8647] feat: print resolved address for localhost](https://github.com/vitejs/vite/pull/8647) - - `server.printUrls` and `previewServer.printUrls` are now async ## Migration from v1 diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index a4ffd2b7c917be..51990edf709da2 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -56,8 +56,7 @@ describe('resolveHostname', () => { expect(await resolveHostname(undefined)).toEqual({ host: 'localhost', - name: resolved ?? 'localhost', - implicit: true + name: resolved ?? 'localhost' }) }) @@ -66,24 +65,21 @@ describe('resolveHostname', () => { expect(await resolveHostname('localhost')).toEqual({ host: 'localhost', - name: resolved ?? 'localhost', - implicit: false + name: resolved ?? 'localhost' }) }) test('accepts 0.0.0.0', async () => { expect(await resolveHostname('0.0.0.0')).toEqual({ host: '0.0.0.0', - name: 'localhost', - implicit: false + name: 'localhost' }) }) test('accepts ::', async () => { expect(await resolveHostname('::')).toEqual({ host: '::', - name: 'localhost', - implicit: false + name: 'localhost' }) }) @@ -92,8 +88,7 @@ describe('resolveHostname', () => { await resolveHostname('0000:0000:0000:0000:0000:0000:0000:0000') ).toEqual({ host: '0000:0000:0000:0000:0000:0000:0000:0000', - name: 'localhost', - implicit: false + name: 'localhost' }) }) }) diff --git a/packages/vite/src/node/logger.ts b/packages/vite/src/node/logger.ts index 597de2358f43e3..d0c5d29334c023 100644 --- a/packages/vite/src/node/logger.ts +++ b/packages/vite/src/node/logger.ts @@ -1,15 +1,9 @@ /* eslint no-console: 0 */ -import type { AddressInfo, Server } from 'node:net' -import os from 'node:os' import readline from 'readline' import colors from 'picocolors' import type { RollupError } from 'rollup' -import type { CommonServerOptions } from './http' -import type { Hostname } from './utils' -import { resolveHostname } from './utils' -import { loopbackHosts } from './constants' -import type { ResolvedConfig } from '.' +import type { ResolvedServerUrls } from './server' export type LogType = 'error' | 'warn' | 'info' export type LogLevel = LogType | 'silent' @@ -145,94 +139,23 @@ export function createLogger( return logger } -export async function printCommonServerUrls( - server: Server, - options: CommonServerOptions, - config: ResolvedConfig -): Promise { - const address = server.address() - const isAddressInfo = (x: any): x is AddressInfo => x?.address - if (isAddressInfo(address)) { - const hostname = await resolveHostname(options.host) - const protocol = options.https ? 'https' : 'http' - const base = config.base === './' || config.base === '' ? '/' : config.base - printServerUrls(hostname, protocol, address.port, base, config.logger.info) - } -} - -function printServerUrls( - hostname: Hostname, - protocol: string, - port: number, - base: string, +export function printServerUrls( + urls: ResolvedServerUrls, + optionsHost: string | boolean | undefined, info: Logger['info'] ): void { - const urls: Array<{ label: string; url: string; disabled?: boolean }> = [] - const notes: Array<{ label: string; message: string }> = [] - - if (hostname.host && loopbackHosts.has(hostname.host)) { - let hostnameName = hostname.name - if ( - hostnameName === '::1' || - hostnameName === '0000:0000:0000:0000:0000:0000:0000:0001' - ) { - hostnameName = `[${hostnameName}]` - } - - urls.push({ - label: 'Local', - url: colors.cyan( - `${protocol}://${hostnameName}:${colors.bold(port)}${base}` - ) - }) - - if (hostname.implicit) { - urls.push({ - label: 'Network', - url: `use ${colors.white(colors.bold('--host'))} to expose`, - disabled: true - }) - } - } else { - Object.values(os.networkInterfaces()) - .flatMap((nInterface) => nInterface ?? []) - .filter( - (detail) => - detail && - detail.address && - // Node < v18 - ((typeof detail.family === 'string' && detail.family === 'IPv4') || - // Node >= v18 - (typeof detail.family === 'number' && detail.family === 4)) - ) - .forEach((detail) => { - const host = detail.address.replace('127.0.0.1', hostname.name) - const url = `${protocol}://${host}:${colors.bold(port)}${base}` - const label = detail.address.includes('127.0.0.1') ? 'Local' : 'Network' - - urls.push({ label, url: colors.cyan(url) }) - }) + const colorUrl = (url: string) => + colors.cyan(url.replace(/:(\d+)\//, (_, port) => `:${colors.bold(port)}/`)) + for (const url of urls.local) { + info(` ${colors.green('➜')} ${colors.bold('Local')}: ${colorUrl(url)}`) } - - const length = Math.max( - ...[...urls, ...notes].map(({ label }) => label.length) - ) - const print = ( - iconWithColor: string, - label: string, - messageWithColor: string, - disabled?: boolean - ) => { - const message = ` ${iconWithColor} ${ - label ? colors.bold(label) + ':' : ' ' - } ${' '.repeat(length - label.length)}${messageWithColor}` - info(disabled ? colors.dim(message) : message) + for (const url of urls.network) { + info(` ${colors.green('➜')} ${colors.bold('Network')}: ${colorUrl(url)}`) + } + if (urls.network.length === 0 && optionsHost === undefined) { + const note = `use ${colors.white(colors.bold('--host'))} to expose` + info( + colors.dim(` ${colors.green('➜')} ${colors.bold('Network')}: ${note}`) + ) } - - urls.forEach(({ label, url: text, disabled }) => { - print(colors.green('➜'), label, text, disabled) - }) - notes.forEach(({ label, message: text }) => { - print(colors.white('❖'), label, text) - }) } diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index 5820a3bc6cdb91..ad746be0994786 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -4,14 +4,14 @@ import sirv from 'sirv' import connect from 'connect' import type { Connect } from 'types/connect' import corsMiddleware from 'cors' -import type { ResolvedServerOptions } from './server' +import type { ResolvedServerOptions, ResolvedServerUrls } from './server' import type { CommonServerOptions } from './http' import { httpServerStart, resolveHttpServer, resolveHttpsConfig } from './http' import { openBrowser } from './server/openBrowser' import compression from './server/middlewares/compression' import { proxyMiddleware } from './server/middlewares/proxy' -import { resolveHostname } from './utils' -import { printCommonServerUrls } from './logger' +import { resolveHostname, resolveServerUrls } from './utils' +import { printServerUrls } from './logger' import { resolveConfig } from '.' import type { InlineConfig, ResolvedConfig } from '.' @@ -47,10 +47,14 @@ export interface PreviewServer { * native Node http server instance */ httpServer: http.Server + /** + * The resolved urls Vite prints on the CLI + */ + resolvedUrls: ResolvedServerUrls /** * Print server urls */ - printUrls: () => Promise + printUrls(): void } export type PreviewServerHook = (server: { @@ -127,6 +131,12 @@ export async function preview( logger }) + const resolvedUrls = await resolveServerUrls( + httpServer, + config.preview, + config + ) + if (options.open) { const path = typeof options.open === 'string' ? options.open : previewBase openBrowser( @@ -141,8 +151,9 @@ export async function preview( return { config, httpServer, - async printUrls() { - await printCommonServerUrls(httpServer, config.preview, config) + resolvedUrls, + printUrls() { + printServerUrls(resolvedUrls, options.host, logger.info) } } } diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index d270c92bfd0e74..021a5f1d91569a 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -19,7 +19,8 @@ import { isParentDirectory, mergeConfig, normalizePath, - resolveHostname + resolveHostname, + resolveServerUrls } from '../utils' import { ssrLoadModule } from '../ssr/ssrModuleLoader' import { cjsSsrResolveExternals } from '../ssr/ssrExternal' @@ -34,8 +35,8 @@ import { initDevSsrDepsOptimizer } from '../optimizer' import { CLIENT_DIR } from '../constants' -import type { Logger } from '../logger' -import { printCommonServerUrls } from '../logger' +import type { Logger} from '../logger'; +import { printServerUrls } from '../logger' import { invalidatePackageData } from '../packages' import type { PluginContainer } from './pluginContainer' import { createPluginContainer } from './pluginContainer' @@ -184,6 +185,10 @@ export interface ViteDevServer { * and hmr state. */ moduleGraph: ModuleGraph + /** + * The resolved urls Vite prints on the CLI. null in middleware mode. + */ + resolvedUrls: ResolvedServerUrls | null /** * Programmatically resolve, load and transform a URL and get the result * without going through the http request pipeline. @@ -234,7 +239,7 @@ export interface ViteDevServer { /** * Print server urls */ - printUrls(): Promise + printUrls(): void /** * Restart the server. * @@ -271,6 +276,11 @@ export interface ViteDevServer { > } +export interface ResolvedServerUrls { + local: string[] + network: string[] +} + export async function createServer( inlineConfig: InlineConfig = {} ): Promise { @@ -319,6 +329,7 @@ export async function createServer( pluginContainer: container, ws, moduleGraph, + resolvedUrls: null, ssrTransform(code: string, inMap: SourceMap | null, url: string) { return ssrTransform(code, inMap, url, { json: { stringify: server.config.json?.stringify } @@ -350,8 +361,16 @@ export async function createServer( ssrRewriteStacktrace(stack: string) { return ssrRewriteStacktrace(stack, moduleGraph) }, - listen(port?: number, isRestart?: boolean) { - return startServer(server, port, isRestart) + async listen(port?: number, isRestart?: boolean) { + await startServer(server, port, isRestart) + if (httpServer) { + server.resolvedUrls = await resolveServerUrls( + httpServer, + config.server, + config + ) + } + return server }, async close() { if (!middlewareMode) { @@ -368,9 +387,13 @@ export async function createServer( closeHttpServer() ]) }, - async printUrls() { - if (httpServer) { - await printCommonServerUrls(httpServer, config.server, config) + printUrls() { + if (server.resolvedUrls) { + printServerUrls( + server.resolvedUrls, + serverConfig.host, + config.logger.info + ) } else { throw new Error('cannot print server URLs in middleware mode.') } @@ -572,7 +595,7 @@ async function startServer( server: ViteDevServer, inlinePort?: number, isRestart: boolean = false -): Promise { +): Promise { const httpServer = server.httpServer if (!httpServer) { throw new Error('Cannot call server.listen in middleware mode.') @@ -622,8 +645,6 @@ async function startServer( server.config.logger ) } - - return server } function createServerCloseFn(server: http.Server | null) { diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 0b0c205d80b9e4..1d647e366a3be2 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -7,6 +7,7 @@ import { URL, URLSearchParams, pathToFileURL } from 'node:url' import { builtinModules, createRequire } from 'node:module' import { promises as dns } from 'node:dns' import { performance } from 'node:perf_hooks' +import type { AddressInfo, Server } from 'node:net' import resolve from 'resolve' import type { FSWatcher } from 'chokidar' import remapping from '@ampproject/remapping' @@ -26,10 +27,13 @@ import { FS_PREFIX, OPTIMIZABLE_ENTRY_RE, VALID_ID_PREFIX, + loopbackHosts, wildcardHosts } from './constants' import type { DepOptimizationConfig } from './optimizer' -import type { ResolvedConfig } from '.' +import type { ResolvedConfig } from './config' +import type { ResolvedServerUrls } from './server' +import type { CommonServerOptions } from '.' /** * Inlined to keep `@rollup/pluginutils` in devDependencies @@ -770,8 +774,6 @@ export interface Hostname { host: string | undefined /** resolve to localhost when possible */ name: string - /** if it is using the default behavior */ - implicit: boolean } export async function resolveHostname( @@ -799,7 +801,60 @@ export async function resolveHostname( } } - return { host, name, implicit: optionsHost === undefined } + return { host, name } +} + +export async function resolveServerUrls( + server: Server, + options: CommonServerOptions, + config: ResolvedConfig +): Promise { + const address = server.address() + + const isAddressInfo = (x: any): x is AddressInfo => x?.address + if (!isAddressInfo(address)) { + return { local: [], network: [] } + } + + const local: string[] = [] + const network: string[] = [] + const hostname = await resolveHostname(options.host) + const protocol = options.https ? 'https' : 'http' + const port = address.port + const base = config.base === './' || config.base === '' ? '/' : config.base + + if (hostname.host && loopbackHosts.has(hostname.host)) { + let hostnameName = hostname.name + if ( + hostnameName === '::1' || + hostnameName === '0000:0000:0000:0000:0000:0000:0000:0001' + ) { + hostnameName = `[${hostnameName}]` + } + local.push(`${protocol}://${hostnameName}:${port}${base}`) + } else { + Object.values(os.networkInterfaces()) + .flatMap((nInterface) => nInterface ?? []) + .filter( + (detail) => + detail && + detail.address && + // Node < v18 + ((typeof detail.family === 'string' && detail.family === 'IPv4') || + // Node >= v18 + (typeof detail.family === 'number' && detail.family === 4)) + ) + .forEach((detail) => { + const host = detail.address.replace('127.0.0.1', hostname.name) + const url = `${protocol}://${host}:${port}${base}` + if (detail.address.includes('127.0.0.1')) { + local.push(url) + } else { + network.push(url) + } + }) + } + return { local, network } } export function arraify(target: T | T[]): T[] { From 2f84ded90564fca4f62739bd7166b0c908f2ec25 Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 8 Jul 2022 13:36:46 +0800 Subject: [PATCH 2/7] fix: build --- packages/vite/src/node/index.ts | 3 ++- packages/vite/src/node/server/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index bdac099dd214e6..7dfada6825780b 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -17,7 +17,8 @@ export type { ServerOptions, FileSystemServeOptions, ServerHook, - ResolvedServerOptions + ResolvedServerOptions, + ResolvedServerUrls } from './server' export type { BuildOptions, diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 021a5f1d91569a..1bec5b6a7680e4 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -35,7 +35,7 @@ import { initDevSsrDepsOptimizer } from '../optimizer' import { CLIENT_DIR } from '../constants' -import type { Logger} from '../logger'; +import type { Logger } from '../logger' import { printServerUrls } from '../logger' import { invalidatePackageData } from '../packages' import type { PluginContainer } from './pluginContainer' @@ -329,7 +329,7 @@ export async function createServer( pluginContainer: container, ws, moduleGraph, - resolvedUrls: null, + resolvedUrls: null, // will be set on listen ssrTransform(code: string, inMap: SourceMap | null, url: string) { return ssrTransform(code, inMap, url, { json: { stringify: server.config.json?.stringify } From ca397aab77605fc10a8156cee1191acc9feb6a87 Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 8 Jul 2022 15:40:48 +0800 Subject: [PATCH 3/7] docs: update --- docs/guide/api-javascript.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/guide/api-javascript.md b/docs/guide/api-javascript.md index 33202e0d608238..11a1e867ab95b8 100644 --- a/docs/guide/api-javascript.md +++ b/docs/guide/api-javascript.md @@ -81,6 +81,10 @@ interface ViteDevServer { * and hmr state. */ moduleGraph: ModuleGraph + /** + * The resolved urls Vite prints on the CLI. null in middleware mode. + */ + resolvedUrls: ResolvedServerUrls | null /** * Programmatically resolve, load and transform a URL and get the result * without going through the http request pipeline. From 67637da91bd2f4eb0ae7f725396c58e7a810c639 Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 8 Jul 2022 21:39:35 +0800 Subject: [PATCH 4/7] chore: improve error message --- docs/guide/api-javascript.md | 3 ++- packages/vite/src/node/server/index.ts | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/guide/api-javascript.md b/docs/guide/api-javascript.md index ccbd4756a564f7..bebe594a8ffd5b 100644 --- a/docs/guide/api-javascript.md +++ b/docs/guide/api-javascript.md @@ -85,7 +85,8 @@ interface ViteDevServer { */ moduleGraph: ModuleGraph /** - * The resolved urls Vite prints on the CLI. null in middleware mode. + * The resolved urls Vite prints on the CLI. null in middleware mode or + * before `server.listen` is called. */ resolvedUrls: ResolvedServerUrls | null /** diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 1bec5b6a7680e4..fdb4bd827c56d3 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -186,7 +186,8 @@ export interface ViteDevServer { */ moduleGraph: ModuleGraph /** - * The resolved urls Vite prints on the CLI. null in middleware mode. + * The resolved urls Vite prints on the CLI. null in middleware mode or + * before `server.listen` is called. */ resolvedUrls: ResolvedServerUrls | null /** @@ -394,8 +395,12 @@ export async function createServer( serverConfig.host, config.logger.info ) - } else { + } else if (middlewareMode) { throw new Error('cannot print server URLs in middleware mode.') + } else { + throw new Error( + 'cannot print server URLs before server.listen is called' + ) } }, async restart(forceOptimize?: boolean) { From d0bb04f85447f45a29672de674d4a7ce3a00ebc5 Mon Sep 17 00:00:00 2001 From: bluwy Date: Fri, 8 Jul 2022 21:44:59 +0800 Subject: [PATCH 5/7] chore: full stop --- packages/vite/src/node/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index fdb4bd827c56d3..faec9ccdb9c93e 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -399,7 +399,7 @@ export async function createServer( throw new Error('cannot print server URLs in middleware mode.') } else { throw new Error( - 'cannot print server URLs before server.listen is called' + 'cannot print server URLs before server.listen is called.' ) } }, From a087e534bf04b546de5646e1d69ab1b0eaa2c90c Mon Sep 17 00:00:00 2001 From: bluwy Date: Mon, 11 Jul 2022 12:30:25 +0800 Subject: [PATCH 6/7] fix: reset resolvedUrls when server close --- packages/vite/src/node/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index faec9ccdb9c93e..13875066bf8db7 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -380,13 +380,13 @@ export async function createServer( process.stdin.off('end', exitProcess) } } - await Promise.all([ watcher.close(), ws.close(), container.close(), closeHttpServer() ]) + server.resolvedUrls = null }, printUrls() { if (server.resolvedUrls) { From bed92b5beddae33e7b445ed9d3a3f8989b727900 Mon Sep 17 00:00:00 2001 From: bluwy Date: Tue, 12 Jul 2022 00:53:55 +0800 Subject: [PATCH 7/7] chore: mark experimental --- docs/guide/api-javascript.md | 5 ----- packages/vite/src/node/preview.ts | 4 +++- packages/vite/src/node/server/index.ts | 2 ++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/guide/api-javascript.md b/docs/guide/api-javascript.md index bebe594a8ffd5b..6167bd7dfe7793 100644 --- a/docs/guide/api-javascript.md +++ b/docs/guide/api-javascript.md @@ -84,11 +84,6 @@ interface ViteDevServer { * and hmr state. */ moduleGraph: ModuleGraph - /** - * The resolved urls Vite prints on the CLI. null in middleware mode or - * before `server.listen` is called. - */ - resolvedUrls: ResolvedServerUrls | null /** * Programmatically resolve, load and transform a URL and get the result * without going through the http request pipeline. diff --git a/packages/vite/src/node/preview.ts b/packages/vite/src/node/preview.ts index ad746be0994786..7b2cc4bb1729b4 100644 --- a/packages/vite/src/node/preview.ts +++ b/packages/vite/src/node/preview.ts @@ -48,7 +48,9 @@ export interface PreviewServer { */ httpServer: http.Server /** - * The resolved urls Vite prints on the CLI + * The resolved urls Vite prints on the + * + * @experimental */ resolvedUrls: ResolvedServerUrls /** diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 13875066bf8db7..80c1ae308db6ca 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -188,6 +188,8 @@ export interface ViteDevServer { /** * The resolved urls Vite prints on the CLI. null in middleware mode or * before `server.listen` is called. + * + * @experimental */ resolvedUrls: ResolvedServerUrls | null /**