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}