vite devServer

简介
viteDevServer是整个vite运行的核心,下面我们来简单熟悉一下它的运行机制。首先分析一下组成:它是由5大模块和15大中间件组成的

5大模块

  1. httpServer——node http 服务器
  2. fsWatcher——chokidar 监听器
  3. pluginContainer——插件容器,可为特定文件运行插件钩子
  4. webSocketServer——web socket 服务器
  5. moduleGraph——跟踪导入关系,url到文件映射;hmr状态模块图

15大中间件(按顺序执行)

  1. timeMiddleware——记录响应时间
  2. corsMiddleware——设置跨域
  3. proxyMiddleware——设置代理
  4. baseMiddleware——设置base url
  5. launchEditorMiddleware——加载编辑器
  6. viteHMRPingMiddleware——HMR心跳检测
  7. decodeURIMiddleware——解码
  8. servePublicMiddleware——public目录文件处理
  9. transformMiddleware——核心转换模块
  10. serveRawFsMiddleware——fs模块处理
  11. serveStaticMiddleware——static目录文件处理
  12. spaFallbackMiddleware——单页应用处理
  13. indexHtmlMiddleware——index html入口文件处理
  14. vite404Middleware——404处理
  15. errorMiddleware——错误处理

下面使用一张图来直观的看一下它们之间的联系
在这里插入图片描述
简单介绍一下主要中间件
1、timeMiddleware

import { performance } from 'perf_hooks'
import { Connect } from 'types/connect'
import { createDebugger, prettifyUrl, timeFrom } from '../../utils'

const logTime = createDebugger('vite:time')

export function timeMiddleware(root: string): Connect.NextHandleFunction {
  // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
  return function viteTimeMiddleware(req, res, next) {
    const start = performance.now()
    const end = res.end
    res.end = (...args: any[]) => {
      // 打印时间
      logTime(`${timeFrom(start)} ${prettifyUrl(req.url!, root)}`)
      // @ts-ignore
      return end.call(res, ...args)
    }
    next()
  }
}

2、corsMiddleware

import corsMiddleware from 'cors'
const { cors } = serverConfig
if (cors !== false) {
  middlewares.use(corsMiddleware(typeof cors === 'boolean' ? {} : cors))
}
// 在vite.config.ts中配置cors,传给corsMiddleware

3、proxyMiddleware

export function proxyMiddleware(
  httpServer: http.Server | null,
  config: ResolvedConfig
): Connect.NextHandleFunction {
  const options = config.server.proxy!

  // lazy require only when proxy is used
  const proxies: Record<string, [HttpProxy.Server, ProxyOptions]> = {}

  Object.keys(options).forEach((context) => {
    let opts = options[context]
    if (typeof opts === 'string') {
      opts = { target: opts, changeOrigin: true } as ProxyOptions
    }
    const proxy = httpProxy.createProxyServer(opts) as HttpProxy.Server

    proxy.on('error', (err) => {
      config.logger.error(`${chalk.red(`http proxy error:`)}\n${err.stack}`, {
        timestamp: true,
        error: err
      })
    })

    if (opts.configure) {
      opts.configure(proxy, opts)
    }
    // clone before saving because http-proxy mutates the options
    proxies[context] = [proxy, { ...opts }]
  })

  if (httpServer) {
    httpServer.on('upgrade', (req, socket, head) => {
      const url = req.url!
      for (const context in proxies) {
        if (doesProxyContextMatchUrl(context, url)) {
          const [proxy, opts] = proxies[context]
          if (
            (opts.ws || opts.target?.toString().startsWith('ws:')) &&
            req.headers['sec-websocket-protocol'] !== HMR_HEADER
          ) {
            if (opts.rewrite) {
              req.url = opts.rewrite(url)
            }
            debug(`${req.url} -> ws ${opts.target}`)
            proxy.ws(req, socket, head)
            return
          }
        }
      }
    })
  }

  // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
  return function viteProxyMiddleware(req, res, next) {
    const url = req.url!
    for (const context in proxies) {
      if (doesProxyContextMatchUrl(context, url)) {
        const [proxy, opts] = proxies[context]
        const options: HttpProxy.ServerOptions = {}

        if (opts.bypass) {
          const bypassResult = opts.bypass(req, res, opts)
          if (typeof bypassResult === 'string') {
            req.url = bypassResult
            debug(`bypass: ${req.url} -> ${bypassResult}`)
            return next()
          } else if (isObject(bypassResult)) {
            Object.assign(options, bypassResult)
            debug(`bypass: ${req.url} use modified options: %O`, options)
            return next()
          } else if (bypassResult === false) {
            debug(`bypass: ${req.url} -> 404`)
            return res.end(404)
          }
        }

        debug(`${req.url} -> ${opts.target || opts.forward}`)
        if (opts.rewrite) {
          req.url = opts.rewrite(req.url!)
        }
        proxy.web(req, res, options)
        return
      }
    }
    next()
  }
}

// 在vite.config.ts中配置proxy代理信息,中间件会接收代理进行配置

4、transformMiddleware

