-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
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: add CLI keyboard shortcuts #9673
Changes from all commits
6dc65b8
4bc43cb
881412e
1f81b03
ef66f78
7a5cf4c
9a31b04
90da6ff
7e61bf9
abd78e0
e7214e3
5793000
cdc3509
6a04472
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,6 +70,7 @@ import { openBrowser } from './openBrowser' | |
import type { TransformOptions, TransformResult } from './transformRequest' | ||
import { transformRequest } from './transformRequest' | ||
import { searchForWorkspaceRoot } from './searchRoot' | ||
import { bindShortcuts } from './shortcuts' | ||
|
||
export { searchForWorkspaceRoot } from './searchRoot' | ||
|
||
|
@@ -114,6 +115,13 @@ export interface ServerOptions extends CommonServerOptions { | |
* in a future minor version without following semver | ||
*/ | ||
force?: boolean | ||
/** | ||
* Disable key bindings for the server by setting this to `false`. This can be | ||
* useful if you need the `process.stdin` stream for another purpose. | ||
* | ||
* @default true in CLI, `false` in programmatic usage | ||
*/ | ||
bindShortcuts?: boolean | ||
} | ||
|
||
export interface ResolvedServerOptions extends ServerOptions { | ||
|
@@ -255,6 +263,11 @@ export interface ViteDevServer { | |
* @param forceOptimize - force the optimizer to re-bundle, same as --force cli flag | ||
*/ | ||
restart(forceOptimize?: boolean): Promise<void> | ||
/** | ||
* Listen to `process.stdin` for pre-defined keyboard shortcuts, which are | ||
* printed to the terminal by this method. | ||
Comment on lines
+267
to
+268
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What can be useful for non standard custom usage is separating biding from help display with a little boolean flag on the method |
||
*/ | ||
bindShortcuts(): void | ||
/** | ||
* @internal | ||
*/ | ||
|
@@ -421,6 +434,11 @@ export async function createServer( | |
} | ||
return server._restartPromise | ||
}, | ||
bindShortcuts() { | ||
if (serverConfig.bindShortcuts !== false) { | ||
bindShortcuts(server) | ||
} | ||
}, | ||
|
||
_ssrExternals: null, | ||
_restartPromise: null, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import colors from 'picocolors' | ||
import type { ViteDevServer } from '..' | ||
import { openBrowser } from './openBrowser' | ||
|
||
export function bindShortcuts(server: ViteDevServer): void { | ||
if (!server.httpServer) return | ||
|
||
const helpInfo = | ||
colors.dim('press ') + | ||
colors.reset(colors.bold('h')) + | ||
colors.dim(' to show help') | ||
const quitInfo = | ||
colors.dim('press ') + | ||
colors.reset(colors.bold('q')) + | ||
colors.dim(' to quit') | ||
|
||
server.config.logger.info( | ||
colors.dim(` ${colors.green('➜')} ${helpInfo}, ${quitInfo}`) | ||
) | ||
|
||
let actionRunning = false | ||
|
||
const onInput = async (input: string) => { | ||
// ctrl+c or ctrl+d | ||
if (input === '\x03' || input === '\x04') { | ||
return process.kill(process.pid, 'SIGINT') | ||
} | ||
|
||
if (input === 'h') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be itself a shortcut. This is common for CLI to print the help option in the help itself |
||
return server.config.logger.info( | ||
SHORTCUTS.map( | ||
(shortcut) => | ||
colors.dim(' press ') + | ||
colors.reset(colors.bold(shortcut.key)) + | ||
colors.dim(` to ${shortcut.description}`) | ||
).join('\n') | ||
) | ||
} | ||
|
||
const shortcut = SHORTCUTS.find((shortcut) => shortcut.key === input) | ||
|
||
if (!shortcut) { | ||
return | ||
} | ||
|
||
if (actionRunning) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: this should be the second check |
||
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() | ||
}) | ||
} | ||
|
||
export interface Shortcut { | ||
key: string | ||
description: string | ||
action(server: ViteDevServer): void | Promise<void> | ||
} | ||
|
||
export const SHORTCUTS: Shortcut[] = [ | ||
Comment on lines
+66
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be exported? |
||
{ | ||
key: 'r', | ||
description: 'restart the server', | ||
async action(server: ViteDevServer): Promise<void> { | ||
await server.restart() | ||
server.bindShortcuts() | ||
} | ||
}, | ||
{ | ||
key: 'o', | ||
description: 'open in browser', | ||
action(server: ViteDevServer): void { | ||
const url = server.resolvedUrls?.local[0] | ||
|
||
if (!url) { | ||
return server.config.logger.warn( | ||
colors.yellow(`cannot open in browser; no server URLs registered.`) | ||
) | ||
} | ||
|
||
openBrowser(url, true, server.config.logger) | ||
} | ||
}, | ||
{ | ||
key: 'm', | ||
description: 'toggle hmr on/off', | ||
action({ config }: ViteDevServer): void { | ||
/** | ||
* Mutating the server config works because Vite reads from | ||
* it on every file change, instead of caching its value. | ||
* | ||
* Since `undefined` is treated as `true`, we have to | ||
* use `!== true` to flip the boolean value. | ||
*/ | ||
config.server.hmr = config.server.hmr !== true | ||
config.logger.info( | ||
colors.cyan(` hmr ${config.server.hmr ? `enabled` : `disabled`}`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to test but I think the two whitespaces at the beginning will look strange in the middle of Vite dev server logs |
||
) | ||
} | ||
}, | ||
{ | ||
key: 'q', | ||
description: 'quit', | ||
async action(server: ViteDevServer): Promise<void> { | ||
try { | ||
await server.close() | ||
} finally { | ||
process.exit() | ||
} | ||
} | ||
} | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually this is not called at all in programmatic usage. (like printURLs)
There is currently no option to disable print of server urls, I would do the same here.
If people want more complex use case, they can use the pragmatic usage.