首页文章关于

vite-本地开发服务

viterollupdev server

vite本地服务在npm script一般是dev: vite这种形式所以在命令行中是使用根命令来表示:

cli vite command

packages/vite/src/node/cli.ts
ts
cli
.command('[root]', 'start dev server')
.alias('serve')
.alias('dev')
// .option('省略cli参数`)...
.action(async (root: string, options: BuildOptions & GlobalCLIOptions) => {
// 配置选项去重处理
filterDuplicateOptions(options)
const { createServer } = await import('./server')
try {
// 通过connect创建本地服务
const server = await createServer({
root,
base: options.base,
mode: options.mode,
configFile: options.config,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
optimizeDeps: { force: options.force },
server: cleanOptions(options),
})
if (!server.httpServer) {
throw new Error('HTTP server not available')
}
await server.listen()
const info = server.config.logger.info
const viteStartTime = global.__vite_start_time ?? false
const startupDurationString = viteStartTime
? colors.dim(
`ready in ${colors.reset(
colors.bold(Math.ceil(performance.now() - viteStartTime)),
)} ms`,
)
: ''
info(
`\n ${colors.green(
`${colors.bold('VITE')} v${VERSION}`,
)} ${startupDurationString}\n`,
{ clear: !server.config.logger.hasWarned },
)
server.printUrls()
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}`), {
error: e,
})
stopProfiler(logger.info)
process.exit(1)
}
})

dev server process

本地服务开启首先通过createServer创建本地web服务

createServer

packages/vite/src/node/server/index.ts
ts
export async function createServer(
inlineConfig: InlineConfig = {},
): Promise<ViteDevServer> {
// 解析配置:处理用户配置及预设配置合并(省略部分细节)
// 1. 解析vite config文件,通过esbuild对config文件进行build并获取处理后的用户配置信息,
// 如果是函数则传入configEnv进行求值返回否则则直接返回该对象,最终和默认配置进行合并.
// 2. 对worker plugins和user plugins进行扁平化、根据apply属性进行过滤再按照enforce
// 的先后顺序进行排序处理.
// 3. 通过runConfigHook对plugins.config进行hook排序并通过resolvePlugins注入内置plugins,
// 如果是handler就调用求值并合并,否则将作为对象直接返回
// 4. 通过resolveOptions对resolve选项进行赋值(mainFields、extensions、alias...)
// 5. 通过createResolver对optimizer和css中的@imports进行处理
// 6. 对worker plugins进行合并且根据order排序再runConfigHook,根据返回的config结果对
// worker选项进行赋值操作
// 7. 最终对以上的各个解析处理后的配置选项进行合并汇总最终返回
const config = await resolveConfig(inlineConfig, 'serve')
const { root, server: serverConfig } = config
const httpsOptions = await resolveHttpsConfig(config.server.https)
const { middlewareMode } = serverConfig
const resolvedWatchOptions = resolveChokidarOptions(config, {
disableGlobbing: true,
...serverConfig.watch,
})
const middlewares = connect() as Connect.Server
const httpServer = middlewareMode
? null
: await resolveHttpServer(serverConfig, middlewares, httpsOptions)
const ws = createWebSocketServer(httpServer, config, httpsOptions)
if (httpServer) {
setClientErrorHandler(httpServer, config.logger)
}
const watcher = chokidar.watch(
path.resolve(root),
resolvedWatchOptions,
) as FSWatcher
const moduleGraph: ModuleGraph = new ModuleGraph((url, ssr) =>
container.resolveId(url, undefined, { ssr }),
)
const container = await createPluginContainer(config, moduleGraph, watcher)
const closeHttpServer = createServerCloseFn(httpServer)
let exitProcess: () => void
const server: ViteDevServer = {
config,
middlewares,
httpServer,
watcher,
pluginContainer: container,
ws,
moduleGraph,
resolvedUrls: null, // will be set on listen
ssrTransform(
code: string,
inMap: SourceMap | null,
url: string,
originalCode = code,
) {
return ssrTransform(code, inMap, url, originalCode, server.config)
},
transformRequest(url, options) {
return transformRequest(url, server, options)
},
transformIndexHtml: null!, // to be immediately set
async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) {
if (isDepsOptimizerEnabled(config, true)) {
await initDevSsrDepsOptimizer(config, server)
}
await updateCjsSsrExternals(server)
return ssrLoadModule(
url,
server,
undefined,
undefined,
opts?.fixStacktrace,
)
},
ssrFixStacktrace(e) {
ssrFixStacktrace(e, moduleGraph)
},
ssrRewriteStacktrace(stack: string) {
return ssrRewriteStacktrace(stack, moduleGraph)
},
async reloadModule(module) {
if (serverConfig.hmr !== false && module.file) {
updateModules(module.file, [module], Date.now(), server)
}
},
async listen(port?: number, isRestart?: boolean) {
await startServer(server, port, isRestart)
if (httpServer) {
server.resolvedUrls = await resolveServerUrls(
httpServer,
config.server,
config,
)
}
return server
},
async close() {
if (!middlewareMode) {
process.off('SIGTERM', exitProcess)
if (process.env.CI !== 'true') {
process.stdin.off('end', exitProcess)
}
}
await Promise.allSettled([
watcher.close(),
ws.close(),
container.close(),
getDepsOptimizer(server.config)?.close(),
getDepsOptimizer(server.config, true)?.close(),
closeHttpServer(),
])
server.resolvedUrls = null
},
printUrls() {
if (server.resolvedUrls) {
printServerUrls(
server.resolvedUrls,
serverConfig.host,
config.logger.info,
)
} else if (middlewareMode) {
throw new Error('cannot print server URLs in middleware mode.')
} else {
throw new Error(
'cannot print server URLs before server.listen is called.',
)
}
},
async restart(forceOptimize?: boolean) {
if (!server._restartPromise) {
server._forceOptimizeOnRestart = !!forceOptimize
server._restartPromise = restartServer(server).finally(() => {
server._restartPromise = null
server._forceOptimizeOnRestart = false
})
}
return server._restartPromise
},
_ssrExternals: null,
_restartPromise: null,
_importGlobMap: new Map(),
_forceOptimizeOnRestart: false,
_pendingRequests: new Map(),
_fsDenyGlob: picomatch(config.server.fs.deny, { matchBase: true }),
_shortcutsOptions: undefined,
}
server.transformIndexHtml = createDevHtmlTransformFn(server)
if (!middlewareMode) {
exitProcess = async () => {
try {
await server.close()
} finally {
process.exit()
}
}
process.once('SIGTERM', exitProcess)
if (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')) {
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)
})
ws.on('vite:invalidate', async ({ path, message }: InvalidatePayload) => {
const mod = moduleGraph.urlToModuleMap.get(path)
if (mod && mod.isSelfAccepting && mod.lastHMRTimestamp > 0) {
config.logger.info(
colors.yellow(`hmr invalidate `) +
colors.dim(path) +
(message ? ` ${message}` : ''),
{ timestamp: true },
)
const file = getShortName(mod.file!, config.root)
updateModules(
file,
[...mod.importers],
mod.lastHMRTimestamp,
server,
true,
)
}
})
if (!middlewareMode && httpServer) {
httpServer.once('listening', () => {
// update actual port since this may be different from initial value
serverConfig.port = (httpServer.address() as net.AddressInfo).port
})
}
// apply server configuration hooks from plugins
const postHooks: ((() => void) | void)[] = []
for (const hook of config.getSortedPluginHooks('configureServer')) {
postHooks.push(await hook(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, proxy, config))
}
// base
if (config.base !== '/') {
middlewares.use(baseMiddleware(server))
}
// open in editor support
middlewares.use('/__open-in-editor', launchEditorMiddleware())
// 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, config.server.headers),
)
}
// main transform middleware
middlewares.use(transformMiddleware(server))
// serve static files
middlewares.use(serveRawFsMiddleware(server))
middlewares.use(serveStaticMiddleware(root, server))
// html fallback
if (config.appType === 'spa' || config.appType === 'mpa') {
middlewares.use(htmlFallbackMiddleware(root, config.appType === 'spa'))
}
// 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 (config.appType === 'spa' || config.appType === 'mpa') {
// transform index.html
middlewares.use(indexHtmlMiddleware(server))
// 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()
})
}
// 错误处理
middlewares.use(errorMiddleware(server, middlewareMode))
let initingServer: Promise<void> | undefined
let serverInited = false
const initServer = async () => {
if (serverInited) {
return
}
if (initingServer) {
return initingServer
}
initingServer = (async function () {
await container.buildStart({})
if (isDepsOptimizerEnabled(config, false)) {
// non-ssr
await initDepsOptimizer(config, server)
}
initingServer = undefined
serverInited = true
})()
return initingServer
}
if (!middlewareMode && httpServer) {
// overwrite listen to init optimizer before server start
const listen = httpServer.listen.bind(httpServer)
httpServer.listen = (async (port: number, ...args: any[]) => {
try {
await initServer()
} catch (e) {
httpServer.emit('error', e)
return
}
return listen(port, ...args)
}) as any
} else {
await initServer()
}
return server
}
Copyright © 2023, Daily learning records and sharing. Build by