把首屏 JS 从 1.2MB 压到 180KB:构建产物体积优化的 6 个实操点

举报
云上小栈 发表于 2026/11/08 10:41:50 2026/11/08
【摘要】 先量化,再动手体积优化最容易犯的错是凭感觉改配置。先用工具把产物拆开看清楚,再决定改什么。# 安装分析插件 npm i -D rollup-plugin-visualizer # 在 vite.config.ts 中启用 import { visualizer } from 'rollup-plugin-visualizer' export default { plugins: [vi...

先量化,再动手

体积优化最容易犯的错是凭感觉改配置。先用工具把产物拆开看清楚,再决定改什么。

# 安装分析插件
npm i -D rollup-plugin-visualizer

# 在 vite.config.ts 中启用
import { visualizer } from 'rollup-plugin-visualizer'
export default {
  plugins: [visualizer({ gzipSize: true, brotliSize: true, filename: 'stats.html' })]
}

# 构建后打开 stats.html,按 brotli 体积排序

同时统计每次构建的增量,避免优化成果被后续提交吃掉:

npx vite build --mode production
# 记录 dist 下每个 chunk 的 brotli 大小,写进 CI 的对比基线

一个真实项目的首屏 JS 是 1.2MB(brotli),分析后发现 3 个占比最大的依赖:moment(含全部 locale,232KB)、lodash 全量引入(96KB)、一个内部 UI 库把整个组件库打进了入口 chunk(410KB)。

依赖替换与按需引入

体积问题大部分出在依赖,不在业务代码。

  • moment 换成 dayjs,并把 locale 显式引入:import 'dayjs/locale/zh-cn',体积从 232KB 降到 6KB。
  • lodash 改为具名路径引入 import debounce from 'lodash/debounce',或直接用 lodash-es 配合 tree-shaking。
  • UI 库按需引入,antd 5 默认支持 tree-shaking,但仍要确认没有 import { Button } from 'antd' 之外的整包引入。

用一条命令检查是否混入了重复版本:

npm ls lodash react react-dom
# 出现多个版本说明有依赖没对齐,用 overrides 统一
# package.json
"overrides": { "lodash": "4.17.21" }

代码分割与 chunk 策略

默认的自动分割在大项目里经常切得太碎或太粗。手动配置 vendor chunk,让公共依赖的缓存命中率更高:

build: {
  rollupOptions: {
    output: {
      manualChunks(id) {
        if (id.includes('node_modules')) {
          if (id.includes('react') || id.includes('scheduler')) return 'react-vendor'
          if (id.includes('echarts')) return 'charts'
          return 'vendor'
        }
      }
    }
  }
}

路由级别用动态 import 懒加载,把非首屏页面全部拆出去。配合 /* @vite-ignore */ 之外,注意动态 import 的路径不要拼接变量,否则无法静态分析,会退化成一个包含所有可能模块的 chunk。

踩坑一:打包后 IE 兼容插件把体积翻倍

项目引入了 @vitejs/plugin-legacy 支持旧浏览器,构建产物里多出一份完整 polyfill 包(约 300KB)和一份 legacy chunk。实际上只覆盖 99.5% 以上的现代浏览器即可,旧的 legacy 包白白增加部署与缓存成本。

解决办法:

legacy({
  targets: ['defaults', 'not IE 11'],
  renderLegacyChunks: false,   // 不再生成完整 legacy chunk
  modernPolyfills: true
})

同时用 build.target: 'es2015' 让现代产物只需少量 polyfill。调整后首屏下降约 260KB。

踩坑二:tree-shaking 没生效,因为副作用标记

把内部工具库改成 ESM 后体积几乎没降。原因是该包的 package.json 没有声明 "sideEffects": false,打包器保守地保留了所有模块。在工具库中补上:

{
  "main": "dist/index.cjs",
  "module": "dist/index.esm.js",
  "sideEffects": false
}

另一个隐蔽原因是 import * as utils from './utils' 后只用了其中一个方法,命名空间导入会阻止摇树。改成具名导入即可。console.log 也要清理,用 esbuild 的 drop:

esbuild: { drop: ['console', 'debugger'] }

压缩与产物细节

开启 brotli 预压缩,服务端直接下发压缩文件,避免运行时压缩开销:

npm i -D vite-plugin-compression
compression({ algorithm: 'brotliCompress', threshold: 10240, ext: '.br' })

CSS 也要关注:cssCodeSplit: true 按路由拆分样式,配合 PurgeCSS 或 UnoCSS 清除未使用类名。图片走 vite-plugin-imagemin,优先转 WebP。

小结

先量化再优化,重点在依赖替换、按需引入、手动 chunk 和副作用标记。三个改动叠加后,该项目首屏 JS 从 1.2MB 降到 180KB(brotli),首屏加载时间从 3.4s 降到 1.1s。把体积检查写入 CI 阈值(例如入口 chunk 超过 250KB 即失败),是防止体积回流最有效的手段。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。