// 这是核心转换模块,处理文件路径,文件类型转换等等
export function transformMiddleware(
  server: ViteDevServer
): Connect.NextHandleFunction {
  const {
    config: { root, logger, cacheDir },
    moduleGraph
  } = server

  // determine the url prefix of files inside cache directory
  let cacheDirPrefix: string | undefined
  if (cacheDir) {
    // 缓存地址
    const cacheDirRelative = normalizePath(path.relative(root, cacheDir))
    if (cacheDirRelative.startsWith('../')) {
      // if the cache directory is outside root, the url prefix would be something
      // like '/@fs/absolute/path/to/node_modules/.vite'
      cacheDirPrefix = `/@fs/${normalizePath(cacheDir).replace(/^\//, '')}`
    } else {
      // if the cache directory is inside root, the url prefix would be something
      // like '/node_modules/.vite'
      cacheDirPrefix = `/${cacheDirRelative}`
    }
  }

  // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
  return async function viteTransformMiddleware(req, res, next) {
    if (req.method !== 'GET' || knownIgnoreList.has(req.url!)) {
      return next()
    }

    if (
      server._pendingReload &&
      // always allow vite client requests so that it can trigger page reload
      !req.url?.startsWith(CLIENT_PUBLIC_PATH) &&
      !req.url?.includes('vite/dist/client')
    ) {
      // missing dep pending reload, hold request until reload happens
      server._pendingReload.then(() =>
        // If the refresh has not happened after timeout, Vite considers
        // something unexpected has happened. In this case, Vite
        // returns an empty response that will error.
        setTimeout(() => {
          // Don't do anything if response has already been sent
          if (res.writableEnded) return
          // status code request timeout
          res.statusCode = 408
          res.end(
            `<h1>[vite] Something unexpected happened while optimizing "${req.url}"<h1>` +
              `<p>The current page should have reloaded by now</p>`
          )
        }, NEW_DEPENDENCY_BUILD_TIMEOUT)
      )
      return
    }

    let url = decodeURI(removeTimestampQuery(req.url!)).replace(
      NULL_BYTE_PLACEHOLDER,
      '\0'
    )

    const withoutQuery = cleanUrl(url)

    try {
      const isSourceMap = withoutQuery.endsWith('.map')
      // since we generate source map references, handle those requests here
      if (isSourceMap) {
        const originalUrl = url.replace(/\.map($|\?)/, '$1')
        const map = (await moduleGraph.getModuleByUrl(originalUrl))
          ?.transformResult?.map
        if (map) {
          return send(req, res, JSON.stringify(map), 'json')
        } else {
          return next()
        }
      }

      // check if public dir is inside root dir
      const publicDir = normalizePath(server.config.publicDir)
      const rootDir = normalizePath(server.config.root)
      if (publicDir.startsWith(rootDir)) {
        const publicPath = `${publicDir.slice(rootDir.length)}/`
        // warn explicit public paths
        if (url.startsWith(publicPath)) {
          logger.warn(
            chalk.yellow(
              `files in the public directory are served at the root path.\n` +
                `Instead of ${chalk.cyan(url)}, use ${chalk.cyan(
                  url.replace(publicPath, '/')
                )}.`
            )
          )
        }
      }

      if (
        isJSRequest(url) ||
        isImportRequest(url) ||
        isCSSRequest(url) ||
        isHTMLProxy(url)
      ) {
        // strip ?import
        url = removeImportQuery(url)
        // Strip valid id prefix. This is prepended to resolved Ids that are
        // not valid browser import specifiers by the importAnalysis plugin.
        url = unwrapId(url)

        // for CSS, we need to differentiate between normal CSS requests and
        // imports
        if (
          isCSSRequest(url) &&
          !isDirectRequest(url) &&
          req.headers.accept?.includes('text/css')
        ) {
          url = injectQuery(url, 'direct')
        }

        // check if we can return 304 early
        const ifNoneMatch = req.headers['if-none-match']
        if (
          ifNoneMatch &&
          (await moduleGraph.getModuleByUrl(url))?.transformResult?.etag ===
            ifNoneMatch
        ) {
          isDebug && debugCache(`[304] ${prettifyUrl(url, root)}`)
          res.statusCode = 304
          return res.end()
        }

		// 通过这个方法生成新的依赖ID,并加载进来,更新moduleGraph
        // resolve, load and transform using the plugin container
        const result = await transformRequest(url, server, {
          html: req.headers.accept?.includes('text/html')
        })
        if (result) {
          const type = isDirectCSSRequest(url) ? 'css' : 'js'
          const isDep =
            DEP_VERSION_RE.test(url) ||
            (cacheDirPrefix && url.startsWith(cacheDirPrefix))
          return send(
            req,
            res,
            result.code,
            type,
            result.etag,
            // allow browser to cache npm deps!
            isDep ? 'max-age=31536000,immutable' : 'no-cache',
            result.map
          )
        }
      }
    } catch (e) {
      return next(e)
    }

    next()
  }
}

关于viteDevServer,这里就简单的介绍一下它的组成,在我们使用的过程中,是没有bundle打包的,它是利用原生ES模块的方式来处理引入文件进行热更新,这种方式使得vite开发在冷启动和模块重载方面的体验非常巴适。。。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
createServerVite框架中的一个方法,用于创建一个viteDevServer实例。在执行npm run dev命令时,会调用cli.js文件中的createServer方法,传递vite.config.js或cli命令上的自定义配置,然后创建一个viteDevServer实例。createServer的流程包括以下几个步骤: 1. 创建httpServer和webSocketServer,用于后续构建服务器。 2. 创建watcher,用于监听文件模块和依赖的变化。 3. 创建moduleGraph,用于记录每个模块之间的关系。 4. 创建container,用于处理插件。 5. 处理模块中的hook。 6. 返回一个完整的server对象。 createServer方法依赖于websocketServer、fsWatcher、moduleGraph等5个模块来支持其功能。通过这些模块的协作,根据15个中间件的分工来串联整个加工流程,最终打磨出一个完整的devServer。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [10 分钟搞懂 Vite devServer,速来围观](https://blog.csdn.net/qq_41581588/article/details/129178050)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [vite createServer 探索](https://blog.csdn.net/qq_42501092/article/details/125535006)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值