Skip to content

Commit

Permalink
feat: expose server resolved urls (#8986)
Browse files Browse the repository at this point in the history
  • Loading branch information
bluwy committed Jul 11, 2022
1 parent 519f7de commit 26bcdc3
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 129 deletions.
2 changes: 0 additions & 2 deletions docs/guide/migration.md
Expand Up @@ -139,8 +139,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

Expand Down
15 changes: 5 additions & 10 deletions packages/vite/src/node/__tests__/utils.spec.ts
Expand Up @@ -56,8 +56,7 @@ describe('resolveHostname', () => {

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

Expand All @@ -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'
})
})

Expand All @@ -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'
})
})
})
Expand Down
3 changes: 2 additions & 1 deletion packages/vite/src/node/index.ts
Expand Up @@ -17,7 +17,8 @@ export type {
ServerOptions,
FileSystemServeOptions,
ServerHook,
ResolvedServerOptions
ResolvedServerOptions,
ResolvedServerUrls
} from './server'
export type {
BuildOptions,
Expand Down
109 changes: 16 additions & 93 deletions 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'
Expand Down Expand Up @@ -145,94 +139,23 @@ export function createLogger(
return logger
}

export async function printCommonServerUrls(
server: Server,
options: CommonServerOptions,
config: ResolvedConfig
): Promise<void> {
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)
})
}
25 changes: 19 additions & 6 deletions packages/vite/src/node/preview.ts
Expand Up @@ -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 '.'

Expand Down Expand Up @@ -47,10 +47,16 @@ export interface PreviewServer {
* native Node http server instance
*/
httpServer: http.Server
/**
* The resolved urls Vite prints on the
*
* @experimental
*/
resolvedUrls: ResolvedServerUrls
/**
* Print server urls
*/
printUrls: () => Promise<void>
printUrls(): void
}

export type PreviewServerHook = (server: {
Expand Down Expand Up @@ -127,6 +133,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(
Expand All @@ -141,8 +153,9 @@ export async function preview(
return {
config,
httpServer,
async printUrls() {
await printCommonServerUrls(httpServer, config.preview, config)
resolvedUrls,
printUrls() {
printServerUrls(resolvedUrls, options.host, logger.info)
}
}
}
54 changes: 41 additions & 13 deletions packages/vite/src/node/server/index.ts
Expand Up @@ -19,7 +19,8 @@ import {
isParentDirectory,
mergeConfig,
normalizePath,
resolveHostname
resolveHostname,
resolveServerUrls
} from '../utils'
import { ssrLoadModule } from '../ssr/ssrModuleLoader'
import { cjsSsrResolveExternals } from '../ssr/ssrExternal'
Expand All @@ -35,7 +36,7 @@ import {
} from '../optimizer'
import { CLIENT_DIR } from '../constants'
import type { Logger } from '../logger'
import { printCommonServerUrls } from '../logger'
import { printServerUrls } from '../logger'
import { invalidatePackageData } from '../packages'
import type { PluginContainer } from './pluginContainer'
import { createPluginContainer } from './pluginContainer'
Expand Down Expand Up @@ -184,6 +185,13 @@ export 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.
*
* @experimental
*/
resolvedUrls: ResolvedServerUrls | null
/**
* Programmatically resolve, load and transform a URL and get the result
* without going through the http request pipeline.
Expand Down Expand Up @@ -234,7 +242,7 @@ export interface ViteDevServer {
/**
* Print server urls
*/
printUrls(): Promise<void>
printUrls(): void
/**
* Restart the server.
*
Expand Down Expand Up @@ -271,6 +279,11 @@ export interface ViteDevServer {
>
}

export interface ResolvedServerUrls {
local: string[]
network: string[]
}

export async function createServer(
inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
Expand Down Expand Up @@ -319,6 +332,7 @@ export async function createServer(
pluginContainer: container,
ws,
moduleGraph,
resolvedUrls: null, // will be set on listen
ssrTransform(code: string, inMap: SourceMap | null, url: string) {
return ssrTransform(code, inMap, url, code, {
json: { stringify: server.config.json?.stringify }
Expand Down Expand Up @@ -350,8 +364,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) {
Expand All @@ -360,19 +382,27 @@ export async function createServer(
process.stdin.off('end', exitProcess)
}
}

await Promise.all([
watcher.close(),
ws.close(),
container.close(),
closeHttpServer()
])
server.resolvedUrls = null
},
async printUrls() {
if (httpServer) {
await printCommonServerUrls(httpServer, config.server, config)
} else {
printUrls() {
if (server.resolvedUrls) {
printServerUrls(
server.resolvedUrls,
serverConfig.host,
config.logger.info
)
} 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) {
Expand Down Expand Up @@ -572,7 +602,7 @@ async function startServer(
server: ViteDevServer,
inlinePort?: number,
isRestart: boolean = false
): Promise<ViteDevServer> {
): Promise<void> {
const httpServer = server.httpServer
if (!httpServer) {
throw new Error('Cannot call server.listen in middleware mode.')
Expand Down Expand Up @@ -622,8 +652,6 @@ async function startServer(
server.config.logger
)
}

return server
}

function createServerCloseFn(server: http.Server | null) {
Expand Down

1 comment on commit 26bcdc3

@charbelnicolas
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, It would have be nice if you'd chosen a unicode character that has more coverage in popular terminal fonts:

from logger.ts:

"for (const url of urls.local) {
    info(`  ${colors.green('➜')}  ${colors.bold('Local')}:   ${colorUrl(url)}`)
  }"

Not many fonts have the HEAVY ROUND-TIPPED RIGHTWARDS ARROW (➜) character...

I would even argue that the arrow character is not even necessary but everyone wants new shiny icons nowadays for everything...

Please sign in to comment.