Skip to content

Commit

Permalink
feat: add CLI keyboard shortcuts (vitejs#11228)
Browse files Browse the repository at this point in the history
  • Loading branch information
ArnaudBarre authored and futurGH committed Feb 26, 2023
1 parent 59018d3 commit 5755244
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 9 deletions.
56 changes: 47 additions & 9 deletions packages/vite/src/node/cli.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import path from 'node:path'
import fs from 'node:fs'
import { performance } from 'node:perf_hooks'
import type { Session } from 'node:inspector'
import { cac } from 'cac'
import colors from 'picocolors'
import type { BuildOptions } from './build'
import type { ServerOptions } from './server'
import type { LogLevel } from './logger'
import { createLogger } from './logger'
import { VERSION } from './constants'
import { bindShortcuts } from './shortcuts'
import { resolveConfig } from '.'

const cli = cac('vite')
Expand All @@ -30,25 +32,34 @@ interface GlobalCLIOptions {
force?: boolean
}

export const stopProfiler = (log: (message: string) => void): void => {
// @ts-ignore
const profileSession = global.__vite_profile_session
if (profileSession) {
profileSession.post('Profiler.stop', (err: any, { profile }: any) => {
// @ts-ignore
let profileSession: Session | undefined = global.__vite_profile_session
let profileCount = 0

export const stopProfiler = (
log: (message: string) => void,
): void | Promise<void> => {
if (!profileSession) return
return new Promise((res, rej) => {
profileSession!.post('Profiler.stop', (err: any, { profile }: any) => {
// Write profile to disk, upload, etc.
if (!err) {
const outPath = path.resolve('./vite-profile.cpuprofile')
const outPath = path.resolve(
`./vite-profile-${profileCount++}.cpuprofile`,
)
fs.writeFileSync(outPath, JSON.stringify(profile))
log(
colors.yellow(
`CPU profile written to ${colors.white(colors.dim(outPath))}`,
),
)
profileSession = undefined
res()
} else {
throw err
rej(err)
}
})
}
})
}

const filterDuplicateOptions = <T extends object>(options: T) => {
Expand Down Expand Up @@ -148,7 +159,34 @@ cli
)

server.printUrls()
stopProfiler((message) => server.config.logger.info(` ${message}`))
bindShortcuts(server, {
print: true,
customShortcuts: [
profileSession && {
key: 'p',
description: 'start/stop the profiler',
async action(server) {
if (profileSession) {
await stopProfiler(server.config.logger.info)
} else {
const inspector = await import('node:inspector').then(
(r) => r.default,
)
await new Promise<void>((res) => {
profileSession = new inspector.Session()
profileSession.connect()
profileSession.post('Profiler.enable', () => {
profileSession!.post('Profiler.start', () => {
server.config.logger.info('Profiler started')
res()
})
})
})
}
},
},
],
})
} catch (e) {
const logger = createLogger(options.logLevel)
logger.error(colors.red(`error when starting dev server:\n${e.stack}`), {
Expand Down
16 changes: 16 additions & 0 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
initDepsOptimizer,
initDevSsrDepsOptimizer,
} from '../optimizer'
import { bindShortcuts } from '../shortcuts'
import type { BindShortcutsOptions } from '../shortcuts'
import { CLIENT_DIR } from '../constants'
import type { Logger } from '../logger'
import { printServerUrls } from '../logger'
Expand Down Expand Up @@ -300,6 +302,13 @@ export interface ViteDevServer {
* @internal
*/
_fsDenyGlob: Matcher
/**
* @internal
* Actually BindShortcutsOptions | undefined but api-extractor checks for
* export before trimming internal types :(
* And I don't want to complexity prePatchTypes for that
*/
_shortcutsOptions: any | undefined
}

export interface ResolvedServerUrls {
Expand Down Expand Up @@ -452,6 +461,7 @@ export async function createServer(
_forceOptimizeOnRestart: false,
_pendingRequests: new Map(),
_fsDenyGlob: picomatch(config.server.fs.deny, { matchBase: true }),
_shortcutsOptions: undefined,
}

server.transformIndexHtml = createDevHtmlTransformFn(server)
Expand Down Expand Up @@ -771,6 +781,7 @@ async function restartServer(server: ViteDevServer) {
// @ts-ignore
global.__vite_start_time = performance.now()
const { port: prevPort, host: prevHost } = server.config.server
const shortcutsOptions: BindShortcutsOptions = server._shortcutsOptions

await server.close()

Expand Down Expand Up @@ -819,6 +830,11 @@ async function restartServer(server: ViteDevServer) {
logger.info('server restarted.', { timestamp: true })
}

if (shortcutsOptions) {
shortcutsOptions.print = false
bindShortcuts(newServer, shortcutsOptions)
}

// new server (the current server) can restart now
newServer._restartPromise = null
}
Expand Down
112 changes: 112 additions & 0 deletions packages/vite/src/node/shortcuts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import colors from 'picocolors'
import type { ViteDevServer } from './server'
import { openBrowser } from './server/openBrowser'
import { isDefined } from './utils'

export type BindShortcutsOptions = {
/**
* Print a one line hint to the terminal.
*/
print?: boolean
customShortcuts?: (CLIShortcut | undefined | null)[]
}

export type CLIShortcut = {
key: string
description: string
action(server: ViteDevServer): void | Promise<void>
}

export function bindShortcuts(
server: ViteDevServer,
opts: BindShortcutsOptions,
): void {
if (!server.httpServer) return
server._shortcutsOptions = opts

if (opts.print) {
server.config.logger.info(
colors.dim(colors.green(' ➜')) +
colors.dim(' press ') +
colors.bold('h') +
colors.dim(' to show help'),
)
}

const shortcuts = (opts.customShortcuts ?? [])
.filter(isDefined)
.concat(BASE_SHORTCUTS)

let actionRunning = false

const onInput = async (input: string) => {
// ctrl+c or ctrl+d
if (input === '\x03' || input === '\x04') {
process.emit('SIGTERM')
return
}

if (actionRunning) return

if (input === 'h') {
server.config.logger.info(
shortcuts
.map(
(shortcut) =>
colors.dim(' press ') +
colors.bold(shortcut.key) +
colors.dim(` to ${shortcut.description}`),
)
.join('\n'),
)
}

const shortcut = shortcuts.find((shortcut) => shortcut.key === input)
if (!shortcut) return

actionRunning = true
await shortcut.action(server)
actionRunning = false
}

if (process.stdin.isTTY) {
process.stdin.setRawMode(true)
}

process.stdin.on('data', onInput).setEncoding('utf8').resume()

server.httpServer.on('close', () => {
process.stdin.off('data', onInput).pause()
})
}

const BASE_SHORTCUTS: CLIShortcut[] = [
{
key: 'r',
description: 'restart the server',
async action(server) {
await server.restart()
},
},
{
key: 'o',
description: 'open in browser',
action(server) {
const url = server.resolvedUrls?.local[0]

if (!url) {
server.config.logger.warn('No URL available to open in browser')
return
}

openBrowser(url, true, server.config.logger)
},
},
{
key: 'q',
description: 'quit',
async action(server) {
await server.close().finally(() => process.exit())
},
},
]

0 comments on commit 5755244

Please sign in to comment.