前端知识点

vite插件-📒

2022-08-22  本文已影响0人  Lee弟弟

vite整合了rollup,所以vite的插件可以看成是受限制的rollup插件,它支持部分rollup的Hooks。vite也提供仅属于它自己的Hooks。

命名规范

兼容rollup部分Hooks(仅列举常用的)

服务启动时

会兼容rollup的optionsbuildStart钩子,只会执行一次,文件改变触发的打包不会再触发钩子与文件更新无关,也符合插件编码规范。

每个模块

为了实现代码编译能力,兼容resolveIdloadtransformbuildEnd

服务关闭时

会触发closeBundle

在vite里使用rollup插件

如果需要在vite里使用rollup的插件,vite会用自己的规范读取rollup插件的config,只会执行vite里兼容的Hooks。

常见的场景是,已经写了rollup的插件,不想重写一个插件来兼容vite了,有没有办法能使我们在vite按照rollup的规则来使用插件呢?
vite提供了rollupOptions配置项,使我们可以在build中配置rollup 插件:

// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  // ...
  build: {
    rollupOptions: {
      plugins: []
    }
  }
})

vite 在 build 的时候其实是执行了 rollup 的 build。
⚠️ 需要注意的是modulePased钩子不会被触发,vite为了对代码整体进行ast的解析,在开发环境代码编译是通过esbuild进行,在执行完rollup插件会传给esbuild来产出最终的代码,所以modulePased这部分会交给esbuild来完成。

总结:适合在vite使用的rollup插件需要满足三要素

vite专属的Hooks

config

在config里返回的对象会被合并到vite的配置当中立即生效,可以根据vite当前配置(包含用户自定义配置)自动生成额外的配置或初始化用户漏传的配置,它的返回值有两种方式,返回对象或Promise。

export default () => {
  return {
    name: 'test',
    config(userConfig) {
      // 方式1直接返回对象
      return {
        resolve: {
          alias: {
            '@diy': '/src/diy'
          }
        }
      }
      // 方式2在Promise里的resolve返回对象,
      return new Promise(resolve => {
        resolve({
          resolve: {
            alias: {
              '@diy': '/src/diy'
            }
          }
        })
      })

    }
  }
}

configResolved

所有插件的config执行完毕会触发这个hook,可以在这个钩子里拿到最终的配置,在这个钩子不能再修改配置了。
可以将配置存到插件的运行环境(闭包)中,方便后续在其它钩子中读到它。

