源码学习目录

1. 前言

1.1 环境

  1. 操作系统: macOS 11.5.2
  2. 浏览器: Chrome 94.0.4606.81
  3. vite 2.7.1

    1.2 阅读该文章可以get以下知识点

  • 学会 vite 简单原理

    2. 开始

    2.1 项目结构

    image.png

    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执行文件
    image.png

    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() }

  1. <a name="FA8Lf"></a>
  2. ## 2.3 vite src/node/cli.ts
  3. ```typescript
  4. // cli工具
  5. import { cac } from 'cac'
  6. // log输出带颜色
  7. import chalk from 'chalk'
  8. // 性能工具
  9. import { performance } from 'perf_hooks'
  10. // 打包配置
  11. import { BuildOptions } from './build'
  12. // 服务配置
  13. import { ServerOptions } from './server'
  14. // 日志和等级
  15. import { createLogger, LogLevel } from './logger'
  16. // 解析配置
  17. import { resolveConfig } from '.'
  18. import { preview } from './preview'
  19. const cli = cac('vite')
  20. // global options 全局配置
  21. interface GlobalCLIOptions {
  22. '--'?: string[]
  23. c?: boolean | string
  24. config?: string
  25. base?: string
  26. l?: LogLevel
  27. logLevel?: LogLevel
  28. clearScreen?: boolean
  29. d?: boolean | string
  30. debug?: boolean | string
  31. f?: string
  32. filter?: string
  33. m?: string
  34. mode?: string
  35. }
  36. /**
  37. * removing global flags before passing as command specific sub-configs
  38. */
  39. function cleanOptions<Options extends GlobalCLIOptions>(
  40. options: Options
  41. ): Omit<Options, keyof GlobalCLIOptions> {
  42. const ret = { ...options }
  43. delete ret['--']
  44. delete ret.c
  45. delete ret.config
  46. delete ret.base
  47. delete ret.l
  48. delete ret.logLevel
  49. delete ret.clearScreen
  50. delete ret.d
  51. delete ret.debug
  52. delete ret.f
  53. delete ret.filter
  54. delete ret.m
  55. delete ret.mode
  56. return ret
  57. }
  58. // 基础配置说明 cli.option都是用来做help说明的
  59. cli
  60. .option('-c, --config <file>', `[string] use specified config file`)
  61. .option('--base <path>', `[string] public base path (default: /)`)
  62. .option('-l, --logLevel <level>', `[string] info | warn | error | silent`)
  63. .option('--clearScreen', `[boolean] allow/disable clear screen when logging`)
  64. .option('-d, --debug [feat]', `[string | boolean] show debug logs`)
  65. .option('-f, --filter <filter>', `[string] filter debug logs`)
  66. .option('-m, --mode <mode>', `[string] set env mode`)
  67. // dev
  68. // 执行cli serve 和 dev就会匹配到执行里面的action
  69. cli
  70. .command('[root]') // default command,如果不输入命令,直接执行这个
  71. .alias('serve') // the command is called 'serve' in Vite's API
  72. .alias('dev') // alias to align with the script name
  73. .option('--host [host]', `[string] specify hostname`)
  74. .option('--port <port>', `[number] specify port`)
  75. .option('--https', `[boolean] use TLS + HTTP/2`)
  76. .option('--open [path]', `[boolean | string] open browser on startup`)
  77. .option('--cors', `[boolean] enable CORS`)
  78. .option('--strictPort', `[boolean] exit if specified port is already in use`)
  79. .option(
  80. '--force',
  81. `[boolean] force the optimizer to ignore the cache and re-bundle`
  82. )
  83. .action(async (root: string, options: ServerOptions & GlobalCLIOptions) => {
  84. // output structure is preserved even after bundling so require()
  85. // is ok here 在代码里面使用import
  86. const { createServer } = await import('./server')
  87. try {
  88. // 创建server对象
  89. const server = await createServer({
  90. root,
  91. base: options.base,
  92. mode: options.mode,
  93. configFile: options.config,
  94. logLevel: options.logLevel,
  95. clearScreen: options.clearScreen,
  96. server: cleanOptions(options)
  97. })
  98. if (!server.httpServer) {
  99. throw new Error('HTTP server not available')
  100. }
  101. await server.listen()
  102. const info = server.config.logger.info
  103. info(
  104. chalk.cyan(`\n vite v${require('vite/package.json').version}`) +
  105. chalk.green(` dev server running at:\n`),
  106. {
  107. clear: !server.config.logger.hasWarned
  108. }
  109. )
  110. server.printUrls()
  111. // @ts-ignore 性能监控的内容,这个值从bin/vite.js中设置的,用来获取开启服务的事件
  112. if (global.__vite_start_time) {
  113. // @ts-ignore
  114. const startupDuration = performance.now() - global.__vite_start_time
  115. info(`\n ${chalk.cyan(`ready in ${Math.ceil(startupDuration)}ms.`)}\n`)
  116. }
  117. } catch (e) {
  118. createLogger(options.logLevel).error(
  119. chalk.red(`error when starting dev server:\n${e.stack}`),
  120. { error: e }
  121. )
  122. process.exit(1)
  123. }
  124. })
  125. // build
  126. // 打包相关的
  127. cli
  128. .command('build [root]')
  129. .option('--target <target>', `[string] transpile target (default: 'modules')`)
  130. .option('--outDir <dir>', `[string] output directory (default: dist)`)
  131. .option(
  132. '--assetsDir <dir>',
  133. `[string] directory under outDir to place assets in (default: _assets)`
  134. )
  135. .option(
  136. '--assetsInlineLimit <number>',
  137. `[number] static asset base64 inline threshold in bytes (default: 4096)`
  138. )
  139. .option(
  140. '--ssr [entry]',
  141. `[string] build specified entry for server-side rendering`
  142. )
  143. .option(
  144. '--sourcemap',
  145. `[boolean] output source maps for build (default: false)`
  146. )
  147. .option(
  148. '--minify [minifier]',
  149. `[boolean | "terser" | "esbuild"] enable/disable minification, ` +
  150. `or specify minifier to use (default: esbuild)`
  151. )
  152. .option('--manifest', `[boolean] emit build manifest json`)
  153. .option('--ssrManifest', `[boolean] emit ssr manifest json`)
  154. .option(
  155. '--emptyOutDir',
  156. `[boolean] force empty outDir when it's outside of root`
  157. )
  158. .option('-w, --watch', `[boolean] rebuilds when modules have changed on disk`)
  159. .action(async (root: string, options: BuildOptions & GlobalCLIOptions) => {
  160. // 调用build函数
  161. const { build } = await import('./build')
  162. const buildOptions: BuildOptions = cleanOptions(options)
  163. try {
  164. await build({
  165. root,
  166. base: options.base,
  167. mode: options.mode,
  168. configFile: options.config,
  169. logLevel: options.logLevel,
  170. clearScreen: options.clearScreen,
  171. build: buildOptions
  172. })
  173. } catch (e) {
  174. createLogger(options.logLevel).error(
  175. chalk.red(`error during build:\n${e.stack}`),
  176. { error: e }
  177. )
  178. process.exit(1)
  179. }
  180. })
  181. // optimize 优化
  182. cli
  183. .command('optimize [root]')
  184. .option(
  185. '--force',
  186. `[boolean] force the optimizer to ignore the cache and re-bundle`
  187. )
  188. .action(
  189. async (root: string, options: { force?: boolean } & GlobalCLIOptions) => {
  190. const { optimizeDeps } = await import('./optimizer')
  191. try {
  192. const config = await resolveConfig(
  193. {
  194. root,
  195. base: options.base,
  196. configFile: options.config,
  197. logLevel: options.logLevel
  198. },
  199. 'build',
  200. 'development'
  201. )
  202. await optimizeDeps(config, options.force, true)
  203. } catch (e) {
  204. createLogger(options.logLevel).error(
  205. chalk.red(`error when optimizing deps:\n${e.stack}`),
  206. { error: e }
  207. )
  208. process.exit(1)
  209. }
  210. }
  211. )
  212. cli
  213. .command('preview [root]')
  214. .option('--host [host]', `[string] specify hostname`)
  215. .option('--port <port>', `[number] specify port`)
  216. .option('--strictPort', `[boolean] exit if specified port is already in use`)
  217. .option('--https', `[boolean] use TLS + HTTP/2`)
  218. .option('--open [path]', `[boolean | string] open browser on startup`)
  219. .action(
  220. async (
  221. root: string,
  222. options: {
  223. host?: string | boolean
  224. port?: number
  225. https?: boolean
  226. open?: boolean | string
  227. strictPort?: boolean
  228. } & GlobalCLIOptions
  229. ) => {
  230. try {
  231. const server = await preview({
  232. root,
  233. base: options.base,
  234. configFile: options.config,
  235. logLevel: options.logLevel,
  236. preview: {
  237. port: options.port,
  238. strictPort: options.strictPort,
  239. host: options.host,
  240. https: options.https,
  241. open: options.open
  242. }
  243. })
  244. server.printUrls()
  245. } catch (e) {
  246. createLogger(options.logLevel).error(
  247. chalk.red(`error when starting preview server:\n${e.stack}`),
  248. { error: e }
  249. )
  250. process.exit(1)
  251. }
  252. }
  253. )
  254. // cli.js --help 可以查看上面配置的option
  255. cli.help()
  256. // cli.js --version 可以查看当前版本
  257. cli.version(require('../../package.json').version)
  258. // 获取命令的值
  259. cli.parse()

