首页
/ Vite 插件系统实战:plugins 配置、enforce 排序、apply 条件应用与插件生态

Vite 插件系统实战:plugins 配置、enforce 排序、apply 条件应用与插件生态

2026-09-04 09:14:08作者:何举烈Damon

本文围绕 Vite 官方文档 Using Plugins 展开,系统讲解如何在 vite.config.js 中接入、排序和条件启用插件,并结合 Vite 源码 印证 plugins 数组的扁平化、falsy 过滤、enforce 分桶与 apply 判定等底层机制。读完你可以独立完成插件选型与配置,理解一个插件最终在 Vite 管线中的执行位置,并判断 Rollup/Rolldown 插件的兼容性边界。

插件是什么:Rollup/Rolldown 插件接口的超集

Vite 的插件体系基于 Rollup 设计良好的插件接口,并在此之上扩展了若干 Vite 特有的选项。这意味着:

  • 用户可以复用成熟的 Rollup 插件生态;
  • 同时插件还能扩展 dev server 与 SSR 功能(如自定义中间件、HMR 处理逻辑等)这些 Rollup 构建器本身没有的概念。

需要注意版本演进:官方 Plugin API 文档 在当前仓库中表述为 “Vite plugins extends Rolldown's plugin interface with a few extra Vite-specific options”——即当前版本 Vite 的插件接口建立在 Rolldown 之上,而兼容层保证大量既有 Rollup 插件仍可直接工作。无论底层是 Rollup 还是 Rolldown,对使用者来说都是“写一次插件,dev 和 build 通用”。

添加插件:安装、注册与 preset 机制

基本用法

使用一个插件需要两步:把它加入项目的 devDependencies,再写进 vite.config.jsplugins 数组。以官方的 @vitejs/plugin-legacy(为生产构建提供旧浏览器兼容支持)为例:

$ npm add -D @vitejs/plugin-legacy
import legacy from '@vitejs/plugin-legacy'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11'],
    }),
  ],
})

@vitejs/plugin-legacy 就位于本仓库 packages/plugin-legacy 目录,可作为官方插件包结构的参考实现。

preset:一个元素里装多个插件

plugins 数组也接受 preset——即“一个元素包含多个插件”的形式,适合由多个插件协作实现的复杂功能(典型场景是框架集成)。这个数组会在内部被展平:

// framework-plugin:返回插件数组即构成 preset
import frameworkRefresh from 'vite-plugin-framework-refresh'
import frameworkDevtools from 'vite-plugin-framework-devtools'

export default function framework(config) {
  return [frameworkRefresh(config), frameworkDevtools(config)]
}
import { defineConfig } from 'vite'
import framework from 'vite-plugin-framework'

export default defineConfig({
  plugins: [framework()],
})

源码印证:配置解析时,plugins 先经过 asyncFlatten 展平(因此 preset 既可以是数组,也可以是返回 Promise 的异步工厂),再逐个过滤,见 config.ts

const rawPlugins = (await asyncFlatten(config.plugins || [])).filter(
  filterPlugin,
)
const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawPlugins)

asyncFlatten 的实现位于 utils.ts,并有对应单测覆盖嵌套数组与 Promise 场景,见 utils.spec.ts

falsy 插件会被忽略

plugins 数组中的 falsy 值(nullundefinedfalse 等)会被直接忽略,这提供了一种“按条件启停插件”的轻量写法:

plugins: [
  useCssPlugin && cssPlugin(),
  typescript(),
]

同样可以在源码里验证:filterPlugin 的第一步就是对 falsy 插件返回 false(见 config.ts),后续所有管线组装处也普遍使用 filter(Boolean) 收尾,例如 resolvePlugins

找到合适的插件:先查内置能力,再查生态

在去 npm 上搜索插件之前,官方文档给出了明确建议:先查 Features Guide。Vite 的目标是为常见 Web 开发模式提供开箱即用的支持——很多在 Rollup 项目里需要插件才能解决的问题,在 Vite 中已经是内置能力(如 CSS 预处理、JSON/import.meta.glob/import.meta.url、Web Worker、HTML 作为入口等)。

确认内置能力不覆盖需求后,按这个顺序找:

  1. 官方插件:见 Plugins 文档。当前仓库列出的官方插件包括:
  2. 社区插件:发布到 npm 的社区插件收录在 Vite Plugin Registry(外部站点,此处不展开链接);本仓库文档 docs/plugins/index.md 同时提示可参考 Rolldown 内置插件。

enforce:强制插件在管线中的位置

为了兼容部分 Rollup 插件,有时需要强制指定插件的执行位置。enforce 修饰符有三个取值:

取值 含义
pre 在 Vite 核心插件之前调用
缺省(default) 在 Vite 核心插件之后调用
post 在 Vite 构建插件之后调用

官方文档提醒:对 Vite 插件作者而言,enforce 应当是插件的实现细节而非暴露给用户的配置。用法示例——把 @rollup/plugin-image 提到 Vite 核心插件之前:

import image from '@rollup/plugin-image'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    {
      ...image(),
      enforce: 'pre',
    },
  ],
})

源码印证:排序逻辑分两层。

第一层是分桶,sortUserPlugins 把展平后的用户插件按 enforce 分成三组:

plugins.flat().forEach((p) => {
  if (p.enforce === 'pre') prePlugins.push(p)
  else if (p.enforce === 'post') postPlugins.push(p)
  else normalPlugins.push(p)
})