export default () => {
  let root = ''
  return {
    name: 'test',
    config(userConfig) {
      return { resolve: { alias: { '@diy': '/src/diy' } } 
    },
    configResolved(config) {
      root = config.root
      console.log(config.resolve)
      // { ...,  alias: [
      //    { find: /^\/@vite\//, replacement: [Function: replacement] },
      //    { find: '@diy', replacement: '/src/diy' }
      //  ]
      // } 
    }
  }
}

configureServer

在这个钩子里可以获取server的实例,增加devServer中间件做一些处理。

// server属性
interface ViteDevServer {
  /**
   * 被解析的 Vite 配置对象
   */
  config: ResolvedConfig
  /**
   * 一个 connect 应用实例
   * - 可以用于将自定义中间件附加到开发服务器。
   * - 还可以用作自定义http服务器的处理函数。
      或作为中间件用于任何 connect 风格的 Node.js 框架。
   *
   * https://github.com/senchalabs/connect#use-middleware
   */
  middlewares: Connect.Server
  /**
   * 本机 node http 服务器实例
   */
  httpServer: http.Server | null
  /**
   * chokidar 监听器实例
   * https://github.com/paulmillr/chokidar#api
   */
  watcher: FSWatcher
  /**
   * web socket 服务器,带有 `send(payload)` 方法。
   */
  ws: WebSocketServer
  /**
   * Rollup 插件容器,可以针对给定文件运行插件钩子。
   */
  pluginContainer: PluginContainer
  /**
   * 跟踪导入关系、url 到文件映射和 hmr 状态的模块图。
   */
  moduleGraph: ModuleGraph
  /**
   * 以代码方式解析、加载和转换 url 并获取结果
   * 而不需要通过 http 请求管道。
   */
  transformRequest(
    url: string,
    options?: TransformOptions
  ): Promise<TransformResult | null>
  /**
   * 应用 Vite 内建 HTML 转换和任意插件 HTML 转换
   */
  transformIndexHtml(url: string, html: string): Promise<string>
  /**
   * 加载一个给定的 URL 作为 SSR 的实例化模块
   */
  ssrLoadModule(
    url: string,
    options?: { isolated?: boolean }
  ): Promise<Record<string, any>>
  /**
   * 解决 ssr 错误堆栈信息
   */
  ssrFixStacktrace(e: Error): void
  /**
   * 启动服务器
   */
  listen(port?: number, isRestart?: boolean): Promise<ViteDevServer>
  /**
   * 重启服务器
   *
   * @param forceOptimize - 强制优化器重新大伯啊,和命令行内使用 --force 一致
   */
  restart(forceOptimize?: boolean): Promise<void>
  /**
   * 停止服务器
   */
  close(): Promise<void>
}

控制添加的中间件执行顺序:

export default () => {
  return {
    name: 'test',
    configureServer(server) {
      // 高优先级:直接添加中间件优先级很高,会在vite中间件执行之前执行 
      server.middlewares.use((req, res, next) => {
        next()
      })
      // 低优先级:在Hook的返回值里添加,会在vite中间件执行之后执行
      return () => {
        server.middlewares.use((req, res, next) => {
          next()
        })
      }
    }
  }
}

中间件的入参和express中间件的入参含义一样。

⚠️ 如果需要处理get请求的接口推荐使用高优先级的方式,vite的中间件会将页面发出的get请求映射到index html里,轮不到低优先级的中间件处理。

transformIndexHtml

处理入口html文件内容的钩子。

export default () => {
  return {
    name: 'test',
    transformIndexHtml(html) {
      console.log(html)
      /** 
       * 控制台打印index.html文件的内容:
       * <!DOCTYPE html>
       * <html lang="en">
       *   <head>
       *     <meta charset="UTF-8" />
       *     <link rel="icon" href="/favicon.ico" />
       *     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
       *     <title>Vite App</title>
       *   </head>
       *   <body>
       *     <div id="app"></div>
       *     <script type="module" src="/src/main.js"></script>
       *   </body>
       * </html>
       */
    }
  }
}

handleHotUpdate

热更新的时候会触发的钩子,钩子能拿到热更新模块的信息。

export default () => {
  return {
    name: 'test',
    handleHotUpdate(ctx) {
      console.log(ctx)
      /**
       * {
       *    file: 更新的文件
       *    timestamp: 更新的时间
       *    modules: 更新的module [
       *      ModuleNode: modulePased处理的内容
       *    ]
       *    ...
       * }
       */ 
    }
  }
}

热更新后,通知客户端处理热更新:

export default () => {
  return {
    name: 'test',
    handleHotUpdate(ctx) {
      ctx.server.ws.send({
        type: 'custom',
        event: 'handleHotUpdate',
        data: {
          msg: 'hello client, hot updated',
          file: ctx.file,
          timestamp: ctx.timestamp
        }
      })
    }
  }
}

// app.jsx
if (import.meta.hot) {
  import.meta.hot.on('handleHotUpdate', val => {
    console.log(val)
  })
}

// 触发热更新,控制台打印
/**
 * {
 *   msg: 'hello client, hot updated',
 *   file: '/.../src/App.jsx', (绝对路径)
 *   timestamp: 1660728266941
 * }
 */

vite插件执行时机

相对于rollup插件是按照配置数组顺序执行vite的执行顺序更加灵活,按照数组执行的前提下支持3阶段配置。

// vite-plugin-test.js
export default (enforce: 'pre' | 'post') => {
    return {
        name: 'test',
        enforce,
        buildStart() {
            console.log('buildStart:', enforce);
        }
    }
}

// vite.config.js
import { defineConfig } from 'vite'
import testPlugin from './plugins/test-plugin'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [testPlugin('post'), testPlugin(), testPlugin('pre')]
})

// 执行vite
#sh: vite
/** 
 * 终端打印
 * buildStart: pre
 * buildStart: undefined
 * buildStart: post
 */

可以看到组件的执行机制是先按照优先级,再按顺序。

上一篇 下一篇

猜你喜欢

热点阅读