Skip to content

Commit eb52d36

Browse files
sapphi-redbluwy
andauthoredJun 20, 2022
feat: print resolved address for localhost (#8647)
Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
1 parent 60721ac commit eb52d36

File tree

9 files changed

+85
-37
lines changed

9 files changed

+85
-37
lines changed
 

‎docs/config/server-options.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This can be set via the CLI using `--host 0.0.0.0` or `--host`.
1414

1515
There are cases when other servers might respond instead of Vite.
1616

17-
The first case is when `localhost` is used. Node.js below v17 reorders the result of DNS-resolved address by default. When accessing `localhost`, browsers use DNS to resolve the address and that address might differ from the address which Vite is listening.
17+
The first case is when `localhost` is used. Node.js below v17 reorders the result of DNS-resolved address by default. When accessing `localhost`, browsers use DNS to resolve the address and that address might differ from the address which Vite is listening. Vite prints the resolved address when it differs.
1818

1919
You could set [`dns.setDefaultResultOrder('verbatim')`](https://nodejs.org/api/dns.html#dns_dns_setdefaultresultorder_order) to disable the reordering behavior. Or you could set `server.host` to `127.0.0.1` explicitly.
2020

‎docs/guide/migration.md

+2
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ Also there are other breaking changes which only affect few users.
134134
- `server.force` option was removed in favor of `optimizeDeps.force` option.
135135
- [[#8550] fix: dont handle sigterm in middleware mode](https://github.com/vitejs/vite/pull/8550)
136136
- When running in middleware mode, Vite no longer kills process on `SIGTERM`.
137+
- [[#8647] feat: print resolved address for localhost](https://github.com/vitejs/vite/pull/8647)
138+
- `server.printUrls` and `previewServer.printUrls` are now async
137139

138140
## Migration from v1
139141

‎packages/vite/src/node/__tests__/utils.spec.ts

+27-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest'
22
import {
33
asyncFlatten,
44
getHash,
5+
getLocalhostAddressIfDiffersFromDNS,
56
getPotentialTsSrcPaths,
67
injectQuery,
78
isWindows,
@@ -50,38 +51,49 @@ describe('injectQuery', () => {
5051
})
5152

5253
describe('resolveHostname', () => {
53-
test('defaults to localhost', () => {
54-
expect(resolveHostname(undefined)).toEqual({
54+
test('defaults to localhost', async () => {
55+
const resolved = await getLocalhostAddressIfDiffersFromDNS()
56+
57+
expect(await resolveHostname(undefined)).toEqual({
5558
host: 'localhost',
56-
name: 'localhost'
59+
name: resolved ?? 'localhost',
60+
implicit: true
5761
})
5862
})
5963

60-
test('accepts localhost', () => {
61-
expect(resolveHostname('localhost')).toEqual({
64+
test('accepts localhost', async () => {
65+
const resolved = await getLocalhostAddressIfDiffersFromDNS()
66+
67+
expect(await resolveHostname('localhost')).toEqual({
6268
host: 'localhost',
63-
name: 'localhost'
69+
name: resolved ?? 'localhost',
70+
implicit: false
6471
})
6572
})
6673

67-
test('accepts 0.0.0.0', () => {
68-
expect(resolveHostname('0.0.0.0')).toEqual({
74+
test('accepts 0.0.0.0', async () => {
75+
expect(await resolveHostname('0.0.0.0')).toEqual({
6976
host: '0.0.0.0',
70-
name: 'localhost'
77+
name: 'localhost',
78+
implicit: false
7179
})
7280
})
7381

74-
test('accepts ::', () => {
75-
expect(resolveHostname('::')).toEqual({
82+
test('accepts ::', async () => {
83+
expect(await resolveHostname('::')).toEqual({
7684
host: '::',
77-
name: 'localhost'
85+
name: 'localhost',
86+
implicit: false
7887
})
7988
})
8089

81-
test('accepts 0000:0000:0000:0000:0000:0000:0000:0000', () => {
82-
expect(resolveHostname('0000:0000:0000:0000:0000:0000:0000:0000')).toEqual({
90+
test('accepts 0000:0000:0000:0000:0000:0000:0000:0000', async () => {
91+
expect(
92+
await resolveHostname('0000:0000:0000:0000:0000:0000:0000:0000')
93+
).toEqual({
8394
host: '0000:0000:0000:0000:0000:0000:0000:0000',
84-
name: 'localhost'
95+
name: 'localhost',
96+
implicit: false
8597
})
8698
})
8799
})

‎packages/vite/src/node/logger.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,15 @@ export function createLogger(
145145
return logger
146146
}
147147

148-
export function printCommonServerUrls(
148+
export async function printCommonServerUrls(
149149
server: Server,
150150
options: CommonServerOptions,
151151
config: ResolvedConfig
152-
): void {
152+
): Promise<void> {
153153
const address = server.address()
154154
const isAddressInfo = (x: any): x is AddressInfo => x?.address
155155
if (isAddressInfo(address)) {
156-
const hostname = resolveHostname(options.host)
156+
const hostname = await resolveHostname(options.host)
157157
const protocol = options.https ? 'https' : 'http'
158158
printServerUrls(
159159
hostname,
@@ -191,7 +191,7 @@ function printServerUrls(
191191
)
192192
})
193193

194-
if (hostname.name === 'localhost') {
194+
if (hostname.implicit) {
195195
urls.push({
196196
label: 'Network',
197197
url: `use ${colors.white(colors.bold('--host'))} to expose`,

‎packages/vite/src/node/preview.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export interface PreviewServer {
5050
/**
5151
* Print server urls
5252
*/
53-
printUrls: () => void
53+
printUrls: () => Promise<void>
5454
}
5555

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

114114
const options = config.preview
115-
const hostname = resolveHostname(options.host)
115+
const hostname = await resolveHostname(options.host)
116116
const port = options.port ?? 4173
117117
const protocol = options.https ? 'https' : 'http'
118118
const logger = config.logger
@@ -139,8 +139,8 @@ export async function preview(
139139
return {
140140
config,
141141
httpServer,
142-
printUrls() {
143-
printCommonServerUrls(httpServer, config.preview, config)
142+
async printUrls() {
143+
await printCommonServerUrls(httpServer, config.preview, config)
144144
}
145145
}
146146
}

‎packages/vite/src/node/server/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ export interface ViteDevServer {
223223
/**
224224
* Print server urls
225225
*/
226-
printUrls(): void
226+
printUrls(): Promise<void>
227227
/**
228228
* Restart the server.
229229
*
@@ -353,9 +353,9 @@ export async function createServer(
353353
closeHttpServer()
354354
])
355355
},
356-
printUrls() {
356+
async printUrls() {
357357
if (httpServer) {
358-
printCommonServerUrls(httpServer, config.server, config)
358+
await printCommonServerUrls(httpServer, config.server, config)
359359
} else {
360360
throw new Error('cannot print server URLs in middleware mode.')
361361
}
@@ -555,7 +555,7 @@ async function startServer(
555555

556556
const options = server.config.server
557557
const port = inlinePort ?? options.port ?? 5173
558-
const hostname = resolveHostname(options.host)
558+
const hostname = await resolveHostname(options.host)
559559

560560
const protocol = options.https ? 'https' : 'http'
561561
const info = server.config.logger.info

‎packages/vite/src/node/utils.ts

+37-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { createHash } from 'node:crypto'
55
import { promisify } from 'node:util'
66
import { URL, URLSearchParams, pathToFileURL } from 'node:url'
77
import { builtinModules, createRequire } from 'node:module'
8+
import { promises as dns } from 'node:dns'
89
import { performance } from 'node:perf_hooks'
910
import resolve from 'resolve'
1011
import type { FSWatcher } from 'chokidar'
@@ -735,16 +736,38 @@ export function unique<T>(arr: T[]): T[] {
735736
return Array.from(new Set(arr))
736737
}
737738

739+
/**
740+
* Returns resolved localhost address when `dns.lookup` result differs from DNS
741+
*
742+
* `dns.lookup` result is same when defaultResultOrder is `verbatim`.
743+
* Even if defaultResultOrder is `ipv4first`, `dns.lookup` result maybe same.
744+
* For example, when IPv6 is not supported on that machine/network.
745+
*/
746+
export async function getLocalhostAddressIfDiffersFromDNS(): Promise<
747+
string | undefined
748+
> {
749+
const [nodeResult, dnsResult] = await Promise.all([
750+
dns.lookup('localhost'),
751+
dns.lookup('localhost', { verbatim: true })
752+
])
753+
const isSame =
754+
nodeResult.family === dnsResult.family &&
755+
nodeResult.address === dnsResult.address
756+
return isSame ? undefined : nodeResult.address
757+
}
758+
738759
export interface Hostname {
739-
// undefined sets the default behaviour of server.listen
760+
/** undefined sets the default behaviour of server.listen */
740761
host: string | undefined
741-
// resolve to localhost when possible
762+
/** resolve to localhost when possible */
742763
name: string
764+
/** if it is using the default behavior */
765+
implicit: boolean
743766
}
744767

745-
export function resolveHostname(
768+
export async function resolveHostname(
746769
optionsHost: string | boolean | undefined
747-
): Hostname {
770+
): Promise<Hostname> {
748771
let host: string | undefined
749772
if (optionsHost === undefined || optionsHost === false) {
750773
// Use a secure default
@@ -757,10 +780,17 @@ export function resolveHostname(
757780
}
758781

759782
// Set host name to localhost when possible
760-
const name =
761-
host === undefined || wildcardHosts.has(host) ? 'localhost' : host
783+
let name = host === undefined || wildcardHosts.has(host) ? 'localhost' : host
784+
785+
if (host === 'localhost') {
786+
// See #8647 for more details.
787+
const localhostAddr = await getLocalhostAddressIfDiffersFromDNS()
788+
if (localhostAddr) {
789+
name = localhostAddr
790+
}
791+
}
762792

763-
return { host, name }
793+
return { host, name, implicit: optionsHost === undefined }
764794
}
765795

766796
export function arraify<T>(target: T | T[]): T[] {

‎playground/cli-module/__tests__/serve.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ async function startedOnPort(serverProcess, port, timeout) {
119119
const str = data.toString()
120120
// hack, console output may contain color code gibberish
121121
// skip gibberish between localhost: and port number
122-
const match = str.match(/(http:\/\/localhost:)(?:.*)(\d{4})/)
122+
const match = str.match(
123+
/(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):)(?:.*)(\d{4})/
124+
)
123125
if (match) {
124126
const startedPort = parseInt(match[2], 10)
125127
if (startedPort === port) {

‎playground/cli/__tests__/serve.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,9 @@ async function startedOnPort(serverProcess, port, timeout) {
119119
const str = data.toString()
120120
// hack, console output may contain color code gibberish
121121
// skip gibberish between localhost: and port number
122-
const match = str.match(/(http:\/\/localhost:)(?:.*)(\d{4})/)
122+
const match = str.match(
123+
/(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):)(?:.*)(\d{4})/
124+
)
123125
if (match) {
124126
const startedPort = parseInt(match[2], 10)
125127
if (startedPort === port) {

0 commit comments

Comments
 (0)
Please sign in to comment.