鬼故事
46.88MB · 2025-10-29
“分包” 就是按 “使用时机” 和 “功能” 将代码分割成多个小文件,核心是 “按需加载”,解决传统单包模式下 “体积过大、加载慢” 的问题。
import() 动态导入语法和打包工具(Webpack/Vite)的配置;首屏加载:浏览器下载主包(app.js)和当前页面必需的分包(如首页路由的 home.js);
解析执行:主包代码先执行,初始化应用(如创建 Vue 实例、配置路由);
按需加载:当用户触发某个操作(如跳转路由、点击按钮),需要新的分包时:
import() 动态请求对应的分包文件(如 order.js);路由拆分的关键是修改 router/index.js 中 “路由组件的导入方式”,将静态 import 改为动态 () => import()。
所有页面代码会打包到一起,不推荐:
// router/index.js(未拆分,不推荐)
import Vue from 'vue';
import Router from 'vue-router';
// 静态导入所有路由页面(会全部打包到核心 JS)
import Home from '@/views/Home';
import About from '@/views/About';
import User from '@/views/User';
Vue.use(Router);
export default new Router({
routes: [
{ path: '/', name: 'Home', component: Home },
{ path: '/about', name: 'About', component: About },
{ path: '/user', name: 'User', component: User }
]
});
每个路由页面会被拆分为独立 Chunk:
// router/index.js(已拆分,推荐)
import Vue from 'vue';
import Router from 'vue-router';
// 无需静态导入页面,改为动态导入
import ElementUI from 'element-ui'; // 第三方 UI 库(会被拆到 chunk-vendors)
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(Router);
Vue.use(ElementUI);
export default new Router({
routes: [
{
path: '/',
name: 'Home',
// 动态导入:Home 页面会被拆分为独立 Chunk
component: () => import('@/views/Home')
},
{
path: '/about',
name: 'About',
// 可选:给 Chunk 自定义名称(打包后文件名更清晰)
component: () => import(/* webpackChunkName: "about-page" */ '@/views/About')
},
{
path: '/user',
name: 'User',
component: () => import('@/views/User')
}
]
});
打包前后对比:
非按需引入:
按需引入:
组件的分包拆分(即 “异步组件”)是前端性能优化的关键手段之一,核心是将 “非首屏必需、体积较大或按需加载的组件” 从主页面代码中分离,单独打包成独立文件,仅在组件被使用时才加载。
不是所有组件都需要拆分,以下三类组件是 “分包重点”:
Vue 2 中通过 “动态 import + 组件注册” 实现分包,无需额外 API:
<!-- 页面组件:ProductDetail.vue(商品详情页) -->
<template>
<div>
<!-- 主内容:立即加载 -->
<div class="product-basic">图片、标题、价格...</div>
<!-- 按需加载的组件:点击按钮才显示 -->
<el-button @click="showComment = true">查看评价</el-button>
<comment-list v-if="showComment" /> <!-- 评价列表组件(需拆分) -->
</div>
</template>
<script>
export default {
components: {
// 关键:动态导入组件,实现分包
CommentList: () => import('@/components/CommentList.vue')
},
data() {
return {
showComment: false // 控制组件显示,初始为 false(不加载)
};
}
};
</script>
() => import('路径') 告诉 Webpack/Vite:“这个组件不是必须的,打包时单独拆成一个文件”。showComment 变为 true(用户点击按钮)时,浏览器才会请求 CommentList 对应的 JS/CSS 文件。defineAsyncComponent(更强大、更完善、给vue官方点)Vue 3 提供了 defineAsyncComponent API,专门用于异步组件,支持加载状态、错误处理等高级配置:
<!-- 页面组件:ProductDetail.vue -->
<template>
<div>
<div class="product-basic">图片、标题、价格...</div>
<el-button @click="showComment = true">查看评价</el-button>
<CommentList v-if="showComment" />
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue';
// 导入加载中、加载失败的占位组件(可选)
import Loading from '@/components/Loading.vue';
import Error from '@/components/Error.vue';
// 关键:用 defineAsyncComponent 定义异步组件,实现分包
const CommentList = defineAsyncComponent({
loader: () => import('@/components/CommentList.vue'), // 动态导入路径
loadingComponent: Loading, // 组件加载过程中显示的占位符
errorComponent: Error, // 组件加载失败时显示的内容
delay: 200, // 延迟 200ms 显示 loading(避免一闪而过)
timeout: 5000 // 5秒内未加载完成则视为失败
});
const showComment = ref(false);
</script>
defineAsyncComponent 能处理加载状态(避免用户看到空白)和错误情况(如网络故障),体验更友好。默认情况下,拆分的组件文件会以哈希值命名(如 123.js),可通过 Webpack 魔法注释自定义名称:
// Vue 2 中
components: {
CommentList: () => import(/* webpackChunkName: "comment-list" */ '@/components/CommentList.vue')
}
// Vue 3 中
const CommentList = defineAsyncComponent({
loader: () => import(/* webpackChunkName: "comment-list" */ '@/components/CommentList.vue')
});
打包后会生成 comment-list.xxxx.js,更易识别。
如果多个小异步组件(如弹窗 A、弹窗 B)都依赖同一个工具函数,可通过 “统一 chunk 名称” 将它们合并打包:
// 弹窗 A 组件
const PopupA = () => import(/* webpackChunkName: "popups" */ '@/components/PopupA.vue');
// 弹窗 B 组件
const PopupB = () => import(/* webpackChunkName: "popups" */ '@/components/PopupB.vue');
打包后,PopupA 和 PopupB 会合并到 popups.xxxx.js 中,避免生成过多小文件(小文件过多会增加 HTTP 请求次数)。
打包后查看产物:执行 npm run build,在 dist/js 目录中查找是否有组件对应的独立文件(如 comment-list.xxxx.js)。
浏览器 Network 面板:
Network 中的 JS 文件,确认异步组件的文件未被加载。组件的分包拆分是 “同一页面内的按需加载优化”,与路由拆分(不同页面的按需加载)形成互补。核心逻辑是:用动态导入让非必需组件 “延迟加载”,减少首屏代码体积。实现时需注意 “按需拆分”(只拆大组件、按需组件),避免过度拆分导致请求增多。
Vue CLI 官方文档 - 构建优化在 Vue CLI 官方文档的「构建优化」章节中提到,其内置的 Webpack 配置会自动拆分代码,具体包括:
vue、vue-router 等)和应用代码,避免第三方库被重复打包。文档中明确说明:Vue CLI 的默认配置已针对大多数应用做了优化,包括合理的代码拆分策略。
Vue CLI 内置 Webpack 配置解析Vue CLI 通过 @vue/cli-service 封装了 Webpack 配置,其默认的 splitChunks 配置逻辑可通过以下方式验证:
执行 vue inspect --plugin splitChunks 命令(在 Vue CLI 项目根目录),可查看内置的代码拆分配置。
输出结果中会包含类似以下的核心配置(简化版):
splitChunks: {
chunks: 'all', // 对所有类型的 chunk(初始、异步、所有)进行拆分
cacheGroups: {
vendors: {
name: 'chunk-vendors', // 第三方库拆分后的文件名
test: /[/]node_modules[/]/, // 匹配 node_modules 中的第三方库
priority: 10, // 优先级高于默认的 common 组
chunks: 'initial' // 针对初始 chunk 拆分
},
common: {
name: 'chunk-common', // 公共代码拆分后的文件名
minChunks: 2, // 被至少 2 个 chunk 共享才会拆分
priority: 1, // 优先级低于 vendors 组
reuseExistingChunk: true // 复用已存在的 chunk
}
}
}
这一配置明确将 node_modules 中的第三方库(如 vue、axios 等)拆分为 chunk-vendors.js,而应用自身代码和公共组件拆分为其他 chunk,与官方描述一致。也就是所有的三方库为一个大的文件,其他的为一个文件这样的形式去打包
假设项目有两个独立业务模块:
echarts、chart.js;xlsx、pdfjs-dist。默认分包会把这 4 个库全部混入 chunk-vendors.js,如果用户只访问 “数据可视化模块”,xlsx 和 pdfjs-dist 的代码就是 “无效加载”;且只要其中一个库更新(如 echarts 升级),整个 chunk-vendors.js 的 hash 会变,导致所有依赖这个包的页面缓存失效。
按业务模块拆分第三方库,让每个模块的依赖独立打包:
// vue.config.js
module.exports = {
configureWebpack: {
optimization: {
splitChunks: {
cacheGroups: {
// 1. 数据可视化模块的第三方库
vendor-visual: {
test: /[/]node_modules[/](echarts|chart.js)[/]/,
name: 'chunk-vendor-visual', // 独立包:仅包含可视化相关库
priority: 20,
chunks: chunk => chunk.name.includes('visual') // 仅对“可视化模块页面”生效
},
// 2. 文档处理模块的第三方库
vendor-doc: {
test: /[/]node_modules[/](xlsx|pdfjs-dist)[/]/,
name: 'chunk-vendor-doc', // 独立包:仅包含文档相关库
priority: 20,
chunks: chunk => chunk.name.includes('doc') // 仅对“文档模块页面”生效
},
// 3. 通用核心库(vue、vue-router 等)
vendors: {
test: /[/]node_modules[/]/,
name: 'chunk-vendors',
priority: 10,
// 排除上述两个业务模块的依赖
exclude: /[/]node_modules[/](echarts|chart.js|xlsx|pdfjs-dist)[/]/
}
}
}
}
}
};
chunk-vendors.js + chunk-vendor-visual.js,无无效代码;echarts 升级时,仅 chunk-vendor-visual.js 的 hash 变化,chunk-vendor-doc.js 和通用 chunk-vendors.js 的缓存不受影响,提升后续访问速度。不管是 Vue CLI 还是 Vite,对 Vant UI 的打包处理逻辑都和 “引用范围” 强相关,先理清本质差异:
| 引用方式 | 打包结果 | 核心逻辑 |
|---|---|---|
| 全局引用 | 所有 Vant 组件(即使没用到)都打包进 chunk-vendors.js(或类似第三方库 chunk),最终只有 1 个第三方库文件 | 全局注册时,Webpack/Vite 会把整个 vant 包视为 “必需依赖”,无法 Tree-Shaking 剔除未使用组件 |
| 按需引用 | 只打包你实际用到的 Vant 组件(如 Button、Dialog),每个组件(或组件组)可能拆成独立小 chunk(如 chunk-vant-button.js),最终会多几个小文件 | 按需引入时(如 import Button from 'vant/lib/button' 或用 Vant 插件),工具能精准识别 “用到的代码”,未使用组件被 Tree-Shaking 剔除,同时按组件拆分 chunk |
Button、Toast、Dialog 3 个组件,按需引用能避免打包 150KB+ 的全量包,体积优势明显。Vue CLI 默认会对 “体积超过 30KB(压缩前)” 的依赖单独拆分,但有时会出现两种问题:
lodash-es 的子模块、date-fns)被拆分成多个小 chunk,导致浏览器请求数增加(HTTP/1.1 环境下会阻塞加载);lodash 的 debounce 方法),默认未合并,导致代码冗余。// vue.config.js
module.exports = {
configureWebpack: {
optimization: {
splitChunks: {
minSize: 10000, // 调整最小分包体积(如 10KB 以下不单独拆分)
cacheGroups: {
// 合并小体积工具库(lodash-es、date-fns 等)
vendor-utils: {
test: /[/]node_modules[/](lodash-es|date-fns|dayjs)[/]/,
name: 'chunk-vendor-utils', // 合并成一个工具库包
priority: 20,
minSize: 0, // 强制合并,忽略 minSize 限制
minChunks: 2 // 被引用超过 2 次才拆分(避免单次引用的小库被合并)
}
}
}
}
}
};
当满足以下任一条件时,就需要手动干预 Vue CLI 的第三方库分包:
简单说:Vue CLI 的默认分包是 “通用方案”,当项目有个性化的性能优化需求或特殊依赖场景时,就需要手动配置 splitChunks 来调整分库逻辑。
2025-10-30
国产类魂游戏《明末:渊虚之羽》今日上线,Steam 国区 248 元起
2025-10-30
国产游戏《明末:渊虚之羽》首发 Steam“差评如潮”:被指灾难级优化、环国区永久降价