我们知道, package.json
文件的 bin
字段是用来指定要运行的命令及运行命令所要执行的文件
vite 命令执行的文件
vite 的配置如下:
json
{
"bin": {
"vite": "bin/vite.js"
}
}
好了,我们知道启动 dev-server 的 vite
命令,实际运行的就是 bin/vite.js
文件,那我们进去看看:
1. 入口:vite/bin/vite.js
判断当前目录是否包含 node_modules
目录,若不包含,则需要 source-map
支持
js
#!/usr/bin/env node // 固定格式,用来指定命令运行的环境(node)
if (!__dirname.includes('node_modules')) {
try {
// only available as dev dependency
require('source-map-support').install()
} catch (e) {}
}
2. 检查 process.argv
是否是 debug
模式
js
// check debug mode first before requiring the CLI.
const debugIndex = process.argv.findIndex(arg => /^(?:-d|--debug)$/.test(arg))
const filterIndex = process.argv.findIndex(arg => /^(?:-f|--filter)$/.test(arg))
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. 导入编译后的 cli
脚本
js
const profileIndex = process.argv.indexOf('--profile')
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)
}
// 创建会话连接(webkit inspector)
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. 进入 cli.js
,源码:vite/src/node/cli.ts
ts
import { cac } from 'cac' /* 一个简单且强大的命令行库,比 commander 更轻量 */
import chalk from 'chalk' /* 美化控制台日志记录 */
import { performance } from 'perf_hooks' /* node.js 收集性能指标的对象 */
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. 全局 cli
选项及清理选项
ts
// global options
interface GlobalCLIOptions {
'--'?: string[]
c?: boolean | string
config?: string
base?: string
l?: LogLevel
logLevel?: LogLevel
clearScreen?: boolean
d?: boolean | string
debug?: boolean | string
f?: string
filter?: string
m?: string
mode?: string
}
/**
* removing global flags before passing
* as command specific sub-configs
* 删除全局 cli 配置
*/
function cleanOptions<Options extends GlobalCLIOptions>(
options: Options
): Omit<Options, keyof GlobalCLIOptions> {
const ret = { ...options }
delete ret['--']
delete ret.c
delete ret.config
delete ret.base
delete ret.l
delete ret.logLevel
delete ret.clearScreen
delete ret.d
delete ret.debug
delete ret.f
delete ret.filter
delete ret.m
delete ret.mode
return ret
}
6. cli 参数定义
这里我们就只研究一下开发环境下的功能实现,生产环境/预览环境后续再考虑吧~
ts
// 下面是一些核心参数,如 -c 指定配置文件等
cli
.option('-c, --config <file>', `[string] use specified config file`)
.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`)
.option('-m, --mode <mode>', `[string] set env mode`)
// dev 开发环境,主要针对 server 的参数配置
cli
.command('[root]') // default command
.alias('serve') // the command is called 'serve' in Vite's API
.alias('dev') // alias to align with the script name
.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(
'--force',
`[boolean] force the optimizer to ignore the cache and re-bundle`
)
.action(devAction)
/* 这里将 action 函数抽出来单独看,免得篇幅过长,拎不清注意点 */
cli.help() // 命令行使用 -h / --help 参数,输出帮助信息
cli.version(require('../../package.json').version) // vite 版本号
cli.parse() // 解析命令行参数
devAction 函数
:
ts
const devAction = async (
root: string,
options: ServerOptions & GlobalCLIOptions
) => {
// output structure is preserved even after bundling so require()
// is ok here
const { createServer } = await import('./server') // 导入创建 dev-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)
})
// native Node http server instance or null
if (!server.httpServer) {
throw new Error('HTTP server not available')
}
await server.listen() // 启动 dev-server
const info = server.config.logger.info // info 日志
info(
chalk.cyan(`\n vite v${require('vite/package.json').version}`) +
chalk.green(` dev server running at:\n`),
{
clear: !server.config.logger.hasWarned
}
)
// 调用 logger.ts 的 printCommonServerUrls 方法打印 dev-server 运行的 url
server.printUrls()
// __vite_start_time 在执行 vite 命令时
// 赋值为 performance.now(),记录启动服务的开始时间
if (global.__vite_start_time) {
// 服务启动时间(ms)
const startupDuration = performance.now() - global.__vite_start_time
info(`\n ${chalk.cyan(`ready in ${Math.ceil(startupDuration)}ms.`)}\n`)
}
} catch (e) {
// 打印服务启动失败的日志
createLogger(options.logLevel).error(
chalk.red(`error when starting dev server:\n${e.stack}`),
{ error: e }
)
// 结束进程
process.exit(1)
}
}
7. createServer
ts
// vite/src/node/server/index.ts
export async function createServer(
inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
// 这个方法里,大概做了下面这些事:
// 1. 处理服务的启动配置
const config = await resolveConfig(inlineConfig, 'serve', 'development')
const httpsOptions = await resolveHttpsConfig(config.server.https)
// 2. 检查 config.server.middlewareMode 是否 === true,若是将其置为 'ssr'
// connect 包创建连接(方便使用中间件来扩展的 node http server 框架)
const middlewares = connect() as Connect.Server
const httpServer = middlewareMode
? null
: await resolveHttpServer(serverConfig, middlewares, httpsOptions)
// resolveHttpServer 方法用来区分使用 http / https / http2 (npm package)来
// 创建 server 并返回赋值给 httpServer
// 3. 创建 webSocket 实例
const ws = createWebSocketServer(httpServer, config, httpsOptions)
// 4. 使用 chokidar 监听 文件 修改
const { ignored = [], ...watchOptions } = serverConfig.watch || {}
const watcher = chokidar.watch(path.resolve(root), {
ignored: [
'**/node_modules/**',
'**/.git/**',
...(Array.isArray(ignored) ? ignored : [ignored])
],
ignoreInitial: true,
ignorePermissionErrors: true,
disableGlobbing: true,
...watchOptions
}) as FSWatcher
// 5. 创建 pluginContainer,创建模块映射表
const container = await createPluginContainer(config, moduleGraph, watcher)
const moduleGraph: ModuleGraph = new ModuleGraph(url =>
container.resolveId(url)
)
const closeHttpServer = createServerCloseFn(httpServer)
// 6. 创建 server 对象
const server: ViteServer = {
config,
middlewares,
get app() {
// 过期的 api
return middlewares
},
httpServer,
watcher,
pluginContainer: container,
ws,
moduleGraph,
transformWithEsbuild,
transformRequest(url, options) {
return transformRequest(url, server, options)
},
transformIndexHtml: null!, // to be immediately set
ssrLoadModule(url) {
// ... 服务端渲染加载 module
},
listen(port?: number, isRestart?: boolean) {
// 启动 dev-server 方法
return startServer(server, port, isRestart)
},
async close() {
// 退出 dev-server
// watcher,ws,container,httpServer
},
printUrls() {
// 打印日志
},
async restart(forceOptimize: boolean) {
// 调用 restartServer 方法,重启 dev-server
}
// _xxxx 一些私有属性
}
// 立即创建转换 html 的方法
server.transformIndexHtml = createDevHtmlTransformFn(server)
// 7. package 缓存
const { packageCache } = config
const setPackageData = packageCache.set.bind(packageCache)
packageCache.set = (id, pkg) => {
if (id.endsWith('.json')) {
watcher.add(id)
}
return setPackageData(id, pkg)
}
// 8. watcher 监听文件变化:change, add, unlink
// 具体可见 【热更新机制】
watcher.on('change', async file => {
/* ...other code */
})
// 9. 一堆中间件的应用
// /node/server/middlewares/*.ts
return server // 返回创建的 server 对象
}