第二层是拼装,resolvePlugins 将用户插件插入到核心插件序列的固定位置:...prePlugins 插在 alias 解析之后、核心插件(CSS、JSON、WASM、Worker、Asset 等)之前;...normalPlugins 插在核心插件之后、buildPlugins.pre 之前;...postPlugins 再插在 buildPlugins.pre(如 dynamicImportVarsimportGlob)之后、buildPlugins.post 之前。最终管线还以三个仅服务 dev server 的内部插件收尾:clientInjectionsPlugincssAnalysisPluginimportAnalysisPlugin

由此可以整理出 Plugin API 文档 给出的完整顺序:

  1. Alias
  2. enforce: 'pre' 用户插件
  3. Vite 核心插件
  4. enforce 的用户插件
  5. Vite 构建插件
  6. enforce: 'post' 用户插件
  7. Vite 构建后插件(minify、manifest、reporting)

需要区分两个概念:enforce 决定插件整体在管线中的位置,而单个 hook 之间还有独立的 hook 级 order 属性('pre' / 'post')。getSortedPluginsByHook 会针对每个 hook 再次按 hook.order 排序,二者互不替代。

apply:只在 serve 或 build 阶段生效

默认情况下,插件在 serve 和 build 两个阶段都会被调用。若某个插件只应在单一阶段生效,使用 apply 属性限定为 'build''serve'

import typescript2 from 'rollup-plugin-typescript2'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    {
      ...typescript2(),
      apply: 'build',
    },
  ],
})

除了字符串,apply 还可以是函数,用于更精细的控制(例如“只在非 SSR 的 build 时应用”):

apply(config, { command }) {
  // apply only on build but not for SSR
  return command === 'build' && !config.build.ssr
}

源码印证:判定的真实实现是 filterPlugin,逻辑与文档完全一致:

const filterPlugin = (p: Plugin | FalsyPlugin): p is Plugin => {
  if (!p) {
    return false
  } else if (!p.apply) {
    return true
  } else if (typeof p.apply === 'function') {
    return p.apply({ ...config, mode }, configEnv)
  } else {
    return p.apply === command
  }
}

也就是说,字符串形式就是与当前命令(serve / build)做相等比较;函数形式则会把当前(合并后的)配置对象和 config env 传给你自行决策。过滤发生在 config 钩子执行之前,被过滤掉的插件不会收到任何 hook 调用。

构建插件:接口兼容性与实践约束

如果要写自己的插件,完整文档在 Plugins API Guide。这里从“使用插件”的视角梳理几个影响选型的要点:

Rollup/Rolldown 插件的兼容性判据

相当多的 Rollup/Rolldown 插件(如 @rollup/plugin-alias@rollup/plugin-json)可以直接当 Vite 插件用,因为 Vite dev server 内部创建的 plugin container 以与 Rolldown 相同的方式调用构建钩子。但并非全部兼容,官方给出的判据是:

  • 不使用 moduleParsed 钩子(Vite dev 期为避免全量 AST 解析而不调用它);
  • 不依赖 bundler 专属选项(如 transform.inject);
  • bundle 阶段钩子与 output 阶段钩子之间没有强耦合。

如果一个插件只服务于构建阶段,也可以把它放到 build.rolldownOptions.plugins 下,效果等价于同时声明 enforce: 'post'apply: 'build' 的 Vite 插件。

路径规范化与 include/exclude 过滤

Vite 解析 id 时会把路径规范化为 POSIX 分隔符(Windows 下保留卷号),而 Rollup 默认保留原始分隔符。因此编写插件、对 resolved id 做路径比较时,应先用 vite 模块导出的 normalizePath 规范化路径:

import { normalizePath } from 'vite'

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

官方同时建议插件统一使用 @rollup/pluginutilscreateFilter 做 include/exclude 过滤(该函数由 vite 再导出,Vite 核心内部同样使用这一模式),以保证 include/exclude 与其他工具的语义一致。

Vite 特有钩子的存在边界

判断一个插件属于“通用兼容型”还是“Vite 专属型”,可以直接看它是否使用了 configconfigResolvedconfigureServerconfigurePreviewServertransformIndexHtmlhandleHotUpdatecloseServer 等 Vite 特有钩子——这些钩子会被 Rollup 忽略,详见 Plugin API 文档的 Vite Specific Hooks 一节。其中与“使用插件”最相关的两个:

  • configureServer:最常用的场景是向 dev server 的 connect 中间件栈注入自定义中间件;它返回一个函数时,该函数会在内部中间件安装之后执行,用于把自定义中间件插到内部中间件之后。注意该钩子在生产构建时不会被调用,依赖它的其他钩子需要做好缺省保护。
  • config:插件可以在配置被 resolve 前修改配置(推荐返回局部配置对象做深合并)。注意用户插件在该钩子执行前已完成解析,因此在 config 钩子里再注入其他插件不会生效。

小结

对照官方文档与本仓库源码,Vite 插件机制的核心要点可以收敛为五条:

  1. 注册npm add -D 安装后写入 plugins 数组;preset(返回数组的插件工厂)由 asyncFlatten 自动展平,falsy 元素被 filterPlugin 忽略;
  2. 选型:先查 Features Guide 的内置能力,再查官方插件列表与社区 Registry;
  3. 排序enforce: 'pre' | 'post' 决定插件在核心/构建插件序列中的相对位置,分桶逻辑见 sortUserPlugins,最终管线见 resolvePlugins
  4. 阶段apply: 'build' | 'serve' 或函数形式控制插件生效阶段,判定代码同上 filterPlugin
  5. 扩展:需要 Vite 专属能力(中间件、HMR、HTML 转换)时转向 Plugin API 文档 编写插件,并注意 Rollup/Rolldown 插件的兼容性判据与路径规范化要求。

以上结论均可在当前仓库对应文档与源码路径中复核,适用前提为当前仓库所处的 Vite 版本(基于 Rolldown 的 Vite 8 系列)。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384