cac cli工具

2.4 vite src/node/server/index.ts

2.4.1 createServer 创建server对象

  1. import fs from 'fs'
  2. import path from 'path'
  3. import * as net from 'net'
  4. import * as http from 'http'
  5. import connect from 'connect'
  6. import corsMiddleware from 'cors'
  7. import chalk from 'chalk'
  8. // 获取ip信息
  9. import { AddressInfo } from 'net'
  10. // 监听文件变化
  11. import chokidar from 'chokidar'
  12. import {
  13. resolveHttpsConfig,
  14. resolveHttpServer,
  15. httpServerStart,
  16. CommonServerOptions
  17. } from '../http'
  18. import { resolveConfig, InlineConfig, ResolvedConfig } from '../config'
  19. import { createPluginContainer, PluginContainer } from './pluginContainer'
  20. import { FSWatcher, WatchOptions } from 'types/chokidar'
  21. import { createWebSocketServer, WebSocketServer } from './ws'
  22. import { baseMiddleware } from './middlewares/base'
  23. import { proxyMiddleware } from './middlewares/proxy'
  24. import { spaFallbackMiddleware } from './middlewares/spaFallback'
  25. import { transformMiddleware } from './middlewares/transform'
  26. import {
  27. createDevHtmlTransformFn,
  28. indexHtmlMiddleware
  29. } from './middlewares/indexHtml'
  30. import {
  31. serveRawFsMiddleware,
  32. servePublicMiddleware,
  33. serveStaticMiddleware
  34. } from './middlewares/static'
  35. import { timeMiddleware } from './middlewares/time'
  36. import { ModuleGraph, ModuleNode } from './moduleGraph'
  37. import { Connect } from 'types/connect'
  38. import { ensureLeadingSlash, normalizePath } from '../utils'
  39. import { errorMiddleware, prepareError } from './middlewares/error'
  40. import { handleHMRUpdate, HmrOptions, handleFileAddUnlink } from './hmr'
  41. import { openBrowser } from './openBrowser'
  42. import launchEditorMiddleware from 'launch-editor-middleware'
  43. import {
  44. TransformOptions,
  45. TransformResult,
  46. transformRequest
  47. } from './transformRequest'
  48. import {
  49. transformWithEsbuild,
  50. ESBuildTransformResult
  51. } from '../plugins/esbuild'
  52. import { TransformOptions as EsbuildTransformOptions } from 'esbuild'
  53. import { DepOptimizationMetadata, optimizeDeps } from '../optimizer'
  54. import { ssrLoadModule } from '../ssr/ssrModuleLoader'
  55. import { resolveSSRExternal } from '../ssr/ssrExternal'
  56. import {
  57. rebindErrorStacktrace,
  58. ssrRewriteStacktrace
  59. } from '../ssr/ssrStacktrace'
  60. import { ssrTransform } from '../ssr/ssrTransform'
  61. import { createMissingImporterRegisterFn } from '../optimizer/registerMissing'
  62. import { resolveHostname } from '../utils'
  63. import { searchForWorkspaceRoot } from './searchRoot'
  64. import { CLIENT_DIR } from '../constants'
  65. import { printCommonServerUrls } from '../logger'
  66. import { performance } from 'perf_hooks'
  67. import { invalidatePackageData } from '../packages'
  68. import { SourceMap } from 'rollup'
  69. export { searchForWorkspaceRoot } from './searchRoot'
  70. // cli里面调用的createServer
  71. export async function createServer(
  72. // 配置项
  73. inlineConfig: InlineConfig = {}
  74. ): Promise<ViteDevServer> {
  75. // 解析配置项
  76. const config = await resolveConfig(inlineConfig, 'serve', 'development')
  77. const root = config.root
  78. const serverConfig = config.server
  79. // 解析http配置
  80. const httpsOptions = await resolveHttpsConfig(config.server.https)
  81. let { middlewareMode } = serverConfig
  82. if (middlewareMode === true) {
  83. middlewareMode = 'ssr'
  84. }
  85. const middlewares = connect() as Connect.Server
  86. // 创建http服务
  87. const httpServer = middlewareMode
  88. ? null
  89. : await resolveHttpServer(serverConfig, middlewares, httpsOptions)
  90. // 创建一个webSocket服务,用来做hrm做的
  91. const ws = createWebSocketServer(httpServer, config, httpsOptions)
  92. // 监听服务
  93. const { ignored = [], ...watchOptions } = serverConfig.watch || {}
  94. const watcher = chokidar.watch(path.resolve(root), {
  95. ignored: [
  96. '**/node_modules/**',
  97. '**/.git/**',
  98. ...(Array.isArray(ignored) ? ignored : [ignored])
  99. ],
  100. ignoreInitial: true,
  101. ignorePermissionErrors: true,
  102. disableGlobbing: true,
  103. ...watchOptions
  104. }) as FSWatcher
  105. const moduleGraph: ModuleGraph = new ModuleGraph((url) =>
  106. container.resolveId(url)
  107. )
  108. // 插件容器
  109. const container = await createPluginContainer(config, moduleGraph, watcher)
  110. // 关闭http函数
  111. const closeHttpServer = createServerCloseFn(httpServer)
  112. // eslint-disable-next-line prefer-const
  113. let exitProcess: () => void
  114. // server对象,包含的一些属性和方法
  115. const server: ViteDevServer = {
  116. config,
  117. middlewares,
  118. get app() {
  119. config.logger.warn(
  120. `ViteDevServer.app is deprecated. Use ViteDevServer.middlewares instead.`
  121. )
  122. return middlewares
  123. },
  124. httpServer,
  125. watcher,
  126. pluginContainer: container,
  127. ws,
  128. moduleGraph,
  129. ssrTransform,
  130. transformWithEsbuild,
  131. transformRequest(url, options) {
  132. return transformRequest(url, server, options)
  133. },
  134. transformIndexHtml: null!, // to be immediately set
  135. ssrLoadModule(url) {
  136. server._ssrExternals ||= resolveSSRExternal(
  137. config,
  138. server._optimizeDepsMetadata
  139. ? Object.keys(server._optimizeDepsMetadata.optimized)
  140. : []
  141. )
  142. return ssrLoadModule(url, server)
  143. },
  144. ssrFixStacktrace(e) {
  145. if (e.stack) {
  146. const stacktrace = ssrRewriteStacktrace(e.stack, moduleGraph)
  147. rebindErrorStacktrace(e, stacktrace)
  148. }
  149. },
  150. // 这里做监听,开启server
  151. listen(port?: number, isRestart?: boolean) {
  152. return startServer(server, port, isRestart)
  153. },
  154. // 关闭服务
  155. async close() {
  156. // 执行关闭信号量
  157. process.off('SIGTERM', exitProcess)
  158. if (!middlewareMode && process.env.CI !== 'true') {
  159. process.stdin.off('end', exitProcess)
  160. }
  161. // 关闭所有的服务
  162. await Promise.all([
  163. // 监听器
  164. watcher.close(),
  165. // hrm服务
  166. ws.close(),
  167. // 插件容器
  168. container.close(),
  169. // 关闭http服务
  170. closeHttpServer()
  171. ])
  172. },
  173. printUrls() {
  174. if (httpServer) {
  175. printCommonServerUrls(httpServer, config.server, config)
  176. } else {
  177. throw new Error('cannot print server URLs in middleware mode.')
  178. }
  179. },
  180. // 重启
  181. async restart(forceOptimize: boolean) {
  182. if (!server._restartPromise) {
  183. server._forceOptimizeOnRestart = !!forceOptimize
  184. server._restartPromise = restartServer(server).finally(() => {
  185. server._restartPromise = null
  186. server._forceOptimizeOnRestart = false
  187. })
  188. }
  189. return server._restartPromise
  190. },
  191. _optimizeDepsMetadata: null,
  192. _ssrExternals: null,
  193. _globImporters: Object.create(null),
  194. _restartPromise: null,
  195. _forceOptimizeOnRestart: false,
  196. _isRunningOptimizer: false,
  197. _registerMissingImport: null,
  198. _pendingReload: null,
  199. _pendingRequests: new Map()
  200. }
  201. // html转换函数
  202. server.transformIndexHtml = createDevHtmlTransformFn(server)
  203. exitProcess = async () => {
  204. try {
  205. await server.close()
  206. } finally {
  207. process.exit(0)
  208. }
  209. }
  210. // 一旦接受到信号,执行exitProcess
  211. process.once('SIGTERM', exitProcess)
  212. if (!middlewareMode && process.env.CI !== 'true') {
  213. process.stdin.on('end', exitProcess)
  214. }
  215. // 缓存包数据
  216. const { packageCache } = config
  217. const setPackageData = packageCache.set.bind(packageCache)
  218. packageCache.set = (id, pkg) => {
  219. if (id.endsWith('.json')) {
  220. // json文件放入watcher监听
  221. watcher.add(id)
  222. }
  223. return setPackageData(id, pkg)
  224. }
  225. // 监听文件变化
  226. watcher.on('change', async (file) => {
  227. file = normalizePath(file)
  228. if (file.endsWith('/package.json')) {
  229. return invalidatePackageData(packageCache, file)
  230. }
  231. // invalidate module graph cache on file change
  232. moduleGraph.onFileChange(file)
  233. if (serverConfig.hmr !== false) {
  234. try {
  235. await handleHMRUpdate(file, server)
  236. } catch (err) {
  237. ws.send({
  238. type: 'error',
  239. err: prepareError(err)
  240. })
  241. }
  242. }
  243. })
  244. watcher.on('add', (file) => {
  245. handleFileAddUnlink(normalizePath(file), server)
  246. })
  247. watcher.on('unlink', (file) => {
  248. handleFileAddUnlink(normalizePath(file), server, true)
  249. })
  250. if (!middlewareMode && httpServer) {
  251. httpServer.once('listening', () => {
  252. // update actual port since this may be different from initial value
  253. serverConfig.port = (httpServer.address() as AddressInfo).port
  254. })
  255. }
  256. // apply server configuration hooks from plugins
  257. // 将插件放入postHooks队列中,执行plugin.configureServer将server实例注入到插件中
  258. const postHooks: ((() => void) | void)[] = []
  259. for (const plugin of config.plugins) {
  260. if (plugin.configureServer) {
  261. postHooks.push(await plugin.configureServer(server))
  262. }
  263. }
  264. // Internal middlewares ------------------------------------------------------
  265. // request timer
  266. if (process.env.DEBUG) {
  267. // 时间中间件
  268. middlewares.use(timeMiddleware(root))
  269. }
  270. // cors (enabled by default)
  271. const { cors } = serverConfig
  272. if (cors !== false) {
  273. // 跨域中间件
  274. middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
  275. }
  276. // proxy
  277. const { proxy } = serverConfig
  278. if (proxy) {
  279. // 代理中间件
  280. middlewares.use(proxyMiddleware(httpServer, config))
  281. }
  282. // base
  283. if (config.base !== '/') {
  284. //base不是/,添加新的base
  285. middlewares.use(baseMiddleware(server))
  286. }
  287. // open in editor support,监听下面的路由,如果监听到会打开编辑器中的文件,如vscode打开一个vue文件
  288. middlewares.use('/__open-in-editor', launchEditorMiddleware())
  289. // hmr reconnect ping
  290. // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
  291. middlewares.use('/__vite_ping', function viteHMRPingMiddleware(_, res) {
  292. res.end('pong')
  293. })
  294. // serve static files under /public
  295. // this applies before the transform middleware so that these files are served
  296. // as-is without transforms.
  297. if (config.publicDir) {
  298. // 公共文件
  299. middlewares.use(servePublicMiddleware(config.publicDir))
  300. }
  301. // main transform middleware 转换中间件,插件的加载也在这里load,如plugin-vue
  302. middlewares.use(transformMiddleware(server))
  303. // serve static files
  304. middlewares.use(serveRawFsMiddleware(server))
  305. middlewares.use(serveStaticMiddleware(root, server))
  306. // spa fallback
  307. if (!middlewareMode || middlewareMode === 'html') {
  308. middlewares.use(spaFallbackMiddleware(root))
  309. }
  310. // run post config hooks
  311. // This is applied before the html middleware so that user middleware can
  312. // serve custom content instead of index.html.
  313. // 执行插件
  314. postHooks.forEach((fn) => fn && fn())
  315. if (!middlewareMode || middlewareMode === 'html') {
  316. // transform index.html
  317. middlewares.use(indexHtmlMiddleware(server))
  318. // handle 404s
  319. // 添加404
  320. // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
  321. middlewares.use(function vite404Middleware(_, res) {
  322. res.statusCode = 404
  323. res.end()
  324. })
  325. }
  326. // error handler 异常处理
  327. middlewares.use(errorMiddleware(server, !!middlewareMode))
  328. const runOptimize = async () => {
  329. if (config.cacheDir) {
  330. server._isRunningOptimizer = true
  331. try {
  332. server._optimizeDepsMetadata = await optimizeDeps(
  333. config,
  334. config.server.force || server._forceOptimizeOnRestart
  335. )
  336. } finally {
  337. server._isRunningOptimizer = false
  338. }
  339. server._registerMissingImport = createMissingImporterRegisterFn(server)
  340. }
  341. }
  342. if (!middlewareMode && httpServer) {
  343. let isOptimized = false
  344. // overwrite listen to run optimizer before server start
  345. const listen = httpServer.listen.bind(httpServer)
  346. httpServer.listen = (async (port: number, ...args: any[]) => {
  347. if (!isOptimized) {
  348. try {
  349. await container.buildStart({})
  350. // 运行优化
  351. await runOptimize()
  352. isOptimized = true
  353. } catch (e) {
  354. httpServer.emit('error', e)
  355. return
  356. }
  357. }
  358. return listen(port, ...args)
  359. }) as any
  360. } else {
  361. await container.buildStart({})
  362. await runOptimize()
  363. }
  364. return server
  365. }

