Skip to content

vite中间件原理

vite中间件在vite整个架构中充当了重要角色,当vite服务启动后,页面相关请求和编译都要经过中间件处理,其中编译过程中进行了缓存优化,极大提升了编译效率,并且vite对外暴露了middleware对象,允许我们自定义中间件。

中间件概述

中间件概述

vite服务的本质是借助import('node:http')、import('node:https')、import('node:http2')启动了一个node服务,然后引用第三方库connect,注册中间件,处理具体代理转发请求,静态资源,代码编译等功能。

js
const app = connect()

app.use(middleware()) // 示例代码 注册各种中间件

const { createServer } = await import('node:http') //http协议

return createServer(app)
js
const app = connect()

app.use(middleware()) // 示例代码 注册各种中间件

const { createServer } = await import('node:https') //https协议

return createServer(httpsOptions, app)
js
const app = connect() 

app.use(middleware()) // 示例代码 注册各种中间件

const { createSecureServer } = await import('node:http2') //https2协议

    return createSecureServer(
      {
        maxSessionMemory: 1000,
        ...httpsOptions,
        allowHTTP1: true,
      },
      app,
    )

baseMiddleware中间件

baseMiddleware中间件

baseMiddleware负责把我们的base路径转换成项目目录,即当我们访问localhost:5173/会自动跳转到localost:5173/vue-source-docs/路径下。源码里面执行res.writeHead方法设置响应头,借助Location属性进行重定向,跳转到项目配置的路径下。源码如下:

js
export function baseMiddleware(
  rawBase: string,
  middlewareMode: boolean,
): Connect.NextHandleFunction {
  return function viteBaseMiddleware(req, res, next) {
    const url = req.url!
    const pathname = cleanUrl(url)
    const base = rawBase
    // 这里的writeHead方法用于设置响应头,借助Location字段进行重定向,
    // 实现了localhost:5173/ 重定向到localhost:5173/vue-source-docs/
    if (pathname === '/' || pathname === '/index.html') {
      res.writeHead(302, {
        Location: base + url.slice(pathname.length),
      })
      res.end()
      return
    }
  }
}

indexHtmlMiddleware中间件

indexHtmlMiddleware中间件

indexHtmlMiddleware主要是处理index.html文件,给head中meta标签添加meta csp-nonce属性,防止内联脚本被恶意篡改或注入。将html文件中模板变量%xxx%替换成对应define中配置的变量。给style/script标签添加nonce属性,同时在中间执行自定义插件的transformIndexHtml方法,最后将html通过res.end(content)返回给浏览器。源码如下:

js
export function injectCspNonceMetaTagHook(
  config: ResolvedConfig,
): IndexHtmlTransformHook {
  return () => {
    if (!config.html?.cspNonce) return
    // <meta property="csp-nonce" nonce="xxxx"> 注入到head中,
    // 防止内联脚本被恶意篡改或注入
    return [
      {
        tag: 'meta',
        injectTo: 'head',
        attrs: { property: 'csp-nonce', nonce: config.html.cspNonce },
      },
    ]
  }
}

transformMiddleware中间件

transformMiddleware中间件

transformMiddleware主要编译.html以外其它文件(.html文件不处理),当在浏览器输入localhost:5173/后,浏览器对vite发起请求,vite首先将index.html文件返回给浏览器,浏览器会根据index.html文件中引用的其它文件(比如index.js/index.css等等),会再次发起请求,这时候transformMiddleware中间件就会对这些文件进行编译转换,然后将转换的结果返回给浏览器。transform中会依次执行vite插件api函数,resolveId/load/transform方法。vite执行方法的顺序跟rollup是类似的,先执行所有插件的resolveId方法,然后执行load方法,最后执行transform方法,最终把编译好的代码经过res.end(result)返回给浏览器。源码如下:

resolveId方法代码块

js
async function doTransform(
  environment: DevEnvironment,
  url: string,
  options: TransformOptions,
  timestamp: number
) {
  url = removeTimestampQuery(url)
  // 省略其它代码
  let module = await environment.moduleGraph.getModuleByUrl(url)
  const resolved = module
    ? undefined
    : ((await pluginContainer.resolveId(url, undefined)) ?? undefined)
  // 省略其它代码
  const result = loadAndTransform(
    environment,
    id,
    url,
    options,
    timestamp,
    module,
    resolved,
  )
  return result
}

load和transform方法代码块

js
async function loadAndTransform(
  environment: DevEnvironment,
  id: string,
  url: string,
  options: TransformOptions,
  timestamp: number,
  mod?: EnvironmentModuleNode,
  resolved?: PartialResolvedId,
) {
  const { config, pluginContainer, logger } = environment
  // 省略中间代码
  const loadResult = await pluginContainer.load(id)
  // 省略中间代码
  // map是load方法返回map
  const transformResult = await pluginContainer.transform(code, id, {
    inMap: map,
  })
}

MIT Licensed | thanks vue team