按需引入实战:减少打包体积提升前端性能的关键技巧
先看效果,再看代码
上周我接手一个老项目,打包后 vendor.js 高达 3.8MB,首屏加载直接卡成 PPT。打开一看,好家伙,import { Button, Input, Modal, Table, ... } from 'ant-design-vue' 全量引入,连用都没用的组件也塞进去了。这不优化说不过去。
我直接上按需引入,改完后 vendor.js 降到 1.1MB,首屏快了将近 2 秒。亲测有效,建议直接用这种方式。
核心代码就这几行(Vue 3 + Vite)
别被网上那些 webpack 配置吓到,Vite 下按需引入简单到离谱。我用的是 ant-design-vue,但思路通用。
首先,安装插件:
npm install -D unplugin-vue-components
然后在 vite.config.js 里加一行:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [AntDesignVueResolver()]
})
]
})
完事。现在你直接在组件里写:
<template>
<a-button type="primary">点我</a-button>
<a-input v-model:value="text" />
</template>
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>
不用手动 import,也不用全局注册。插件自动识别 a-button、a-input 这些标签,只打包你用到的组件。是不是比手写 import 省事多了?
这个场景最好用:动态组件 or 条件渲染
有时候组件是动态决定的,比如根据用户角色显示不同按钮。这时候很多人会慌:“按需引入还能生效吗?”
能!只要你的模板里写了标签名,插件就能识别。比如:
<template>
<component :is="userRole === 'admin' ? 'a-button' : 'a-tag'">
{{ userRole === 'admin' ? '管理' : '普通用户' }}
</component>
</template>
但注意:如果完全用字符串拼接,比如 :is=",那插件可能识别不到。稳妥起见,这种动态场景我建议显式 import 一下,或者用计算属性返回确定的组件名。a-${type}"
另外,条件渲染也没问题:
<template>
<a-modal v-if="showModal" v-model:open="showModal" />
</template>
哪怕 showModal 初始是 false,只要模板里有 a-modal 标签,它就会被按需引入。这点我专门验证过,放心用。
踩坑提醒:这三点一定注意
我折腾了半天,踩了几个坑,这里重点说说:
- 样式丢失问题:早期 ant-design-vue 的按需引入需要单独引入样式,但现在
AntDesignVueResolver默认开启importStyle: 'css',会自动引入对应 CSS。但如果你用的是 less,记得改成importStyle: true,否则样式会乱。不过现在大部分项目都直接用 CSS 版本,所以默认配置基本够用。 - 自定义组件命名冲突:如果你自己写了叫
MyButton的组件,而 UI 库也有MyButton(虽然概率低),可能会冲突。解决办法是在 resolver 里加前缀,比如:AntDesignVueResolver({ prefix: 'Ad' })这样你就要写
ad-button。但一般没必要,UI 库组件都有固定前缀(如 a-、el-),冲突概率极低。 - 服务端渲染(SSR)兼容性:如果你用 Nuxt 3 或其他 SSR 框架,
unplugin-vue-components依然可用,但要确保插件在客户端和服务端都运行。Nuxt 3 官方推荐的方式就是直接装这个插件,不用额外配置。但如果你遇到 hydration 错误,检查是不是某些组件没在服务端正确注册——不过这种情况极少,我试过 Nuxt 3 + antd 没问题。
高级技巧:手动控制 or 混合使用
有些时候你不想自动引入,比如某个页面要用一整套图表组件,手动 import 反而更清晰。这时候可以关闭自动引入,局部用传统方式:
// vite.config.js 里可以针对特定目录禁用
Components({
dirs: ['src/components'], // 只扫描这个目录
resolvers: [AntDesignResolvers()],
// 或者用 exclude 排除某些文件
})
或者,你可以在单个组件里显式 import,和自动引入混用。Vite 会自动去重,不会重复打包。比如:
<script setup>
// 手动引入一个特殊组件
import { DatePicker } from 'ant-design-vue'
// 其他组件如 a-button 依然自动引入
</script>
这样灵活度更高。我有个项目里,90% 组件用自动引入,剩下 10% 复杂组件(比如带自定义逻辑的 Table)手动 import,结构更清晰。
不止 UI 库,API 工具函数也能按需
按需引入不只是 UI 库的专利。比如你用 lodash,全量引入 import _ from 'lodash' 会把整个库打包进去。其实可以这样:
// 只引入用到的函数
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
或者用 babel-plugin-lodash(虽然 Vite 不用 babel,但有对应的 esbuild 插件)。不过现在更推荐直接路径引入,简单直接,tree-shaking 也能生效。
再比如 axios,如果你封装了 API,可以这样按需导出:
// api/user.js
export const getUser = () => fetch('https://jztheme.com/api/user')
export const updateUser = (data) => fetch('https://jztheme.com/api/user', { method: 'PUT', body: data })
然后在组件里只 import 需要的函数:
import { getUser } from '@/api/user'
这样打包时没用到的 API 函数就不会被打包进去。虽然省的体积不大,但积少成多。
最后说两句
按需引入不是银弹,但它是最简单、见效最快的优化手段之一。我见过太多项目因为图省事全量引入,导致首屏慢得离谱。花 10 分钟配一下,收益立竿见影。
当然,这个技术的拓展用法还有很多,比如结合 lazy loading 做更细粒度的拆分,或者在微前端里做依赖隔离。后续会继续分享这类博客。
以上是我踩坑后的总结,希望对你有帮助。有更优的实现方式欢迎评论区交流。

暂无评论