2.4.2 startServer 启动http服务

  1. async function startServer(
  2. server: ViteDevServer,
  3. inlinePort?: number,
  4. isRestart: boolean = false
  5. ): Promise<ViteDevServer> {
  6. const httpServer = server.httpServer
  7. if (!httpServer) {
  8. throw new Error('Cannot call server.listen in middleware mode.')
  9. }
  10. const options = server.config.server
  11. const port = inlinePort || options.port || 3000
  12. const hostname = resolveHostname(options.host)
  13. const protocol = options.https ? 'https' : 'http'
  14. const info = server.config.logger.info
  15. const base = server.config.base
  16. const serverPort = await httpServerStart(httpServer, {
  17. port,
  18. strictPort: options.strictPort,
  19. host: hostname.host,
  20. logger: server.config.logger
  21. })
  22. // @ts-ignore 分析工具, 参数来源于cli里面配置的
  23. const profileSession = global.__vite_profile_session
  24. if (profileSession) {
  25. profileSession.post('Profiler.stop', (err: any, { profile }: any) => {
  26. // Write profile to disk, upload, etc.
  27. if (!err) {
  28. const outPath = path.resolve('./vite-profile.cpuprofile')
  29. fs.writeFileSync(outPath, JSON.stringify(profile))
  30. info(
  31. chalk.yellow(` CPU profile written to ${chalk.white.dim(outPath)}\n`)
  32. )
  33. } else {
  34. throw err
  35. }
  36. })
  37. }
  38. // 打开浏览器
  39. if (options.open && !isRestart) {
  40. const path = typeof options.open === 'string' ? options.open : base
  41. openBrowser(
  42. path.startsWith('http')
  43. ? path
  44. : `${protocol}://${hostname.name}:${serverPort}${path}`,
  45. true,
  46. server.config.logger
  47. )
  48. }
  49. return server
  50. }

