Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: print resolved address for localhost #8647

Merged
42 changes: 27 additions & 15 deletions packages/vite/src/node/__tests__/utils.spec.ts
@@ -1,6 +1,7 @@
import { describe, expect, test } from 'vitest'
import {
getHash,
getLocalhostAddressIfDiffersFromDNS,
getPotentialTsSrcPaths,
injectQuery,
isWindows,
Expand Down Expand Up @@ -49,38 +50,49 @@ describe('injectQuery', () => {
})

describe('resolveHostname', () => {
test('defaults to localhost', () => {
expect(resolveHostname(undefined)).toEqual({
test('defaults to localhost', async () => {
const resolved = await getLocalhostAddressIfDiffersFromDNS()

expect(await resolveHostname(undefined)).toEqual({
host: 'localhost',
name: 'localhost'
name: resolved ?? 'localhost',
implicit: true
})
})

test('accepts localhost', () => {
expect(resolveHostname('localhost')).toEqual({
test('accepts localhost', async () => {
const resolved = await getLocalhostAddressIfDiffersFromDNS()

expect(await resolveHostname('localhost')).toEqual({
host: 'localhost',
name: 'localhost'
name: resolved ?? 'localhost',
implicit: false
})
})

test('accepts 0.0.0.0', () => {
expect(resolveHostname('0.0.0.0')).toEqual({
test('accepts 0.0.0.0', async () => {
expect(await resolveHostname('0.0.0.0')).toEqual({
host: '0.0.0.0',
name: 'localhost'
name: 'localhost',
implicit: false
})
})

test('accepts ::', () => {
expect(resolveHostname('::')).toEqual({
test('accepts ::', async () => {
expect(await resolveHostname('::')).toEqual({
host: '::',
name: 'localhost'
name: 'localhost',
implicit: false
})
})

test('accepts 0000:0000:0000:0000:0000:0000:0000:0000', () => {
expect(resolveHostname('0000:0000:0000:0000:0000:0000:0000:0000')).toEqual({
test('accepts 0000:0000:0000:0000:0000:0000:0000:0000', async () => {
expect(
await resolveHostname('0000:0000:0000:0000:0000:0000:0000:0000')
).toEqual({
host: '0000:0000:0000:0000:0000:0000:0000:0000',
name: 'localhost'
name: 'localhost',
implicit: false
})
})
})
Expand Down
8 changes: 4 additions & 4 deletions packages/vite/src/node/logger.ts
Expand Up @@ -145,15 +145,15 @@ export function createLogger(
return logger
}

export function printCommonServerUrls(
export async function printCommonServerUrls(
server: Server,
options: CommonServerOptions,
config: ResolvedConfig
): void {
): Promise<void> {
const address = server.address()
const isAddressInfo = (x: any): x is AddressInfo => x?.address
if (isAddressInfo(address)) {
const hostname = resolveHostname(options.host)
const hostname = await resolveHostname(options.host)
const protocol = options.https ? 'https' : 'http'
printServerUrls(
hostname,
Expand Down Expand Up @@ -191,7 +191,7 @@ function printServerUrls(
)
})

if (hostname.name === 'localhost') {
if (hostname.implicit) {
notes.push({
label: 'Hint',
message: colors.dim(
Expand Down
8 changes: 4 additions & 4 deletions packages/vite/src/node/preview.ts
Expand Up @@ -50,7 +50,7 @@ export interface PreviewServer {
/**
* Print server urls
*/
printUrls: () => void
printUrls: () => Promise<void>
}

export type PreviewServerHook = (server: {
Expand Down Expand Up @@ -112,7 +112,7 @@ export async function preview(
postHooks.forEach((fn) => fn && fn())

const options = config.preview
const hostname = resolveHostname(options.host)
const hostname = await resolveHostname(options.host)
const port = options.port ?? 4173
const protocol = options.https ? 'https' : 'http'
const logger = config.logger
Expand All @@ -139,8 +139,8 @@ export async function preview(
return {
config,
httpServer,
printUrls() {
printCommonServerUrls(httpServer, config.preview, config)
async printUrls() {
await printCommonServerUrls(httpServer, config.preview, config)
}
}
}
8 changes: 4 additions & 4 deletions packages/vite/src/node/server/index.ts
Expand Up @@ -222,7 +222,7 @@ export interface ViteDevServer {
/**
* Print server urls
*/
printUrls(): void
printUrls(): Promise<void>
/**
* Restart the server.
*
Expand Down Expand Up @@ -355,9 +355,9 @@ export async function createServer(
closeHttpServer()
])
},
printUrls() {
async printUrls() {
if (httpServer) {
printCommonServerUrls(httpServer, config.server, config)
await printCommonServerUrls(httpServer, config.server, config)
} else {
throw new Error('cannot print server URLs in middleware mode.')
}
Expand Down Expand Up @@ -561,7 +561,7 @@ async function startServer(

const options = server.config.server
const port = inlinePort ?? options.port ?? 5173
const hostname = resolveHostname(options.host)
const hostname = await resolveHostname(options.host)

const protocol = options.https ? 'https' : 'http'
const info = server.config.logger.info
Expand Down
44 changes: 37 additions & 7 deletions packages/vite/src/node/utils.ts
Expand Up @@ -6,6 +6,7 @@ import { promisify } from 'util'
import { URL, URLSearchParams, pathToFileURL } from 'url'
import { builtinModules, createRequire } from 'module'
import { performance } from 'perf_hooks'
import { promises as dns } from 'dns'
import resolve from 'resolve'
import type { FSWatcher } from 'chokidar'
import remapping from '@ampproject/remapping'
Expand Down Expand Up @@ -735,16 +736,38 @@ export function unique<T>(arr: T[]): T[] {
return Array.from(new Set(arr))
}

/**
* Returns resolved localhost address when `dns.lookup` result differs from DNS
*
* `dns.lookup` result is same when defaultResultOrder is `verbatim`.
* Even if defaultResultOrder is `ipv4first`, `dns.lookup` result maybe same.
* For example, when IPv6 is not supported on that machine/network.
*/
export async function getLocalhostAddressIfDiffersFromDNS(): Promise<
string | undefined
> {
const [nodeResult, dnsResult] = await Promise.all([
dns.lookup('localhost'),
dns.lookup('localhost', { verbatim: true })
])
const isSame =
nodeResult.family === dnsResult.family &&
nodeResult.address === dnsResult.address
return isSame ? undefined : nodeResult.address
}

export interface Hostname {
// undefined sets the default behaviour of server.listen
/** undefined sets the default behaviour of server.listen */
host: string | undefined
// resolve to localhost when possible
/** resolve to localhost when possible */
name: string
/** if it is using the default behavior */
implicit: boolean
}

export function resolveHostname(
export async function resolveHostname(
optionsHost: string | boolean | undefined
): Hostname {
): Promise<Hostname> {
let host: string | undefined
if (optionsHost === undefined || optionsHost === false) {
// Use a secure default
Expand All @@ -757,10 +780,17 @@ export function resolveHostname(
}

// Set host name to localhost when possible
const name =
host === undefined || wildcardHosts.has(host) ? 'localhost' : host
let name = host === undefined || wildcardHosts.has(host) ? 'localhost' : host

if (host === 'localhost') {
// See #8647 for more details.
const localhostAddr = await getLocalhostAddressIfDiffersFromDNS()
if (localhostAddr) {
name = localhostAddr
}
}

return { host, name }
return { host, name, implicit: optionsHost === undefined }
bluwy marked this conversation as resolved.
Show resolved Hide resolved
}

export function arraify<T>(target: T | T[]): T[] {
Expand Down
4 changes: 3 additions & 1 deletion playground/cli-module/__tests__/serve.ts
Expand Up @@ -119,7 +119,9 @@ async function startedOnPort(serverProcess, port, timeout) {
const str = data.toString()
// hack, console output may contain color code gibberish
// skip gibberish between localhost: and port number
const match = str.match(/(http:\/\/localhost:)(?:.*)(\d{4})/)
const match = str.match(
/(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):)(?:.*)(\d{4})/
)
if (match) {
const startedPort = parseInt(match[2], 10)
if (startedPort === port) {
Expand Down
4 changes: 3 additions & 1 deletion playground/cli/__tests__/serve.ts
Expand Up @@ -119,7 +119,9 @@ async function startedOnPort(serverProcess, port, timeout) {
const str = data.toString()
// hack, console output may contain color code gibberish
// skip gibberish between localhost: and port number
const match = str.match(/(http:\/\/localhost:)(?:.*)(\d{4})/)
const match = str.match(
/(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):)(?:.*)(\d{4})/
)
if (match) {
const startedPort = parseInt(match[2], 10)
if (startedPort === port) {
Expand Down