首先我们来看入口文件:/vite/packages/vite/bin/vite.js
第1步:
判断当前目录是否包含node_module目录,如果不包含,需要source-map的支持
// vite/packages/vite/bin/vite.js
#!/usr/bin/env node
if (!__dirname.includes('node_modules')) {
try {
// only available as dev dependency
require('source-map-support').install()
} catch (e) {}
}
第2步:
在导入cli之前通过正则匹配process.argv检查下debug模式:
// vite/packages/vite/bin/vite.js
const debugIndex = process.argv.findIndex((arg) => /^(?:-d|--debug)$/.test(arg))
const filterIndex = process.argv.findIndex((arg) =>
/^(?:-f|--filter)$/.test(arg)
)
const profileIndex = process.argv.indexOf('--profile')
if (debugIndex > 0) {
let value = process.argv[debugIndex + 1]
if (!value || value.startsWith('-')) {
value = 'vite:*'
} else {
// support debugging multiple flags with comma-separated list
value = value
.split(',')
.map((v) => `vite:${v}`)
.join(',')
}
process.env.DEBUG = value
if (filterIndex > 0) {
const filter = process.argv[filterIndex + 1]
if (filter && !filter.startsWith('-')) {
process.env.VITE_DEBUG_FILTER = filter
}
}
}
第3步:
// vite/packages/vite/bin/vite.js
// 启动方式就是导入编译后的node cli
function start() {
require('../dist/node/cli')
}
// 如果配置了profile参数
if (profileIndex > 0) {
// 删除profile参数
process.argv.splice(profileIndex, 1)
// 获取下一个参数,如果下个参数还有但并没有以-开头,继续删除一个
const next = process.argv[profileIndex]
if (next && !next.startsWith('-')) {
process.argv.splice(profileIndex, 1)
}
// 导入inspector库Inspector API,创建对应会话并连接
const inspector = require('inspector')
const session = (global.__vite_profile_session = new inspector.Session())
session.connect()
session.post('Profiler.enable', () => {
session.post('Profiler.start', start)
})
} else {
// 正常导入启动
start()
}
第4步:进入node 的cli部分
// packages/vite/src/node/cli.ts
import { cac } from 'cac'
import chalk from 'chalk'
import { BuildOptions } from './build'
import { ServerOptions } from './server'
import { createLogger, LogLevel } from './logger'
import { resolveConfig } from '.'
import { preview } from './preview'
// 利用cac生成cli实例
const cli = cac('vite')
第5步:全局选项定义及清理选项
// packages/vite/src/node/cli.ts
interface GlobalCLIOptions {
'--'?: string[]
debug?: boolean | string
d?: boolean | string
filter?: string
f?: string
config?: string
c?: boolean | string
root?: string
base?: string
r?: string
mode?: string
m?: string
logLevel?: LogLevel
l?: LogLevel
clearScreen?: boolean
}
/**
* removing global flags before passing as command specific sub-configs
*/
function cleanOptions(options: GlobalCLIOptions) {
const ret = { ...options }
delete ret['--']
delete ret.debug
delete ret.d
delete ret.filter
delete ret.f
delete ret.config
delete ret.c
delete ret.root
delete ret.base
delete ret.r
delete ret.mode
delete ret.m
delete ret.logLevel
delete ret.l
delete ret.clearScreen
return ret
}
第6步:cli 参数定义
// packages/vite/src/node/cli.ts
// 下面几个是核心参数,例如-c指定配置文件
cli
.option('-c, --config <file>', `[string] use specified config file`)
.option('-r, --root <path>', `[string] use specified root directory`)
.option('--base <path>', `[string] public base path (default: /)`)
.option('-l, --logLevel <level>', `[string] info | warn | error | silent`)
.option('--clearScreen', `[boolean] allow/disable clear screen when logging`)
.option('-d, --debug [feat]', `[string | boolean] show debug logs`)
.option('-f, --filter <filter>', `[string] filter debug logs`)
// 开发参数,主要针对server的配置
cli
.command('[root]') // default command
.alias('serve')
.option('--host [host]', `[string] specify hostname`)
.option('--port <port>', `[number] specify port`)
.option('--https', `[boolean] use TLS + HTTP/2`)
.option('--open [path]', `[boolean | string] open browser on startup`)
.option('--cors', `[boolean] enable CORS`)
.option('--strictPort', `[boolean] exit if specified port is already in use`)
.option('-m, --mode <mode>', `[string] set env mode`)
.option(
'--force',
`[boolean] force the optimizer to ignore the cache and re-bundle`
)
.action(async (root: string, options: ServerOptions & GlobalCLIOptions) => {
// output structure is preserved even after bundling so require()
// is ok here
const { createServer } = await import('./server')
try {
const server = await createServer({
root,
base: options.base,
mode: options.mode,
configFile: options.config,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
server: cleanOptions(options) as ServerOptions
})
await server.listen()
} catch (e) {
createLogger(options.logLevel).error(
chalk.red(`error when starting dev server:\n${e.stack}`)
)
process.exit(1)
}
})
// 构建参数
cli
.command('build [root]')
.option('--target <target>', `[string] transpile target (default: 'modules')`)
.option('--outDir <dir>', `[string] output directory (default: dist)`)
.option(
'--assetsDir <dir>',
`[string] directory under outDir to place assets in (default: _assets)`
)
.option(
'--assetsInlineLimit <number>',
`[number] static asset base64 inline threshold in bytes (default: 4096)`
)
.option(
'--ssr [entry]',
`[string] build specified entry for server-side rendering`
)
.option(
'--sourcemap',
`[boolean] output source maps for build (default: false)`
)
.option(
'--minify [minifier]',
`[boolean | "terser" | "esbuild"] enable/disable minification, ` +
`or specify minifier to use (default: terser)`
)
.option('--manifest', `[boolean] emit build manifest json`)
.option('--ssrManifest', `[boolean] emit ssr manifest json`)
.option(
'--emptyOutDir',
`[boolean] force empty outDir when it's outside of root`
)
.option('-m, --mode <mode>', `[string] set env mode`)
.option('-w, --watch', `[boolean] rebuilds when modules have changed on disk`)
.action(async (root: string, options: BuildOptions & GlobalCLIOptions) => {
const { build } = await import('./build')
const buildOptions = cleanOptions(options) as BuildOptions
try {
await build({
root,
base: options.base,
mode: options.mode,
configFile: options.config,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
build: buildOptions
})
} catch (e) {
createLogger(options.logLevel).error(
chalk.red(`error during build:\n${e.stack}`)
)
process.exit(1)
}
})
// 优化参数:这里主要配置是否忽略缓存,默认vite在依赖预构建会缓存公共依赖包,例如lodash
cli
.command('optimize [root]')
.option(
'--force',
`[boolean] force the optimizer to ignore the cache and re-bundle`
)
.action(
async (root: string, options: { force?: boolean } & GlobalCLIOptions) => {
const { optimizeDeps } = await import('./optimizer')
try {
const config = await resolveConfig(
{
root,
base: options.base,
configFile: options.config,
logLevel: options.logLevel
},
'build',
'development'
)
await optimizeDeps(config, options.force, true)
} catch (e) {
createLogger(options.logLevel).error(
chalk.red(`error when optimizing deps:\n${e.stack}`)
)
process.exit(1)
}
}
)
// 预览配置
cli
.command('preview [root]')
.option('--host [host]', `[string] specify hostname`)
.option('--port <port>', `[number] specify port`)
.option('--https', `[boolean] use TLS + HTTP/2`)
.option('--open [path]', `[boolean | string] open browser on startup`)
.action(
async (
root: string,
options: {
host?: string
port?: number
https?: boolean
open?: boolean | string
} & GlobalCLIOptions
) => {
try {
const config = await resolveConfig(
{
root,
base: options.base,
configFile: options.config,
logLevel: options.logLevel,
server: {
open: options.open
}
},
'serve',
'development'
)
await preview(
config,
cleanOptions(options) as {
host?: string
port?: number
https?: boolean
}
)
} catch (e) {
createLogger(options.logLevel).error(
chalk.red(`error when starting preview server:\n${e.stack}`)
)
process.exit(1)
}
}
)
// 输出帮助信息
cli.help()
// 取当前package.json的版本作为cli的版本
cli.version(require('../../package.json').version)
// 开始执行解析
cli.parse()
总结
vite在cli部分判断了包括profile,debugger等多类参数,并针对build
,server
,preview
等不同类型提供对应的配置内容。cli本身借助cac的能力,相当简洁清晰。
备注
- inspector库:WebKit inspector API的node实现
- cac:cli 参数识别工具包