上述代码执行server服务

2.5 plugin-vue插件

2.5.1 vue-project中的vite.config.js配置

  1. import { fileURLToPath } from 'url'
  2. import { defineConfig } from 'vite'
  3. import vue from '@vitejs/plugin-vue'
  4. // https://vitejs.dev/config/
  5. export default defineConfig({
  6. plugins: [vue()],
  7. resolve: {
  8. alias: {
  9. '@': fileURLToPath(new URL('./src', import.meta.url))
  10. }
  11. }
  12. })

上述配置可以看出使用了vue插件,通过server服务监听路由,请求了vue文件,然后会调用插件进行处理

2.5.2 plugin-vue/src/index.tx

  1. export default function vuePlugin(rawOptions: Options = {}): Plugin {
  2. // 插件配置,可以传空
  3. const {
  4. include = /\.vue$/,
  5. exclude,
  6. customElement = /\.ce\.vue$/,
  7. refTransform = false
  8. } = rawOptions
  9. const filter = createFilter(include, exclude)
  10. const customElementFilter =
  11. typeof customElement === 'boolean'
  12. ? () => customElement
  13. : createFilter(customElement)
  14. const refTransformFilter =
  15. refTransform === false
  16. ? () => false
  17. : refTransform === true
  18. ? createFilter(/\.(j|t)sx?$/, /node_modules/)
  19. : createFilter(refTransform)
  20. // compat for older versions
  21. const canUseRefTransform = typeof compiler.shouldTransformRef === 'function'
  22. let options: ResolvedOptions = {
  23. isProduction: process.env.NODE_ENV === 'production',
  24. ...rawOptions,
  25. include,
  26. exclude,
  27. customElement,
  28. refTransform,
  29. root: process.cwd(),
  30. sourceMap: true
  31. }
  32. // Temporal handling for 2.7 breaking change
  33. const isSSR = (opt: { ssr?: boolean } | boolean | undefined) =>
  34. opt === undefined
  35. ? !!options.ssr
  36. : typeof opt === 'boolean'
  37. ? opt
  38. : opt?.ssr === true
  39. // 返回插件配置
  40. return {
  41. name: 'vite:vue',
  42. handleHotUpdate(ctx) {
  43. if (!filter(ctx.file)) {
  44. return
  45. }
  46. return handleHotUpdate(ctx, options)
  47. },
  48. config(config) {
  49. return {
  50. define: {
  51. __VUE_OPTIONS_API__: true,
  52. __VUE_PROD_DEVTOOLS__: false
  53. },
  54. ssr: {
  55. external: ['vue', '@vue/server-renderer']
  56. }
  57. }
  58. },
  59. configResolved(config) {
  60. options = {
  61. ...options,
  62. root: config.root,
  63. sourceMap: config.command === 'build' ? !!config.build.sourcemap : true,
  64. isProduction: config.isProduction
  65. }
  66. },
  67. configureServer(server) {
  68. options.devServer = server
  69. },
  70. async resolveId(id) {
  71. // component export helper
  72. if (id === EXPORT_HELPER_ID) {
  73. return id
  74. }
  75. // serve sub-part requests (*?vue) as virtual modules
  76. if (parseVueRequest(id).query.vue) {
  77. return id
  78. }
  79. },
  80. // 加载vue文件
  81. load(id, opt) {
  82. const ssr = isSSR(opt)
  83. if (id === EXPORT_HELPER_ID) {
  84. return helperCode
  85. }
  86. const { filename, query } = parseVueRequest(id)
  87. // select corresponding block for sub-part virtual modules
  88. if (query.vue) {
  89. if (query.src) {
  90. return fs.readFileSync(filename, 'utf-8')
  91. }
  92. const descriptor = getDescriptor(filename, options)!
  93. let block: SFCBlock | null | undefined
  94. if (query.type === 'script') {
  95. // handle <scrip> + <script setup> merge via compileScript()
  96. block = getResolvedScript(descriptor, ssr)
  97. } else if (query.type === 'template') {
  98. block = descriptor.template!
  99. } else if (query.type === 'style') {
  100. block = descriptor.styles[query.index!]
  101. } else if (query.index != null) {
  102. block = descriptor.customBlocks[query.index]
  103. }
  104. if (block) {
  105. return {
  106. code: block.content,
  107. map: block.map as any
  108. }
  109. }
  110. }
  111. },
  112. // 转换文件
  113. transform(code, id, opt) {
  114. const ssr = isSSR(opt)
  115. const { filename, query } = parseVueRequest(id)
  116. if (query.raw) {
  117. return
  118. }
  119. if (!filter(filename) && !query.vue) {
  120. if (!query.vue && refTransformFilter(filename)) {
  121. if (!canUseRefTransform) {
  122. this.warn('refTransform requires @vue/compiler-sfc@^3.2.5.')
  123. } else if (compiler.shouldTransformRef(code)) {
  124. return compiler.transformRef(code, {
  125. filename,
  126. sourceMap: true
  127. })
  128. }
  129. }
  130. return
  131. }
  132. if (!query.vue) {
  133. // main request
  134. return transformMain(
  135. code,
  136. filename,
  137. options,
  138. this,
  139. ssr,
  140. customElementFilter(filename)
  141. )
  142. } else {
  143. // sub block request
  144. const descriptor = getDescriptor(filename, options)!
  145. if (query.type === 'template') {
  146. return transformTemplateAsModule(code, descriptor, options, this, ssr)
  147. } else if (query.type === 'style') {
  148. return transformStyle(
  149. code,
  150. descriptor,
  151. Number(query.index),
  152. options,
  153. this
  154. )
  155. }
  156. }
  157. }
  158. }
  159. }

2.6 整体server流程

  1. 运行vite server命令
  2. 会启动server服务,hrm websocket服务,监听文件变化
  3. 注册一些中间件,插件初始化
  4. 当用户请求一个路由,会在http中请求一个.vue文件
  5. 会触发transformMiddleware中间件,里面做了对vue文件的处理

    3. 总结

  6. perf_hooks和inspector分别是node性能工具和调试工具,后面可以在深入了解

  7. 通过正则方式去匹配命令行参数/^(?:-d|—debug)$/.test(arg),这种写法可以不借助minimist等参数处理库,减少了包体积
  8. cac库对cli的体验不错,有命令帮助手册

    4. 参考文档

  9. https://juejin.cn/post/7021306258057592862

  10. github.com/vitejs/vite
  11. http://nodejs.cn/api/perf_hooks.html
  12. https://nodejs.org/api/inspector.html