「Vant 打包太大」是移动端项目里最常见的一类性能投诉,但多数时候问题不在 JS,而在样式。Vant 按需引入要解决的正是这件事:Vant 4 默认支持 Tree Shaking,未使用的组件 JS 会被构建工具摇掉,可 vant/lib/index.css 一旦整体引入,全量 CSS 就会原样进包。本文以 Vite 为例,把三种引入方式的差别、unplugin-vue-components + VantResolver 的完整配置、产物验证方法和几个高频坑一次讲透。
先搞清 Vant 4 的模块化结构
Vant 4 的 npm 包同时提供 es(ES Module,供打包器 Tree Shaking)与 lib(含全量样式 index.css)两份产物。从 Vant 4.0 起,官方移除了对 babel-plugin-import 的支持,按需引入不再依赖 Babel 插件:
import { createApp } from 'vue';
import { Button, Field, Form } from 'vant'; // 具名导入,JS 层天然可摇树
import 'vant/lib/index.css'; // 这一行是全量 CSS,无法被摇掉
const app = createApp(App);
app.use(Button).use(Field).use(Form);
app.mount('#app');
换句话说:JS 按需是默认的,CSS 按需才需要额外配置。这也是官方在文档里建议「如果业务对 CSS 体积要求不是特别极致,用常规用法就好」的原因。
| 引入方式 | JS 体积 | CSS 体积 | 配置成本 | 推荐度 |
|---|---|---|---|---|
全量注册 app.use(Vant) | 全量 | 全量 | 最低 | 仅原型/演示 |
| 常规用法(具名导入 + 全量 CSS) | 按需 | 全量 | 低 | ★ 多数项目首选 |
| 插件自动按需(两个 unplugin) | 按需 | 按需 | 中 | 对首屏体积敏感的项目 |
方案 A:常规用法,先把基础打牢
在 <script setup> 中直接导入组件即可,甚至不需要注册;函数式 API 也用具名导入,写法比 Vant 3 干净得多:
<script setup lang="ts">
import { Button, Field, showToast, showDialog } from 'vant';
const onSubmit = () => showToast('提交成功');
</script>
<template>
<Button type="primary" @click="onSubmit">提交</Button>
</template>
showToast、showDialog、showNotify、showImagePreview)在常规用法下会随全量 CSS 一起带上样式;若切换到插件按需引入,务必同时配置 unplugin-auto-import,否则弹窗可能「有逻辑没样式」。方案 B:Vite 自动按需引入
步骤 1:安装三个开发依赖
npm i vant
npm i @vant/auto-import-resolver unplugin-vue-components unplugin-auto-import -D
三者的分工很清楚:unplugin-vue-components 扫描模板并自动注册组件,unplugin-auto-import 自动导入脚本里的函数式 API,@vant/auto-import-resolver 则告诉前两者「这个组件对应哪份 Vant 样式」。
步骤 2:配置 vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from '@vant/auto-import-resolver';
export default defineConfig({
plugins: [
vue(),
AutoImport({ resolvers: [VantResolver()] }),
Components({ resolvers: [VantResolver()] }),
],
});
步骤 3:删掉手动导入,直接用
<script setup lang="ts">
// 无需 import:showToast 由 AutoImport + VantResolver 自动注入
const onSubmit = () => showToast('提交成功');
</script>
<template>
<!-- 无需注册:van-* 标签由 Components 插件自动解析 -->
<van-button type="primary" @click="onSubmit">提交</van-button>
<van-field label="备注" placeholder="请输入" />
</template>
此时 main.ts 里不要再写 import 'vant/lib/index.css',也不要 app.use(Vant)——官方明确提示:全量引入与按需引入同时存在会导致代码重复、样式错乱。
Components({ resolvers: [VantResolver(), ElementPlusResolver()] }) 里并列配置多个解析器,插件会按标签前缀分别处理。验证优化是否真的生效
改完配置别急着收工,用构建产物说话最可靠:
npm run build
ls -lh dist/assets/*.css
# 用 rollup-plugin-visualizer 或 vite-bundle-visualizer 看 chunk 构成
npx vite-bundle-visualizer
判断标准有三条:
- CSS 明显变小:只用到 Button / Field / Toast 的页面,产物里不应出现整套组件样式。
- 没有重复 chunk:若同时存在全量 CSS 与 resolver 引入的样式,说明
main.ts还有残留导入。 - 生成了声明文件:
components.d.ts/auto-imports.d.ts由插件自动生成,把它们加进tsconfig.json的include,否则 TS 会报「找不到名称showToast」。
顺带把移动端适配挂进 PostCSS
按官方进阶文档,Vant 默认用 px,需要 vw 或 rem 适配时在 postcss.config.js 配置即可。这一步与按需引入互不冲突,但有个容易忽略的前提:
module.exports = {
plugins: [
// 375 设计稿:Vant 与业务代码分开换算
require('postcss-mobile-forever')({
viewportWidth: (file) => (file.includes('vant') ? 375 : 750),
}),
],
};
ignore node_modules,否则 Vant 自带的样式不会被编译,单位换算对组件库完全失效。常见坑速查
| 现象 | 原因 | 处理 |
|---|---|---|
| 样式错乱 / CSS 重复 | 全量引入与自动按需混用 | 删掉 import 'vant/lib/index.css' 与 app.use(Vant) |
| 模板里 Vant 组件不渲染 | 只配了 AutoImport,没配 Components | 两个插件都要配,且都挂 VantResolver() |
| Toast 有逻辑没样式 | 函数式 API 样式未被解析 | 补 unplugin-auto-import;或显式引入对应组件样式 |
TS 报找不到 showToast | 自动生成的 d.ts 未纳入 tsconfig | 把 auto-imports.d.ts 加入 include |
| webpack 下插件报错 | unplugin-vue-components ≥ 0.26.0 的写法变化 | 改用 Components.default({ ... }) 注册 |
| 还在用 babel-plugin-import | Vant 3 时代的旧配置 | 从 babel.config.js 删除,Vant 4 已不支持 |
@vant/nuxt,在 nuxt.config.ts 的 modules 里加一行即可,它已经把函数式组件的样式按需逻辑处理好了。怎么选:给一个明确建议
如果项目只用了十几个组件、首屏不是极致敏感,常规用法(具名导入 + 全量 CSS)更省心,官方也是这么推荐的;当页面数量多、组件覆盖面窄、且对首屏 CSS 体积有明确指标时,再上 VantResolver 自动按需。优化前先跑一次构建看体积分布,别为了「听起来更优」提前引入构建复杂度。
性能优化的第一步是测量,不是换方案。