尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Vue项目打包优化:拆包与依赖治理解决首屏加载慢

Vue项目打包优化:拆包与依赖治理解决首屏加载慢 上周帮一个朋友看他那个 Vue 后台打包完 app.4f3a2c.js 1.8MBchunk-vendors.9c1d2e.js 2.9MB首屏在弱网下白屏快 6 秒。他问我是不是要换 Vite我说先别急。Vue 项目打包后 app.xxx.js 和 chunk-vendors.xxx.js 文件太大导致页面加载时间太长这个问题八成不是框架的锅是拆包和依赖治理没做到位。Vue CLI 和 webpack 本身已经把很多优化开关藏在了默认配置里只是项目一旦引入几个大块头依赖默认策略就撑不住了。这篇文章面向正在被首屏加载时间折磨的 Vue 开发者不管你用的是 Vue 2 还是 Vue 3只要项目基于 webpack 打包里面的分析思路、拆包配置、第三方库瘦身、压缩部署和排查方法都能直接抄。我不会只丢几个配置就完事而是把每个动作背后的原因、参数怎么算、哪里容易翻车都拆开讲。1. 先别急着改配置搞清 app 和 chunk-vendors 为什么这么大1.1 app.xxx.js 和 chunk-vendors.xxx.js 到底装了什么很多人看到 app.xxx.js 大第一反应是把 main.js 删一删其实方向反了。app.xxx.js 通常是你整个应用的入口业务包里面装着 main.js、App.vue、全局路由、全局状态、全局注册的组件、全局指令、全局样式以及所有被同步引入的业务模块。它之所以会膨胀往往不是因为某一行代码写得长而是因为路由同步引入、组件全局注册、大 JSON 直接 import、SVG 被内联成字符串、图片被 base64 塞进 JS 这些操作。比如你在 router/index.js 里写 import Home from /views/Home.vue再在 routes 里 component: Homewebpack 就会认为 Home 是入口依赖必须打进 app 包。十几个页面全这么写app.xxx.js 不大才怪。chunk-vendors.xxx.js 则是 webpack 的 splitChunks 默认策略抽出来的第三方依赖包。只要某个模块来自 node_modules并且被多个 chunk 引用或者满足默认 vendors 缓存组条件它就会被塞进 chunk-vendors。这个文件名看起来人畜无害实际却是体积重灾区。Element UI、Element Plus、ECharts、lodash、moment、xlsx、video.js、地图 SDK、富文本编辑器随便来两三个chunk-vendors 就能冲到 2MB 以上。更麻烦的是它通常属于首屏初始请求因为 main.js 里只要 import 了 UI 库它就会跟着入口一起被加载。所以你要建立第一个认知app 大多半是业务代码引入方式有问题chunk-vendors 大多半是第三方依赖引入方式有问题。两者要分开治不能一锅乱炖。1.2 用 webpack-bundle-analyzer 做一次体检不分析就改配置等于蒙眼修车。webpack-bundle-analyzer 是我最常用的体检工具它能把每个 chunk 里的模块体积画成方块图谁大谁小一眼就能看出来。安装很简单npm install --save-dev webpack-bundle-analyzer然后在 vue.config.js 里加插件。Vue CLI 项目如果已经装了 analyzer直接 npm run build --report 也能出报告但手动配置更灵活const BundleAnalyzerPlugin require(webpack-bundle-analyzer).BundleAnalyzerPlugin module.exports { configureWebpack: { plugins: [ new BundleAnalyzerPlugin({ analyzerMode: server, analyzerPort: 8888, openAnalyzer: true, gzipSize: true }) ] } }构建完成后浏览器会自动打开 127.0.0.1:8888你能看到三个关键体积stat size、parsed size、gzip size。stat size 是模块源码大小parsed size 是 webpack 解析后的大小gzip size 是压缩传输后的大小。真正影响用户下载时间的是 gzip size但 parsed size 会影响浏览器解析和执行时间。如果某个库 stat 很大但 gzip 很小比如大量重复字符串说明压缩收益高如果 gzip 也很大那就必须动刀。我第一次分析一个项目时发现 chunk-vendors 里 ECharts 占了 1.1MBlodash 占了 540KBmoment 占了 230KB而首页其实只用了一个折线图、一个 debounce 和两个日期格式化。这种依赖引法不优化等于让用户替你下载一堆用不上的代码。1.3 判断瓶颈下载慢、解析慢、执行慢要分开治很多人把“页面加载慢”当成一个单一问题其实要拆成下载、解析、执行三段。下载慢看 Network 面板里的 gzip 后传输体积和请求数。解析慢看 Performance 面板里的 Parse HTML、Parse Script、Compile Script 耗时。执行慢看 Evaluate Script、Function Call 和主线程长任务。为什么有的项目 JS 只有 800KB但首屏还是很慢因为代码在入口顶层做了大量同步初始化比如全局注册几十个组件、同步创建 ECharts 实例、同步读取大 JSON、同步执行权限路由计算。这种体积不大但执行重的情况光靠拆包解决不了必须把初始化逻辑延后到路由进入或组件 mounted 之后。我一般按这个优先级处理先砍掉首屏不需要的第三方库再做路由和组件懒加载然后调整 splitChunks 切分最后上 gzip 和 brotli。为什么这个顺序因为如果先把一个 2MB 的 vendors 切成 20 个小包请求数上去了首屏总下载量没降优化效果很有限。先把 ECharts、xlsx、富文本、地图这些重依赖从首屏移走再谈切包收益才明显。这里有个实测经验一个后台项目把 ECharts 改成按需加载后chunk-vendors 从 2.7MB 降到 1.2MB首屏 JS gzip 从 920KB 降到 340KB弱网下 LCP 直接从 5.8 秒降到 2.1 秒。动作不复杂关键是用分析工具找到了真凶。注意分析报告里如果出现多个 chunk 都包含同一个大库说明 splitChunks 没有把它抽成公共包用户可能重复下载。这个问题在手动配置 cacheGroups 后尤其常见改完一定要回看报告。2. 路由与组件级拆包让首屏只加载该加载的代码2.1 路由懒加载的正确写法和三个常见坑路由懒加载是 Vue 项目最划算的优化没有之一。它把页面组件从入口依赖变成异步 chunk用户访问哪个路由才下载哪个页面的代码。正确写法是const routes [ { path: /home, name: Home, component: () import(/* webpackChunkName: home */ /views/Home.vue) }, { path: /order/list, name: OrderList, component: () import(/* webpackChunkName: order */ /views/order/List.vue) } ]第一个坑是“假懒加载”前面已经 import Home from /views/Home.vue后面再用 () import 是无效的webpack 会直接使用同步引入的结果。第二个坑是 chunk 名重复。魔法注释 webpackChunkName 如果多个路由写成同一个名字webpack 会把它们合并成一个包拆包就失去意义。我通常按业务域命名比如 home、order、user、setting而不是按页面名。第三个坑是路由分组过度。一个页面一个 chunk 在大型项目里会产生几百个请求HTTP/1.1 下反而更慢。更稳的做法是按菜单模块分组比如订单相关页面共用一个 order 包用户相关共用一个 user 包。这样既避免首屏加载全部代码又不会把请求数打爆。2.2 组件异步加载把重组件从首屏踢出去不是只有路由页面可以懒加载重组件同样可以。富文本编辑器、代码编辑器、图表组件、地图组件、视频播放器、PDF 预览器这些组件通常只在弹窗或特定页面出现完全没必要打进首屏。Vue 组件级异步加载这样写export default { components: { RichEditor: () import(/* webpackChunkName: rich-editor */ /components/RichEditor.vue), ChartPanel: () import(/* webpackChunkName: chart-panel */ /components/ChartPanel.vue) } }如果组件在弹窗里才显示还可以配合 v-if 或动态组件让代码在真正需要时才加载template div button clickshowEditor true打开编辑器/button async-rich-editor v-ifshowEditor / /div /template script export default { components: { AsyncRichEditor: () import(/* webpackChunkName: rich-editor */ /components/RichEditor.vue) }, data() { return { showEditor: false } } } /script实测下来一个项目里富文本编辑器加地图 SDK 接近 1.3MB改成异步后首页完全不受影响只有用户点击对应按钮才会出现短暂 loading。这里要注意加载失败的兜底尤其是弱网环境。异步组件加载失败会抛 ChunkLoadError最好在路由守卫或全局错误处理里加一次刷新重试router.onError((error) { if (/Loading chunk \d failed/.test(error.message)) { window.location.reload() } })2.3 prefetch 策略别让“优化”变成抢带宽Vue CLI 默认会给所有异步 chunk 加上 prefetch 链接意思是浏览器空闲时提前下载未来可能用到的包。这个策略在 PC 端和 Wi-Fi 下看起来很美但在移动端和弱网下会抢首屏带宽。你辛辛苦苦做了路由懒加载结果 prefetch 把后面十几个页面的 chunk 全排在首屏后面下载用户当前页面还没渲染完带宽已经被未来的包占用了。关掉 prefetch 很简单module.exports { chainWebpack: (config) { config.plugins.delete(prefetch) } }如果你确实想保留部分关键路由的预加载可以用 webpack 魔法注释手动指定 preload而不是全局 prefetch。我的经验是后台管理系统直接关掉 prefetch因为用户访问路径不固定提前下载命中率低面向 C 端的活动页可以保留少数下一步必用的 chunk比如下单流程的下一步页面。关掉 prefetch 后首屏竞争资源明显减少Lighthouse 的 LCP 通常会有可见改善。注意prefetch 和 preload 不是一回事。preload 是当前导航一定需要的资源优先级高prefetch 是未来可能需要的资源优先级低。但优先级低不等于不抢带宽弱网下仍会影响首屏。3. 第三方库瘦身chunk-vendors 体积的大头怎么砍3.1 UI 库按需引入Element UI/Element Plus 的落地配置UI 组件库是 chunk-vendors 最常见的膨胀源。全量引入 Element UI 大约会增加 500KB 到 700KB 的 parsed size按需引入后通常能降到 200KB 以内。Vue 2 项目用 babel-plugin-componentnpm install --save-dev babel-plugin-componentbabel.config.jsmodule.exports { presets: [vue/cli-plugin-babel/preset], plugins: [ [ component, { libraryName: element-ui, styleLibraryName: theme-chalk } ] ] }然后 main.js 不要写 import ElementUI from element-ui 和 Vue.use(ElementUI)。改成在组件里按需引入import { Button, Table, TableColumn, Dialog } from element-ui export default { components: { el-button: Button, el-table: Table, el-table-column: TableColumn, el-dialog: Dialog } }Vue 3 Element Plus 更推荐用 unplugin-vue-components 自动按需引入同时处理组件和样式const Components require(unplugin-vue-components/webpack) const { ElementPlusResolver } require(unplugin-vue-components/resolvers) module.exports { configureWebpack: { plugins: [ Components({ resolvers: [ElementPlusResolver()] }) ] } }这里有个坑ElMessage、ElLoading、ElNotification 这类服务式组件自动按需插件有时不会处理对应样式需要你手动引入样式或者单独配置。我的做法是上线前在无缓存的浏览器里把每个使用服务式组件的页面点一遍确认样式没有丢。别等到用户截图来问“弹窗怎么变丑了”才发现。3.2 用轻量库替换重库以及必须评估的兼容成本不是所有第三方库都值得留。下面这张表是我经常用来做替换决策的参考重库常见体积问题可选替代替换注意moment包含大量 locale打包后 200KB 以上dayjs、date-fnsdayjs API 接近 moment但插件要单独引入lodash 全量约 500KB parsedlodash-es、按方法引入按方法引入最稳如 lodash/debouncexlsx几百 KB 到 1MB后端导出、SheetJS 按需纯前端导出大文件容易卡主线程ECharts 全量1MB 左右echarts/core 按需注册图表类型和组件要逐个 use地图 SDK视功能而定可能超过 1MB动态加载、按需加载首屏不要同步初始化地图以 lodash 为例很多项目只用了 debounce、throttle、cloneDeep、get却写了 import _ from lodash结果整个库进 vendors。改成 import debounce from lodash/debounce体积立刻降下来。如果项目里大量使用 lodash可以改用 lodash-es 配合 tree shaking但要注意 babel 和 webpack 对 ESM 的处理。ECharts 按需引入示例import * as echarts from echarts/core import { LineChart, BarChart } from echarts/charts import { GridComponent, TooltipComponent, LegendComponent } from echarts/components import { CanvasRenderer } from echarts/renderers echarts.use([ LineChart, BarChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer ])这套写法把 ECharts 从 1MB 压到 300KB 左右具体体积取决于你注册了多少图表和组件。替换库之前一定要评估兼容成本特别是日期格式、时区、语言包、API 差异。别为了省 100KB 引入一堆隐藏 bug那就得不偿失。3.3 externals 静态资源托管的取舍externals 的思路是把某些第三方库不打包进 bundle而是在 index.html 里用 script 标签从外部加载。比如module.exports { configureWebpack: { externals: { vue: Vue, vue-router: VueRouter, axios: axios, echarts: echarts } } }index.html 里加上对应资源script src/static/vendor/vue.min.js/script script src/static/vendor/vue-router.min.js/script script src/static/vendor/axios.min.js/script script src/static/vendor/echarts.min.js/script这样做的好处是这些库不会进入 chunk-vendors可以独立缓存用户第二次访问直接命中缓存。坏处也很明显版本耦合、加载顺序依赖、外部资源不可用会导致整站白屏。我一般只把特别稳定、版本不常变、全站都用的库做 externals比如 vue、vue-router。ECharts 这种按页面用的库更推荐动态 import而不是放 index.html 全局加载。还有一点externals 之后要确保全局变量名和库的 UMD 导出一致Vue 2 是 VueVue Router 3 是 VueRouter写错一个字母就是Vue is not defined。如果你不想依赖外部公共资源可以把这些文件放到自有静态服务器或对象存储并配置长期缓存。部署时记得先上传静态资源再更新 HTML否则可能出现新 HTML 引用旧资源 404 的情况。这个顺序问题在灰度发布时尤其重要。3.4 splitChunks 精细切分把大 vendors 拆成可并行缓存的小包默认 splitChunks 只给你一个 chunk-vendors所有第三方库挤在一起。任何一个小库升级整个 vendors 的 hash 都会变用户缓存全部失效。精细切分的目标是让稳定的大库单独成包业务库经常变的不要和稳定库绑在一起。Vue CLI 项目通过 chainWebpack 配置module.exports { chainWebpack: (config) { config.optimization.splitChunks({ chunks: all, minSize: 30000, maxSize: 244000, minChunks: 1, maxInitialRequests: 6, maxAsyncRequests: 10, automaticNameDelimiter: ~, cacheGroups: { vue: { name: chunk-vue, test: /[\\/]node_modules[\\/](vue|vue-router|vuex|pinia)[\\/]/, priority: 40, reuseExistingChunk: true }, elementUI: { name: chunk-element-ui, test: /[\\/]node_modules[\\/](element-ui|element-plus)[\\/]/, priority: 30, reuseExistingChunk: true }, echarts: { name: chunk-echarts, test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/, priority: 30, reuseExistingChunk: true }, lodash: { name: chunk-lodash, test: /[\\/]node_modules[\\/](lodash|lodash-es)[\\/]/, priority: 30, reuseExistingChunk: true }, vendors: { name: chunk-vendors, test: /[\\/]node_modules[\\/]/, priority: -10, reuseExistingChunk: true } } }) } }几个参数解释一下。minSize 是生成 chunk 的最小体积默认 30KB太小的包没必要单独拆。maxSize 是 webpack 尝试把超过这个值的 chunk 再拆小我这里写 244000 是因为 HTTP/2 下并行请求便宜但 HTTP/1.1 项目不要拆太碎否则请求数会拖慢首屏。maxInitialRequests 控制入口并行请求数后台系统一般 5 到 8 比较稳。priority 是缓存组优先级数字越大越先匹配。test 里用路径分隔符正则是为了兼容 Windows 和 Unix 路径。切分之后一定要回看 analyzer 报告确认没有重复打包也确认首屏初始 chunk 总 gzip 体积确实下降了。我曾经见过一个项目拆完请求数从 3 个变成 12 个但总下载量没降首屏反而慢了 300ms这就是没有结合 HTTP 版本和真实网络做取舍的后果。4. 压缩、产物与构建配置把体积继续往下压4.1 productionSourceMap 与 gzip/brotli 双管齐下生产环境 source map 会显著增大产物体积而且默认情况下用户根本用不到。除非你有明确的线上错误监控需要映射源码否则直接关掉module.exports { productionSourceMap: false }关掉之后 dist 目录会少很多 .map 文件部署也更快。接下来是传输压缩。webpack 构建时预生成 gzip 文件服务器直接返回 .gz比每次请求动态压缩更省 CPU。安装 compression-webpack-pluginnpm install --save-dev compression-webpack-pluginvue.config.jsconst CompressionPlugin require(compression-webpack-plugin) module.exports { configureWebpack: { plugins: [ new CompressionPlugin({ filename: [path][base].gz, algorithm: gzip, test: /\.(js|css|html|svg)$/, threshold: 10240, minRatio: 0.8, deleteOriginalAssets: false }) ] } }Brotli 压缩率通常比 gzip 更高但需要服务器支持。可以同时生成 .br 和 .gz让服务器根据 Accept-Encoding 选择new CompressionPlugin({ filename: [path][base].br, algorithm: brotliCompress, test: /\.(js|css|html|svg)$/, compressionOptions: { level: 11 }, threshold: 10240, minRatio: 0.8, deleteOriginalAssets: false })Nginx 静态 gzip 配置大致如下gzip_static on; gzip on; gzip_min_length 1k; gzip_comp_level 6; gzip_types text/plain text/css application/javascript application/json image/svgxml;注意如果服务器上只有 .gz 文件但没有开启 gzip_static或者构建产物没有同步上传你会发现压缩根本没生效。上线后用浏览器 Network 面板看响应头 Content-Encoding是 gzip 还是 br 一目了然。4.2 tree shaking、babel 和 browserslist 的隐藏影响tree shaking 能不能生效取决于模块是不是 ESM。CommonJS 模块很难被静态分析所以尽量选择提供 ES module 入口的库。package.json 里的 sideEffects 字段也会影响 tree shaking如果你的项目里有纯函数工具库可以标记 sideEffects: false但不要把有副作用的样式文件也标记进去否则样式会被摇掉。Vue CLI 默认的 vue/cli-plugin-babel/preset 已经启用了 useBuiltIns: usage只会按需引入 core-js polyfill。真正影响体积的是 browserslist 配置。默认配置会兼容一批老浏览器导致更多语法降级和 polyfill。如果你的用户主要在现代浏览器可以收紧{ browserslist: [ 1%, last 2 versions, not dead, not ie 11 ] }如果确定只支持支持 ES module 的浏览器可以更激进{ browserslist: [ defaults and supports es6-module ] }收紧 browserslist 后构建产物可能减少 10% 到 25%因为 async/await、箭头函数、class 这些语法不再被大量转译成兼容代码。但这是一把双刃剑必须结合产品用户画像决定。后台管理系统通常可以放心收紧面向公众的老产品则要谨慎。我的习惯是先看埋点里的浏览器分布再决定 browserslist而不是凭感觉写。4.3 图片、字体、SVG 和静态资源的体积治理JS 和 CSS 之外图片和字体也会拖慢加载。很多项目把大图放在 src/assets 里webpack 默认会把小图转成 base64 塞进 JS阈值内看起来方便但阈值一大app.xxx.js 就会膨胀。Vue CLI 默认 url-loader 阈值是 4096 字节也就是 4KB。你可以调整module.exports { chainWebpack: (config) { config.module .rule(images) .use(url-loader) .loader(url-loader) .tap((options) { options.limit 4096 return options }) } }超过 4KB 的图片走 file-loader生成独立文件不占 JS 体积。图片本身还要压缩可以用 image-webpack-loadernpm install --save-dev image-webpack-loaderconfig.module .rule(images) .use(image-webpack-loader) .loader(image-webpack-loader) .options({ mozjpeg: { progressive: true, quality: 75 }, optipng: { enabled: true }, pngquant: { quality: [0.65, 0.9], speed: 4 }, webp: { quality: 75 } })字体文件建议做子集化尤其是中文字体全量字体动辄几 MB。SVG 如果只是图标优先用 svg-sprite-loader 做成雪碧图或者直接用 iconfont不要让每个 SVG 都单独请求。注意 base64 不是越大越好它会让 JS 文件变大还会阻塞解析小图标可以内联大图必须独立文件。4.4 构建缓存与文件名哈希别让用户重复下载Vue CLI 默认 filenameHashing 是开启的生产文件名带 contenthash内容不变 hash 不变用户可以长期缓存。但如果你用了 externals 或者手动 copy 静态文件要确保这些文件也有稳定哈希或长期缓存策略。splitChunks 的 cacheGroups 名字也要稳定不要每次构建都变。部署时建议先上传带 hash 的新静态资源再更新 index.html这样用户不会遇到“新 HTML 引用了还没上传的资源”导致白屏。如果你的项目有灰度发布注意新旧 HTML 可能同时存在externals 的全局变量版本要向后兼容否则老 HTML 加载新资源会报错。我一般会在 CI 里加一步检查 dist 里是否存在超过 300KB gzip 的 JS 文件超过就报警防止某次引入一个大库悄悄把体积拉回去。5. 验证与排查优化后怎么确认真的有效5.1 本地分析和线上指标怎么配合看优化不能只看构建日志里的 “build finished”要看真实指标。本地验证时我会用 Chrome DevTools 的 Network 面板勾选 Disable cache选 Fast 3G 或 Slow 4G勾选 Performance 录制首屏。重点看 FCP、LCP、TTI以及总下载字节数。如果首屏 JS 总 gzip 能控制在 300KB 到 500KB 之间后台系统通常体验就不错了。更严格一点初始路由的 JS 最好不超过 200KB gzipvendors 拆出来的公共包不超过 300KB gzip。ECharts、xlsx、地图、富文本这些必须异步不能出现在初始请求里。线上验证要靠 RUM 或性能监控平台看真实用户的 LCP 分位数、首屏 JS 加载耗时、资源错误率。很多时候本地优化得很好线上却被 CDN 缓存、服务器压缩没开、HTTP/1.1 请求排队拖累。我的做法是每次上线后对比优化前后的 LCP 75 分位和首屏传输体积如果 LCP 没降就回 Network 面板看是不是某个大 chunk 仍然被首屏加载了。还有一个容易忽略的点第三方统计、客服、地图、广告脚本这些不在你的 webpack 产物里但会严重拖慢首屏。它们的加载策略同样要改成异步或延迟。5.2 常见问题速查表现象常见原因解决方向路由懒加载后刷新页面 404服务器未配置 history fallbackNginx 配置 try_files $uri $uri/ /index.html点击才加载的组件报 ChunkLoadError部署后旧 HTML 引用旧 chunk或网络失败全局捕获错误后刷新一次部署保证资源先上传按需引入后样式丢失服务式组件样式未自动引入手动引入对应 CSS 或配置 resolverexternals 后报 Vue is not definedscript 顺序错误或全局变量名不对确保 vue 先于项目脚本加载变量名匹配 UMDgzip 配置后体积没变化服务器未开启 gzip_static或上传的是源文件检查响应头 Content-Encoding确认 .gz 已上传拆包后请求数暴增首屏更慢maxSize 太小HTTP/1.1 请求排队增大 minSize减少初始请求数prefetch 导致首屏带宽被抢Vue CLI 默认预取所有异步 chunk删除 prefetch 插件按需 preload修改库版本后缓存全部失效所有依赖打在一个 vendors 里用 cacheGroups 把稳定大库单独拆包5.3 我的避坑经验先分析再动手一次只改一类我踩过最大的坑是在没有分析报告的情况下同时改了路由懒加载、按需引入、splitChunks 和 externals结果首屏确实从 4.5 秒降到 3 秒但页面开始随机报错查了两天才发现是 externals 的 Vue 版本和项目内使用的 API 不匹配。从那以后我给自己定了规矩每次只改一类优化改完跑一遍构建看 analyzer再用无痕窗口走一遍核心流程。优化不是配置写得越多越好而是每一步都能解释为什么。比如你加 maxSize: 244000要想清楚项目是 HTTP/1.1 还是 HTTP/2你关掉 prefetch要想清楚用户下一步大概率去哪你做 externals要想清楚资源可用性和版本管理谁负责。还有一个小技巧在 package.json 里加一个体积检查脚本构建后扫描 dist 里的 JS gzip 体积超过阈值就让 CI 失败。这样后来的人再想随手 import 整个 ECharts 或全量 lodash就会在合并请求阶段被拦住。我现在的习惯是每次上线前跑一次 --report把 gzip 后大于 200KB 的 chunk 列出来逐个问它首屏到底用不用。只要这个习惯保持住app.xxx.js 和 chunk-vendors.xxx.js 基本不会再失控。
返回列表