vite插件-📒

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

命名规范

  • 插件希望在vite和rollup里使用: rollup-plugin-xxx
  • 仅在vite使用:vite-plugin-xxx

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

服务启动时

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

每个模块

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

  • resolveId:找到对应文件
  • load:加载文件的源码
  • transform:把文件源码转换成目标代码
  • buildEnd:代码编译完成

服务关闭时

会触发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插件需要满足三要素

  • 没有使用 moduleParsed 钩子
  • 打包和输出之间没有很强的耦合
  • 代码输出不过分依赖Hooks的执行或使用到的Hooks是vite所兼容的

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阶段配置。

  • pre
    首批被执行的插件,会在@rollup/plugin-alias插件执行之后执行。

  • normal(默认值)
    第二批配执行的插件,会在vite的build阶段之前被执行,可以根据配置判断是否需要处理当前文件的代码。

  • post
    会在vite的build阶段之后被执行,进行代码构建方面的工作(minimize、代码分析...)。

// 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
 */

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容