Vue 3 项目首屏加载慢如何通过按需导入 Composition API 优化?

文章导读
如果你遇到 Vue 3 项目首屏加载慢的问题,第一个反应可能是上 CDN、开 gzip 或者拆路由懒加载。这些确实有效,但有一种情况容易被忽略:Composition API 的导入方式不对,导致整个 Vue 运行时被全量打包,首屏 JS 体积凭空多出几十 KB,甚至上百 KB。下面这篇笔记记的是我当时排查和处理的步骤,供你在类似场景下参考。
📋 目录
  1. 先确认问题出在 Bundle 体积
  2. 检查导入语句:全量导入 vs 按需导入
  3. 操作动作:修改导入方式并验证 Tree-Shaking
  4. 风险边界:留意全局注册和 Side Effects
  5. 改完后看这几个信号
  6. FAQ:两个常见疑问
A A

如果你遇到 Vue 3 项目首屏加载慢的问题,第一个反应可能是上 CDN、开 gzip 或者拆路由懒加载。这些确实有效,但有一种情况容易被忽略:Composition API 的导入方式不对,导致整个 Vue 运行时被全量打包,首屏 JS 体积凭空多出几十 KB,甚至上百 KB。下面这篇笔记记的是我当时排查和处理的步骤,供你在类似场景下参考。

先确认问题出在 Bundle 体积

有一次我接手一个 Vue 3 项目,首屏加载在低端机子上要等将近两秒。先看网络面板,一个 app.js 接近 600 KB(未压缩)。用 webpack-bundle-analyzer 或 Vite 内置的 rollup-plugin-visualizer 扫一下打包产物,发现 vue.esm-bundler.js 这一项占了很大比例,里面包含了整个 Composition API 的运行时方法。这就说明代码里可能用错了导入方式。

检查导入语句:全量导入 vs 按需导入

Vue 3 官方推荐使用按需导入,例如 import { ref, reactive, computed } from 'vue'。如果代码里出现 import Vue from 'vue'(Vue 2 遗留写法)或者 import * as Vue from 'vue',tree-shaking 就很难生效,打包工具会把整个模块保留下来。我当时的项目里有一个公共工具函数,写成了 import * as Vue from 'vue'; Vue.ref(...),结果几乎所有 Composition API 函数都被打包了。改成 import { ref } from 'vue' 后,体积减少了约 30%(具体数值因项目而异,需要你自己测量)。

另外要注意,如果同时使用 @vue/composition-api 插件(比如从 Vue 2 升级过渡),同样需要按需导入,并且确保插件版本与 Vue 版本匹配,否则可能引入额外的 polyfill。

操作动作:修改导入方式并验证 Tree-Shaking

具体操作分两步:第一步,全局搜索项目中所有 from 'vue' 的导入,把 import * as Vue 替换成按需导入的单条语句。第二步,确认打包工具配置没有禁用 tree-shaking。对于 Vite(基于 Rollup),默认就是按需打包,一般不用额外配置;对于 Webpack 4/5,需要保证 mode: 'production' 以及 optimization.usedExports: true。检查 webpack.config.js 中是否误设了 sideEffects: false 导致整个模块被标记为无副作用而跳过 tree-shaking —— 这个反而会影响按需导入的效果。

Vue 3 项目首屏加载慢如何通过按需导入 Composition API 优化?

改完后重新构建,对比两次的 app.js 大小。如果减小了 10% 以上,说明导入方式确实是瓶颈之一。同时用浏览器 DevTools 的 Coverage 面板(打开后按 Ctrl+Shift+P 输入 Coverage)录制首屏加载,查看未使用的代码比例是否下降。

风险边界:留意全局注册和 Side Effects

按需导入并不是万能的。如果你在 main.js 中使用了 Vue.provide 或者 Vue.directive 这类全局 API,它们必须从 import Vue from 'vue'import { createApp } from 'vue' 引入。这时候 createApp 是整个模块的入口,无法 tree-shake 掉其内部引用的其他 API。但好在 createApp 本身不会引入所有 Composition API 方法,影响有限。另外,如果你的组件库(例如 Ant Design Vue、Element Plus)在内部使用了全量导入,那你在业务中再怎么按需导入,组件库的打包体积依然会很大。这时候需要额外配置组件库的按需加载(通常使用各自的 babel 插件或 Vite 插件)。

还有一个容易踩的坑:如果你在 setup 中使用了 import { h } from 'vue' 来创建虚拟节点,而其他文件用 import * as Vue,那么 h 可能被重复打包。保持项目中导入风格一致很重要。

Vue 3 项目首屏加载慢如何通过按需导入 Composition API 优化?

改完后看这几个信号

优化后,除了看打包体积,还应该关注以下几个信号:

  • 构建时间:按需导入一般不会显著改变构建时间,但如果之前因为全量导入导致模块分析变慢,改后可能会略有提升。
  • 页面首次交互时间(FID):JS 体积减小能直接缩短下载和解析时间,但 FID 改善程度还要看剩余代码的复杂度。
  • 运行时错误:如果按需导入漏掉了某个用到的 API,控制台会报 Uncaught ReferenceError。建议在开发环境跑一遍所有页面和功能,或者利用 TypeScript 检查。

如果按以上步骤做了,但体积没有明显变化,那就要排查其他因素,比如图片未压缩、依赖包体积过大、使用了不支持 tree-shaking 的库等。这时候再针对性开路由懒加载或者异步组件也不迟。

FAQ:两个常见疑问

Q:Vite 项目是不是就不需要担心这个?
A:Vite 开发模式使用 esbuild 预构建依赖,生产环境用 Rollup 打包,两者都支持 tree-shaking。但如果代码里写成 import * as Vue,Rollup 仍然会保留整个模块,只是比 Webpack 更容易做一些摇树优化,但并非万无一失。最好还是显式按需导入。

Q:用了 defineComponent 会不会影响按需导入?
A:defineComponent 本身是函数,按需导入没有问题。但如果你在 defineComponent 内部又全量引用了 Vue,那依然需要修改。建议在组件内也只导入实际用到的 API。