1. 前言
1.1 环境
- 学会 vite 简单原理
2. 开始
2.1 项目结构
2.1.1 monorepo管理方式
从上图可以看出,这是一个monorepo项目,单仓库多包管理
使用 pnpm 作为包管理工具,pnpm天生支持workspae,可以不需要再使用 lerna 来管理仓库了
packages文件夹下面的每一个文件夹都是一个npm包
playground文件夹比较特殊,里面的每个文件夹又是一个独立的npm包2.1.2 scripts run-s
在dependencies找了一圈,没发现有run-s这个包,不过看到了 npm-run-all,之前有看过这个包,知道是执行多脚本的,因此在node_modules下面查看了该包的bin文件夹,发现有run-s,run-p,npm-run-all等三个脚本文件夹,恍然大悟,就是run-s就是 npm-run-all的一个bin执行文件
2.2 packages/vite 执行文件bin/vite.js
```javascript!/usr/bin/env node
// node里面的性能api const { performance } = require(‘perf_hooks’) // 如果没有node_modules if (!dirname.includes(‘node_modules’)) { try { // only available as dev dependency, 使用source-map加载 require(‘source-map-support’).install() } catch (e) {} } // 全局添加参数 global.vite_start_time = performance.now()
// check debug mode first before requiring the CLI. // 找到参数是-d | —debug,另一种写法,没使用minimist来处理参数 (?:pattern) 非获取匹配 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) {
// 获取参数的值这里应该是用空格做分割,如 —debug xxx
let value = process.argv[debugIndex + 1]
// 如果不存在,或者以-开头,说明没有参数值
if (!value || value.startsWith(‘-‘)) {
// 默认设置vite:
value = ‘vite:‘
} else {
// support debugging multiple flags with comma-separated list
// 如果有多个包用逗号分隔,批量添加vite:x,vite:z
value = value
.split(‘,’)
.map((v) => vite:${v}
)
.join(‘,’)
}
// 添加环境变量DEBUG,可以在全局用
process.env.DEBUG = value
if (filterIndex > 0) { // 过滤的值 const filter = process.argv[filterIndex + 1] if (filter && !filter.startsWith(‘-‘)) { // 这里没做处理,直接放入环境变量,全局变量可以放入环境变量中 process.env.VITE_DEBUG_FILTER = filter } } }
function start() { // 执行dist/node/cli require(‘../dist/node/cli’) } // 这个是开启分析 if (profileIndex > 0) { // 从argv中删除参数 process.argv.splice(profileIndex, 1) // 拿到—profile后面一个值 const next = process.argv[profileIndex] if (next && !next.startsWith(‘-‘)) { // 从参数中删除 process.argv.splice(profileIndex, 1) } // node调试api,可以接入到chrome const inspector = require(‘inspector’) // 放入到global全局, const session = (global.__vite_profile_session = new inspector.Session()) session.connect() session.post(‘Profiler.enable’, () => { session.post(‘Profiler.start’, start) }) } else { start() }
<a name="FA8Lf"></a>
## 2.3 vite src/node/cli.ts
```typescript
// cli工具
import { cac } from 'cac'
// log输出带颜色
import chalk from 'chalk'
// 性能工具
import { performance } from 'perf_hooks'
// 打包配置
import { BuildOptions } from './build'
// 服务配置
import { ServerOptions } from './server'
// 日志和等级
import { createLogger, LogLevel } from './logger'
// 解析配置
import { resolveConfig } from '.'
import { preview } from './preview'
const cli = cac('vite')
// 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
*/
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
}
// 基础配置说明 cli.option都是用来做help说明的
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
// 执行cli serve 和 dev就会匹配到执行里面的action
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(async (root: string, options: ServerOptions & GlobalCLIOptions) => {
// output structure is preserved even after bundling so require()
// is ok here 在代码里面使用import
const { createServer } = await import('./server')
try {
// 创建server对象
const server = await createServer({
root,
base: options.base,
mode: options.mode,
configFile: options.config,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
server: cleanOptions(options)
})
if (!server.httpServer) {
throw new Error('HTTP server not available')
}
await server.listen()
const info = server.config.logger.info
info(
chalk.cyan(`\n vite v${require('vite/package.json').version}`) +
chalk.green(` dev server running at:\n`),
{
clear: !server.config.logger.hasWarned
}
)
server.printUrls()
// @ts-ignore 性能监控的内容,这个值从bin/vite.js中设置的,用来获取开启服务的事件
if (global.__vite_start_time) {
// @ts-ignore
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)
}
})
// build
// 打包相关的
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: esbuild)`
)
.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('-w, --watch', `[boolean] rebuilds when modules have changed on disk`)
.action(async (root: string, options: BuildOptions & GlobalCLIOptions) => {
// 调用build函数
const { build } = await import('./build')
const buildOptions: BuildOptions = cleanOptions(options)
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}`),
{ error: e }
)
process.exit(1)
}
})
// optimize 优化
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}`),
{ error: e }
)
process.exit(1)
}
}
)
cli
.command('preview [root]')
.option('--host [host]', `[string] specify hostname`)
.option('--port <port>', `[number] specify port`)
.option('--strictPort', `[boolean] exit if specified port is already in use`)
.option('--https', `[boolean] use TLS + HTTP/2`)
.option('--open [path]', `[boolean | string] open browser on startup`)
.action(
async (
root: string,
options: {
host?: string | boolean
port?: number
https?: boolean
open?: boolean | string
strictPort?: boolean
} & GlobalCLIOptions
) => {
try {
const server = await preview({
root,
base: options.base,
configFile: options.config,
logLevel: options.logLevel,
preview: {
port: options.port,
strictPort: options.strictPort,
host: options.host,
https: options.https,
open: options.open
}
})
server.printUrls()
} catch (e) {
createLogger(options.logLevel).error(
chalk.red(`error when starting preview server:\n${e.stack}`),
{ error: e }
)
process.exit(1)
}
}
)
// cli.js --help 可以查看上面配置的option
cli.help()
// cli.js --version 可以查看当前版本
cli.version(require('../../package.json').version)
// 获取命令的值
cli.parse()
cac cli工具
2.4 vite src/node/server/index.ts
2.4.1 createServer 创建server对象
import fs from 'fs'
import path from 'path'
import * as net from 'net'
import * as http from 'http'
import connect from 'connect'
import corsMiddleware from 'cors'
import chalk from 'chalk'
// 获取ip信息
import { AddressInfo } from 'net'
// 监听文件变化
import chokidar from 'chokidar'
import {
resolveHttpsConfig,
resolveHttpServer,
httpServerStart,
CommonServerOptions
} from '../http'
import { resolveConfig, InlineConfig, ResolvedConfig } from '../config'
import { createPluginContainer, PluginContainer } from './pluginContainer'
import { FSWatcher, WatchOptions } from 'types/chokidar'
import { createWebSocketServer, WebSocketServer } from './ws'
import { baseMiddleware } from './middlewares/base'
import { proxyMiddleware } from './middlewares/proxy'
import { spaFallbackMiddleware } from './middlewares/spaFallback'
import { transformMiddleware } from './middlewares/transform'
import {
createDevHtmlTransformFn,
indexHtmlMiddleware
} from './middlewares/indexHtml'
import {
serveRawFsMiddleware,
servePublicMiddleware,
serveStaticMiddleware
} from './middlewares/static'
import { timeMiddleware } from './middlewares/time'
import { ModuleGraph, ModuleNode } from './moduleGraph'
import { Connect } from 'types/connect'
import { ensureLeadingSlash, normalizePath } from '../utils'
import { errorMiddleware, prepareError } from './middlewares/error'
import { handleHMRUpdate, HmrOptions, handleFileAddUnlink } from './hmr'
import { openBrowser } from './openBrowser'
import launchEditorMiddleware from 'launch-editor-middleware'
import {
TransformOptions,
TransformResult,
transformRequest
} from './transformRequest'
import {
transformWithEsbuild,
ESBuildTransformResult
} from '../plugins/esbuild'
import { TransformOptions as EsbuildTransformOptions } from 'esbuild'
import { DepOptimizationMetadata, optimizeDeps } from '../optimizer'
import { ssrLoadModule } from '../ssr/ssrModuleLoader'
import { resolveSSRExternal } from '../ssr/ssrExternal'
import {
rebindErrorStacktrace,
ssrRewriteStacktrace
} from '../ssr/ssrStacktrace'
import { ssrTransform } from '../ssr/ssrTransform'
import { createMissingImporterRegisterFn } from '../optimizer/registerMissing'
import { resolveHostname } from '../utils'
import { searchForWorkspaceRoot } from './searchRoot'
import { CLIENT_DIR } from '../constants'
import { printCommonServerUrls } from '../logger'
import { performance } from 'perf_hooks'
import { invalidatePackageData } from '../packages'
import { SourceMap } from 'rollup'
export { searchForWorkspaceRoot } from './searchRoot'
// cli里面调用的createServer
export async function createServer(
// 配置项
inlineConfig: InlineConfig = {}
): Promise<ViteDevServer> {
// 解析配置项
const config = await resolveConfig(inlineConfig, 'serve', 'development')
const root = config.root
const serverConfig = config.server
// 解析http配置
const httpsOptions = await resolveHttpsConfig(config.server.https)
let { middlewareMode } = serverConfig
if (middlewareMode === true) {
middlewareMode = 'ssr'
}
const middlewares = connect() as Connect.Server
// 创建http服务
const httpServer = middlewareMode
? null
: await resolveHttpServer(serverConfig, middlewares, httpsOptions)
// 创建一个webSocket服务,用来做hrm做的
const ws = createWebSocketServer(httpServer, config, httpsOptions)
// 监听服务
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
const moduleGraph: ModuleGraph = new ModuleGraph((url) =>
container.resolveId(url)
)
// 插件容器
const container = await createPluginContainer(config, moduleGraph, watcher)
// 关闭http函数
const closeHttpServer = createServerCloseFn(httpServer)
// eslint-disable-next-line prefer-const
let exitProcess: () => void
// server对象,包含的一些属性和方法
const server: ViteDevServer = {
config,
middlewares,
get app() {
config.logger.warn(
`ViteDevServer.app is deprecated. Use ViteDevServer.middlewares instead.`
)
return middlewares
},
httpServer,
watcher,
pluginContainer: container,
ws,
moduleGraph,
ssrTransform,
transformWithEsbuild,
transformRequest(url, options) {
return transformRequest(url, server, options)
},
transformIndexHtml: null!, // to be immediately set
ssrLoadModule(url) {
server._ssrExternals ||= resolveSSRExternal(
config,
server._optimizeDepsMetadata
? Object.keys(server._optimizeDepsMetadata.optimized)
: []
)
return ssrLoadModule(url, server)
},
ssrFixStacktrace(e) {
if (e.stack) {
const stacktrace = ssrRewriteStacktrace(e.stack, moduleGraph)
rebindErrorStacktrace(e, stacktrace)
}
},
// 这里做监听,开启server
listen(port?: number, isRestart?: boolean) {
return startServer(server, port, isRestart)
},
// 关闭服务
async close() {
// 执行关闭信号量
process.off('SIGTERM', exitProcess)
if (!middlewareMode && process.env.CI !== 'true') {
process.stdin.off('end', exitProcess)
}
// 关闭所有的服务
await Promise.all([
// 监听器
watcher.close(),
// hrm服务
ws.close(),
// 插件容器
container.close(),
// 关闭http服务
closeHttpServer()
])
},
printUrls() {
if (httpServer) {
printCommonServerUrls(httpServer, config.server, config)
} else {
throw new Error('cannot print server URLs in middleware mode.')
}
},
// 重启
async restart(forceOptimize: boolean) {
if (!server._restartPromise) {
server._forceOptimizeOnRestart = !!forceOptimize
server._restartPromise = restartServer(server).finally(() => {
server._restartPromise = null
server._forceOptimizeOnRestart = false
})
}
return server._restartPromise
},
_optimizeDepsMetadata: null,
_ssrExternals: null,
_globImporters: Object.create(null),
_restartPromise: null,
_forceOptimizeOnRestart: false,
_isRunningOptimizer: false,
_registerMissingImport: null,
_pendingReload: null,
_pendingRequests: new Map()
}
// html转换函数
server.transformIndexHtml = createDevHtmlTransformFn(server)
exitProcess = async () => {
try {
await server.close()
} finally {
process.exit(0)
}
}
// 一旦接受到信号,执行exitProcess
process.once('SIGTERM', exitProcess)
if (!middlewareMode && process.env.CI !== 'true') {
process.stdin.on('end', exitProcess)
}
// 缓存包数据
const { packageCache } = config
const setPackageData = packageCache.set.bind(packageCache)
packageCache.set = (id, pkg) => {
if (id.endsWith('.json')) {
// json文件放入watcher监听
watcher.add(id)
}
return setPackageData(id, pkg)
}
// 监听文件变化
watcher.on('change', async (file) => {
file = normalizePath(file)
if (file.endsWith('/package.json')) {
return invalidatePackageData(packageCache, file)
}
// invalidate module graph cache on file change
moduleGraph.onFileChange(file)
if (serverConfig.hmr !== false) {
try {
await handleHMRUpdate(file, server)
} catch (err) {
ws.send({
type: 'error',
err: prepareError(err)
})
}
}
})
watcher.on('add', (file) => {
handleFileAddUnlink(normalizePath(file), server)
})
watcher.on('unlink', (file) => {
handleFileAddUnlink(normalizePath(file), server, true)
})
if (!middlewareMode && httpServer) {
httpServer.once('listening', () => {
// update actual port since this may be different from initial value
serverConfig.port = (httpServer.address() as AddressInfo).port
})
}
// apply server configuration hooks from plugins
// 将插件放入postHooks队列中,执行plugin.configureServer将server实例注入到插件中
const postHooks: ((() => void) | void)[] = []
for (const plugin of config.plugins) {
if (plugin.configureServer) {
postHooks.push(await plugin.configureServer(server))
}
}
// Internal middlewares ------------------------------------------------------
// request timer
if (process.env.DEBUG) {
// 时间中间件
middlewares.use(timeMiddleware(root))
}
// cors (enabled by default)
const { cors } = serverConfig
if (cors !== false) {
// 跨域中间件
middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
}
// proxy
const { proxy } = serverConfig
if (proxy) {
// 代理中间件
middlewares.use(proxyMiddleware(httpServer, config))
}
// base
if (config.base !== '/') {
//base不是/,添加新的base
middlewares.use(baseMiddleware(server))
}
// open in editor support,监听下面的路由,如果监听到会打开编辑器中的文件,如vscode打开一个vue文件
middlewares.use('/__open-in-editor', launchEditorMiddleware())
// hmr reconnect ping
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
middlewares.use('/__vite_ping', function viteHMRPingMiddleware(_, res) {
res.end('pong')
})
// serve static files under /public
// this applies before the transform middleware so that these files are served
// as-is without transforms.
if (config.publicDir) {
// 公共文件
middlewares.use(servePublicMiddleware(config.publicDir))
}
// main transform middleware 转换中间件,插件的加载也在这里load,如plugin-vue
middlewares.use(transformMiddleware(server))
// serve static files
middlewares.use(serveRawFsMiddleware(server))
middlewares.use(serveStaticMiddleware(root, server))
// spa fallback
if (!middlewareMode || middlewareMode === 'html') {
middlewares.use(spaFallbackMiddleware(root))
}
// run post config hooks
// This is applied before the html middleware so that user middleware can
// serve custom content instead of index.html.
// 执行插件
postHooks.forEach((fn) => fn && fn())
if (!middlewareMode || middlewareMode === 'html') {
// transform index.html
middlewares.use(indexHtmlMiddleware(server))
// handle 404s
// 添加404
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
middlewares.use(function vite404Middleware(_, res) {
res.statusCode = 404
res.end()
})
}
// error handler 异常处理
middlewares.use(errorMiddleware(server, !!middlewareMode))
const runOptimize = async () => {
if (config.cacheDir) {
server._isRunningOptimizer = true
try {
server._optimizeDepsMetadata = await optimizeDeps(
config,
config.server.force || server._forceOptimizeOnRestart
)
} finally {
server._isRunningOptimizer = false
}
server._registerMissingImport = createMissingImporterRegisterFn(server)
}
}
if (!middlewareMode && httpServer) {
let isOptimized = false
// overwrite listen to run optimizer before server start
const listen = httpServer.listen.bind(httpServer)
httpServer.listen = (async (port: number, ...args: any[]) => {
if (!isOptimized) {
try {
await container.buildStart({})
// 运行优化
await runOptimize()
isOptimized = true
} catch (e) {
httpServer.emit('error', e)
return
}
}
return listen(port, ...args)
}) as any
} else {
await container.buildStart({})
await runOptimize()
}
return server
}
2.4.2 startServer 启动http服务
async function startServer(
server: ViteDevServer,
inlinePort?: number,
isRestart: boolean = false
): Promise<ViteDevServer> {
const httpServer = server.httpServer
if (!httpServer) {
throw new Error('Cannot call server.listen in middleware mode.')
}
const options = server.config.server
const port = inlinePort || options.port || 3000
const hostname = resolveHostname(options.host)
const protocol = options.https ? 'https' : 'http'
const info = server.config.logger.info
const base = server.config.base
const serverPort = await httpServerStart(httpServer, {
port,
strictPort: options.strictPort,
host: hostname.host,
logger: server.config.logger
})
// @ts-ignore 分析工具, 参数来源于cli里面配置的
const profileSession = global.__vite_profile_session
if (profileSession) {
profileSession.post('Profiler.stop', (err: any, { profile }: any) => {
// Write profile to disk, upload, etc.
if (!err) {
const outPath = path.resolve('./vite-profile.cpuprofile')
fs.writeFileSync(outPath, JSON.stringify(profile))
info(
chalk.yellow(` CPU profile written to ${chalk.white.dim(outPath)}\n`)
)
} else {
throw err
}
})
}
// 打开浏览器
if (options.open && !isRestart) {
const path = typeof options.open === 'string' ? options.open : base
openBrowser(
path.startsWith('http')
? path
: `${protocol}://${hostname.name}:${serverPort}${path}`,
true,
server.config.logger
)
}
return server
}
2.5 plugin-vue插件
2.5.1 vue-project中的vite.config.js配置
import { fileURLToPath } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
上述配置可以看出使用了vue插件,通过server服务监听路由,请求了vue文件,然后会调用插件进行处理
2.5.2 plugin-vue/src/index.tx
export default function vuePlugin(rawOptions: Options = {}): Plugin {
// 插件配置,可以传空
const {
include = /\.vue$/,
exclude,
customElement = /\.ce\.vue$/,
refTransform = false
} = rawOptions
const filter = createFilter(include, exclude)
const customElementFilter =
typeof customElement === 'boolean'
? () => customElement
: createFilter(customElement)
const refTransformFilter =
refTransform === false
? () => false
: refTransform === true
? createFilter(/\.(j|t)sx?$/, /node_modules/)
: createFilter(refTransform)
// compat for older versions
const canUseRefTransform = typeof compiler.shouldTransformRef === 'function'
let options: ResolvedOptions = {
isProduction: process.env.NODE_ENV === 'production',
...rawOptions,
include,
exclude,
customElement,
refTransform,
root: process.cwd(),
sourceMap: true
}
// Temporal handling for 2.7 breaking change
const isSSR = (opt: { ssr?: boolean } | boolean | undefined) =>
opt === undefined
? !!options.ssr
: typeof opt === 'boolean'
? opt
: opt?.ssr === true
// 返回插件配置
return {
name: 'vite:vue',
handleHotUpdate(ctx) {
if (!filter(ctx.file)) {
return
}
return handleHotUpdate(ctx, options)
},
config(config) {
return {
define: {
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false
},
ssr: {
external: ['vue', '@vue/server-renderer']
}
}
},
configResolved(config) {
options = {
...options,
root: config.root,
sourceMap: config.command === 'build' ? !!config.build.sourcemap : true,
isProduction: config.isProduction
}
},
configureServer(server) {
options.devServer = server
},
async resolveId(id) {
// component export helper
if (id === EXPORT_HELPER_ID) {
return id
}
// serve sub-part requests (*?vue) as virtual modules
if (parseVueRequest(id).query.vue) {
return id
}
},
// 加载vue文件
load(id, opt) {
const ssr = isSSR(opt)
if (id === EXPORT_HELPER_ID) {
return helperCode
}
const { filename, query } = parseVueRequest(id)
// select corresponding block for sub-part virtual modules
if (query.vue) {
if (query.src) {
return fs.readFileSync(filename, 'utf-8')
}
const descriptor = getDescriptor(filename, options)!
let block: SFCBlock | null | undefined
if (query.type === 'script') {
// handle <scrip> + <script setup> merge via compileScript()
block = getResolvedScript(descriptor, ssr)
} else if (query.type === 'template') {
block = descriptor.template!
} else if (query.type === 'style') {
block = descriptor.styles[query.index!]
} else if (query.index != null) {
block = descriptor.customBlocks[query.index]
}
if (block) {
return {
code: block.content,
map: block.map as any
}
}
}
},
// 转换文件
transform(code, id, opt) {
const ssr = isSSR(opt)
const { filename, query } = parseVueRequest(id)
if (query.raw) {
return
}
if (!filter(filename) && !query.vue) {
if (!query.vue && refTransformFilter(filename)) {
if (!canUseRefTransform) {
this.warn('refTransform requires @vue/compiler-sfc@^3.2.5.')
} else if (compiler.shouldTransformRef(code)) {
return compiler.transformRef(code, {
filename,
sourceMap: true
})
}
}
return
}
if (!query.vue) {
// main request
return transformMain(
code,
filename,
options,
this,
ssr,
customElementFilter(filename)
)
} else {
// sub block request
const descriptor = getDescriptor(filename, options)!
if (query.type === 'template') {
return transformTemplateAsModule(code, descriptor, options, this, ssr)
} else if (query.type === 'style') {
return transformStyle(
code,
descriptor,
Number(query.index),
options,
this
)
}
}
}
}
}
2.6 整体server流程
- 运行vite server命令
- 会启动server服务,hrm websocket服务,监听文件变化
- 注册一些中间件,插件初始化
- 当用户请求一个路由,会在http中请求一个.vue文件
会触发transformMiddleware中间件,里面做了对vue文件的处理
3. 总结
perf_hooks和inspector分别是node性能工具和调试工具,后面可以在深入了解
- 通过正则方式去匹配命令行参数/^(?:-d|—debug)$/.test(arg),这种写法可以不借助minimist等参数处理库,减少了包体积
-
4. 参考文档
- github.com/vitejs/vite
- http://nodejs.cn/api/perf_hooks.html
- https://nodejs.org/api/inspector.html