Vite 快速上手 - 创作插件

264 阅读4分钟

插件的创作

可参考官网 插件 API 一节.

几个注意的点:

  1. 学习、调试、创作插件时,可以在项目中引入 vite-plugin-inspect
  • 检查 Vite 插件的中间状态。
  • 访问 localhost:5173/__inspect/ 检查项目的模块和栈信息。
  • vite-plugin-inspect 文档

约定:

Rollup 插件:

如果插件不使用 Vite 特有的钩子,可以作为 兼容 Rollup 的插件 来实现,推荐使用 Rollup 插件名称约定

  • 前缀 rollup-plugin- + 插件名称
  • package.json 中包含 rollup-pluginvite-plugin 关键字

Vite 专属插件:

  • 前缀 vite-plugin- + 插件名称
  • package.json 中包含 vite-plugin 关键字
  • 插件文档中应该加上此插件为什么是一个 Vite 专属插件的详细说明;(如,本插件用了哪些特有的插件钩子)。

插件只适用于特定的框架:

命名遵循格式:

  • vite-plugin-vue- 前缀作为 Vue 插件
  • vite-plugin-react- 前缀作为 React 插件
  • vite-plugin-svelte- 前缀作为 Sevlte 插件

虚拟模块:

虚拟模块是 Vite 沿用 Rollup 的虚拟模块,虚拟模块相似 alias 别名,然而模块的内容并非间接从磁盘中读取,而是编译时生成。

虚拟模块是一种很实用的模式,使你能够对应用 ESM 语法的源文件传入一些编译时信息。

Demo:

创作了一个带虚拟模块的Vite插件 vite-plugin-my-plugin:

// 插件代码:vite-plugin-my-plugin.js

export default function myPlugin() {
  const virtualModuleId = 'virtual:my-module'
  const resolvedVirtualModuleId = '\0' + virtualModuleId

  return {
    name: 'my-plugin', // 必须的,将会在 warning 和 error 中显示
    resolveId(id) {
      if (id === virtualModuleId) {
        return resolvedVirtualModuleId
      }
    },
    load(id) {
      if (id === resolvedVirtualModuleId) {
        return `export const msg = "from virtual module"`
      }
    },
  }
}

项目中应用:

// 如:main.js

import { msg } from 'virtual:my-module'

console.log(msg)

特点:

  • 虚拟模块以 virtual: 为前缀
  • 插件名应被用作命名空间,如:vite-plugin-posts 插件可以要求用户导入 virtual:posts 或者 virtual:posts/helpers; 而不是 virtual:postsvirtual:helpers 一个插件中出现两个没有名称关联的虚拟模块的导入;
  • 使用了虚拟模块的插件在解析时应该将模块 ID 加上前缀 \0 ;避免其他插件尝试处理这个ID;

插件钩子

Vite & Rollup 通用钩子:

以下钩子在服务器启动时被调用:

options

buildStart

以下钩子会在每个传入模块请求时被调用:

resolveId

load

transform

以下钩子在服务器关闭时被调用:

buildEnd

closeBundle

更多钩子可参见:Rollup 构建钩子

Vite 独有钩子:

Vite 插件可以提供钩子来服务于特定的 Vite 目标。这些钩子会被 Rollup 忽略;

在解析 Vite 配置前调用。它可以返回一个将被深度合并到现有配置中的部分配置对象,或者直接改变配置(如果默认的合并不能达到预期的结果)。

config

类型:(config: userConfig, env: { mode: string, command: string }) => UserConfig | null | void

在解析 Vite 配置后调用。使用这个钩子读取和存储最终解析的配置。当插件需要根据运行的命令做一些不同的事情时,它也很有用。

configResolved

类型:(config: ResolvedConfig) => void | Promise

是用于配置开发服务器的钩子:

configureServer

类型: (server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>

与 configureServer 相同但是作为预览服务器:

configPreviewServer

(server: { middlewares: Connect.Server, httpServer: http.Server }) => (() => void) | void | Promise<(() => void) | void>

转换 index.html 的专用钩子:

transformIndexHtml

类型:IndexHtmlTransformHook | { order?: 'pre' | 'post', handler: IndexHtmlTransformHook }

执行自定义 HMR 更新处理:

handleHotUpdate

类型:(ctx: HmrContext) => Array | void | Promise | void>

路径规范化

Vite 提供了工具函数 normalizePath做路径规范化处理;

Demo:

import { normalizePath } from 'vite'

normalizePath('foo\\bar') // 'foo/bar'
normalizePath('foo/bar') // 'foo/bar'

过滤 & include/exculde 模式:

Vite 暴露了 @rollup/pluginutils 的 createFilter 函数,以支持 Vite 独有插件和集成使用标准的 include/exclude 过滤模式

  • @rollup/pluginutils Rollup插件常用的一组实用函数。
  • createFilter 函数:
    • 结合 options.includeoptions.exclude, 进一步做过滤处理
    • 参数: (include?: <picomatch>, exclude?: <picomatch>, options?: Object)
    • 返回: String

Usage:

import { createFilter } from '@rollup/pluginutils';

export default function myPlugin(options = {}) {
  // assume that the myPlugin accepts options of `options.include` and `options.exclude`
  var filter = createFilter(options.include, options.exclude, {
    resolve: '/my/base/dir'
  });

  return {
    transform(code, id) {
      if (!filter(id)) return;

      // proceed with the transformation...
    }
  };
}

插件执行顺序:

Vite 插件可配置属性 enforce 来调整插件列表中插件的执行顺序;

以下是插件执行顺序:

  • Alias
  • 带有 enforce: 'pre' 的用户插件
  • Vite 核心插件
  • 没有 enforce 值的用户插件
  • Vite 构建用的插件
  • 带有 enforce: 'post' 的用户插件
  • Vite 后置构建插件(最小化,manifest,报告)

情景应用:

默认情况下插件在开发(serve)和构建(build)模式中都会调用。如果插件只需要在预览或构建期间有条件地应用,请使用 apply 属性指明它们仅在 'build' 或 'serve' 模式时调用:

Usage:

// vite-plugin-my-plugin.js
function myPlugin() {
  return {
    name: 'build-only',
    apply: 'build' // 或 'serve'
  }
}

apply 也可以是个函数,用来做更精准的控制

// vite-plugin-my-plugin.js

function myPlugin() {
  return {
    name: 'build-only',
    apply(config, { command }) {
      // 非 SSR 情况下的 build
      return command === 'build' && !config.build.ssr
    }
  }
}