按需引入实战:减少打包体积提升前端性能的关键技巧

设计师哲玮 优化 阅读 1,263
赞 8 收藏
二维码
手机扫码查看
反馈

先看效果,再看代码

上周我接手一个老项目,打包后 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-buttona-input 这些标签,只打包你用到的组件。是不是比手写 import 省事多了?

这个场景最好用:动态组件 or 条件渲染

有时候组件是动态决定的,比如根据用户角色显示不同按钮。这时候很多人会慌:“按需引入还能生效吗?”

能!只要你的模板里写了标签名,插件就能识别。比如:

<template>
  <component :is="userRole === 'admin' ? 'a-button' : 'a-tag'">
    {{ userRole === 'admin' ? '管理' : '普通用户' }}
  </component>
</template>

但注意:如果完全用字符串拼接,比如 :is="a-${type}",那插件可能识别不到。稳妥起见,这种动态场景我建议显式 import 一下,或者用计算属性返回确定的组件名。

另外,条件渲染也没问题:

<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 做更细粒度的拆分,或者在微前端里做依赖隔离。后续会继续分享这类博客。

以上是我踩坑后的总结,希望对你有帮助。有更优的实现方式欢迎评论区交流。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论