编译与打包优化
构建阶段的代码分割、Tree Shaking 与 SFC 编译优化。运行时见 runtime。
在前端工程化中,性能优化不仅局限于运行时,更应前置到编译和打包阶段。通过合理的构建配置、代码分割、Tree Shaking 及 Vue 特有的编译优化,可以显著减少产物体积、提升加载速度及运行时效率,本节将围绕这些核心点展开。
一、异步组件与懒加载
懒加载可将应用拆分成更小的代码块,仅在需要时才加载,有效减少首屏资源体积。
1. defineAsyncComponent 的完整配置
Vue 3 提供了 defineAsyncComponent 用于定义异步组件,支持丰富的配置选项(加载成功/加载中/加载失败的状态展示,以及超时控制)。
import { defineAsyncComponent } from "vue";
const AsyncComponent = defineAsyncComponent({
// 实际加载组件函数(必须返回 Promise)
loader: () => import("./HeavyComponent.vue"),
// 加载过程中展示的组件(可选)
loadingComponent: LoadingComponent,
// 加载失败时展示的组件(可选)
errorComponent: ErrorComponent,
// 展示 loadingComponent 之前的延迟(默认 200ms,防止闪烁)
delay: 200,
// 超时时间(毫秒),超时后显示 errorComponent
timeout: 3000,
// 是否支持 Suspense(默认 true)
suspensible: false,
// 服务端渲染时启用
serverPrefetch: true,
});
使用场景:
- 首屏不可见的复杂图表组件、表格组件。
- 根据用户交互才触发的弹窗、表单等。
- 需要 fallback 状态(如加载中或错误重试)的组件。
2. 路由级懒加载 vs 组件级懒加载的取舍
| 层级 | 方式 | 优点 | 缺点 |
|---|---|---|---|
| 路由级 | 在路由配置中使用 () => import('@/views/Home.vue') | 粒度大,简单高效,首屏只加载当前路由组件 | 组件较小时可能产生过多小 chunk,增加请求数 |
| 组件级 | 在组件内部使用 defineAsyncComponent 动态引入子组件 | 更精细的控制(如弹窗、折叠面板等按需加载),减少初始加载 | 需手动管理,引入一定复杂度 |
取舍原则:
- 默认使用路由级懒加载,将每个页面路由打包为独立 chunk。
- 对于页面内用户可能要过一段时间才看到的区域(如底部评论区、模态框、标签页中的隐藏面板),使用组件级懒加载。
- 对于稳定高频的核心组件(如导航栏、侧边栏)则不应该懒加载,应同步加载。
二、Tree Shaking
Tree Shaking 是指构建工具(Webpack、Vite)通过静态分析,剔除未被使用的代码。
1. Vue 3 的按需引入设计
Vue 3 的核心 API(如 ref、reactive、computed、watch 等)都采用 ESM 命名导出,且无副作用(side effect free)。这意味着构建工具可以安全地“摇掉”未使用的 API。
// 只引入 ref 和 computed,未使用的 reactive 不会被打包
import { ref, computed } from "vue";
对比 Vue 2 全局 Vue 对象,Tree Shaking 非常困难。Vue 3 的做法大幅减少了打包体积。
2. 确保第三方库支持 Tree Shaking
- 使用 ES 模块版本(如
lodash-es替代lodash)。 - 检查
package.json中是否包含sideEffects: false或明确声明。 - 配置构建工具保留模块结构(如 Vite 默认支持,Webpack 需
mode: 'production')。
3. SFC 中的 Tree Shaking
Vue 单文件组件(SFC)中的 <template> 和 <style> 也会参与 Tree Shaking:如果整个组件未被引用,则其所有依赖均不会打包。
三、Vue 单文件组件的编译优化
Vue 3 的编译器(@vue/compiler-dom)在编译 SFC 时会执行多项优化,减少运行时代码量和 Diff 压力。
1. 静态标记(PatchFlag)
在编译模板时,编译器会为动态节点添加一个 PatchFlag(数字标记),指示该节点在更新时可能发生哪些变化(文本、属性、类、样式等)。
<div :class="activeClass" :id="dynamicId">{{ message }}</div>
编译后大致生成:
_createVNode(
"div",
{
class: _normalizeClass(activeClass),
id: dynamicId,
},
_toDisplayString(message),
3 /* TEXT + CLASS + PROPS */,
);
运行时 Diff:渲染器根据 PatchFlag 仅检查对应类型的变更,而不需要深度对比整个 VNode 树。例如 TEXT 标记意味着只需对比文本内容,CLASS 标记只需对比 class 属性。
优势:跳过静态子树的 Diff(静态提升),大幅提升组件更新性能。
2. 事件缓存
Vue 3 编译器会自动缓存模板中的内联事件处理函数,避免每次渲染都创建新函数。
<button @click="count++">Increment</button>
传统做法:每次重新渲染都会生成一个新的箭头函数,导致子组件不必要的重新渲染(如果子组件通过 v-on 监听)。Vue 3 编译器将其转换为:
_createVNode("button", { onClick: _cache[0] || (_cache[0] = ($event) => count.value++) });
这样 onClick 始终保持同一个函数引用,有利于优化子组件的 shouldUpdate。
3. 静态提升(Static Hoisting)
编译器会将纯静态的节点提升到渲染函数外部,避免每次渲染都重新创建。
<div class="static">I never change</div>
提升后:
const _hoisted_1 = _createVNode("div", { class: "static" }, "I never change", -1 /* HOISTED */);
function render() {
return _hoisted_1;
}
多个静态相邻元素会被合并为一个 VNode(_createStaticVNode),进一步减少内存占用。
4. 块树优化(Block Tree)
对于带有 v-if、v-for 的结构,Vue 3 引入 Block 概念,收集动态节点,使 Diff 直接定位到动态部分,无需遍历整个树。
四、构建工具配置优化
以 Vite 为例(同样适用于 Webpack),通过合理配置可以进一步优化生产产物。
1. Vite 预构建(Pre-bundling)
Vite 在开发环境下使用 esbuild 预构建依赖(如 vue、element-plus 等),将 CommonJS 或 UMD 转换成 ESM,并合并多个内部模块,减少浏览器请求数量。
生产环境:Vite 使用 Rollup 进行打包,同样可以对依赖提前优化,通过 optimizeDeps 选项控制预构建行为。
// vite.config.js
export default {
optimizeDeps: {
include: ["lodash-es", "axios"], // 强制预构建
exclude: ["your-local-lib"], // 排除无需预构建的库
},
};
2. 代码压缩(Minify)
生产环境必须开启代码压缩,Vite 默认使用 esbuild(极快)或 terser(更兼容,但慢)。
build: {
minify: 'esbuild', // 或 'terser'
terserOptions: { compress: { drop_console: true } } // 移除 console
}
对于大型项目,建议使用 esbuild 压缩以获得更快速度,同时开启 target 降低语法转化级别。
3. CDN 外部化(Externals)
将一些大型库(如 vue、element-plus、echarts)通过 CDN 引入,不打包到 bundle 中,利用浏览器缓存和 CDN 加速。
Vite 配置:
import { defineConfig } from "vite";
export default defineConfig({
build: {
rollupOptions: {
external: ["vue", "element-plus"],
output: {
globals: {
vue: "Vue",
"element-plus": "ElementPlus",
},
},
},
},
});
同时需要在 index.html 中通过 <script> 标签引入这些库。注意:外部化会增加依赖管理复杂度,推荐仅对稳定、大体积、跨项目共享的库使用。
4. 分块策略(Chunk Split)
合理分割第三方库和业务代码,避免单个 chunk 过大。
Rollup / Vite 配置:
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('vue')) return 'vendor-vue';
if (id.includes('element-plus')) return 'vendor-element';
return 'vendor';
}
}
}
}
}
通过手动分块,将 vue、element-plus 等单独打包,用户缓存效果更好。
五、工程级优化总结
| 优化类别 | 关键技术点 | 收益 |
|---|---|---|
| 异步组件与懒加载 | defineAsyncComponent 完整配置;路由级懒加载 + 组件级懒加载组合 | 减少首屏 JS 体积,提升加载速度 |
| Tree Shaking | 使用 ESM 导入 API;检查 sideEffects;构建工具开启生产优化 | 移除未使用代码,减少最终 bundle 体积 |
| SFC 编译优化 | 静态标记(PatchFlag)、事件缓存、静态提升、块树优化 | 提升运行时渲染性能,减少 Diff 开销 |
| 构建工具配置 | Vite 预构建;代码压缩(esbuild/terser);CDN 外部化;分块策略 | 更快的构建速度,更小的产物,更好的缓存 |
工程级性能优化需要贯穿开发、构建、部署全流程。Vue 3 + Vite 的组合提供了开箱即用的优秀优化,而深入理解上述机制,可以在遇到性能瓶颈时更有针对性地调优。
参考文献
以下链接在编写时均可正常访问:
相关文章
资源优化
图片懒加载、按需引入与 Web Vitals 监控。见 HTML 图片、CSS 字体优化。
运行时优化
减少不必要的渲染与响应式开销。构建侧见 build。
构建与分包
通过代码分割与构建配置减小首屏 JavaScript 体积。涵盖 React.lazy/Suspense、路由级分割、Tree Shaking、Vite 生产构建、体积分析、最佳实践与常见坑。运行时优化见 runtime。
GridView 宫格加载渲染优化
系统首页 GridView 宫格模块接口耗时不高,但首次进入总耗时接近 22s。复盘耗时分层定位过程、代码层面的瓶颈(约 2000 行、100 个 tab 重复节点)、优化手段与最终指标对比。
新商家系统性能优化实践
商家系统新版本上线后,团队持续针对构建效率与用户体验做了一轮系统性优化。本文记录核心方案与落地结果,供后续版本复用。
老系统升级与兼容改造实践
公司原有业务系统采用 Node + jQuery 架构,长期运行后暴露出典型遗留系统问题: