微信小程序包体积优化:2MB 到 400KB 的实操记录

6 阅读7分钟

微信小程序包体积优化:2MB 到 400KB 的实操记录

小程序开发有个绕不开的坎:主包体积限制 2MB。

平时开发到一半没感觉,等真机预览、提审的时候突然冒出来"包体积超限",然后开始一轮手忙脚乱的删东西——删图片、删组件、删字体,删到最后功能都不全了。

这篇记录一次真实的瘦身过程,不是理论清单,是每一步改了什么、省了多少、踩了什么坑。

一、先搞清楚:2MB 到底限制的是什么

这是最容易搞错的点。

限制的是「主包 + 分包总和」以外的层级,具体来说:

  • 主包限制 2MB
  • 整个小程序包(含所有分包)限制 20MB
  • 单个分包限制 2MB

所以严格说,不是"整个包不能超 2MB",而是主包里的东西不能超 2MB。

这个区别很关键,因为很多人一看到"超了"就把所有东西塞进分包,结果分包也超。

先明确目标:主包只放"启动即需要"的东西。

二、第一刀:干掉主包里的静态资源

先说结论,这一步通常能省 300KB - 800KB,是性价比最高的。

检查 iconfont 有多臃肿

小程序的 iconfont 是最容易被忽略的体积杀手。

一个正常项目里,iconfont 文件可能包含几百个图标——但实际用到的可能只有二三十个。

排查方式:找到项目里的 .ttf / .woff 文件,直接看文件大小。如果超过 100KB,基本就是图标全量打包了。

解法:用 iconfont 的 Unicode 引用方式,生成的是字体文件 + CSS,字体文件只包含你订阅的图标。

或者更直接:用图片图标或者 SVG。如果你项目里图标数量不多(20 个以内),直接换成 SVG 内联反而更省,而且不受字体加载延迟影响。

检查图片有没有在主包里

项目里肯定有一堆图片,散落在各个目录里。只要被某个主包页面 import 或者写在样式里,它就进主包。

解法:所有图片资源都放 CDN 或者用云存储,主包里不存图片。

判断标准很简单:打开构建产物目录,看 assets 里有什么。这个目录里的东西就是主包资源。

我们项目当时的 assets 里有:

内容体积处理
iconfont 字体210KB改成 SVG 内联,删字体
占位图45KB移到 CDN
启动图38KB移到 CDN
未使用的 tabBar 图标12KB删掉两个变体

合计省掉 305KB。

tabBar 图标的坑

tabBar 必须用本地图片(不能用网络图片),且所有 tab 页的图标都会进主包。

我们之前有 5 个 tab,每个 tab 两套图标(选中/未选中),一共 10 张图。有 2 个 tab 的图标其实长得一样,根本不需要两套。

解法:合并视觉上相同的图标,能用一张就不做两套。

如果 tab 超过 5 个,考虑改成"自定义 tabBar",可以把图标放到分包里。

三、第二刀:分包策略

处理完静态资源,如果还是超,就该分包了。

怎么划分分包

原则:按业务模块划分,不要按页面数量硬切。

我们当时的划分:

主包:首页、登录、我的、以及所有 tab 页
├── 分包 A:订单模块(订单列表、详情、评价)
├── 分包 B:商品模块(商品详情、规格选择、下单)
└── 分包 C:我的扩展(设置、地址簿、消息)

判断标准很简单:这个页面的功能,主流程会用吗?

  • 会的(比如首页)→ 主包
  • 不会的(比如订单历史)→ 分包

用户第一屏根本不会点到的页面,全在主包里就是浪费启动速度。

分包的两个硬限制

1. 分包不能引用主包外的资源

这个经常踩坑。分包页面里 import 主包里的组件是不允许的。

// ❌ 分包页面引用了主包外的组件
import { CustomButton } from '../../components/button'

// ✅ 正确做法:把公共组件放主包,或者放到每个分包里

我们的做法是:高频公共组件(Button、Empty、Loading)放主包,低频业务组件放对应分包。

判断依据:这个组件有几个页面在用?只有 2-3 个页面用又都在同一个分包内,就放分包里。

2. 分包首次进入有加载延迟

分包不是免费的——用户第一次点进那个 tab,会等几百毫秒加载分包。

所以分包不要切太细。我们试过切 6 个包,结果每个包都很小,切换时的等待反而更明显。

最终定在 3 个分包,体验和体积平衡得比较好。

异步分包

如果你有某个特别大的业务模块(比如直播、视频),可以用异步分包:

// 只在真正用到时才加载
const live = defineAsyncComponent(() => import('./live/index'))

配合分包配置里的 "lazyCodeLoading": "requiredComponents",能让启动更快。

但注意:异步分包用到的组件,不能在主包里被同步引用,否则会被打进主包,异步就白做了。

四、第三刀:干掉那些"顺手加的"东西

这一步没有技术含量,但效果出奇地好。

未使用的依赖

# 看依赖树,找出体积大的
npm ls --all --depth=10

我们当时发现一个没用到的日期处理库,带了一整套时区数据,78KB。

解法:定期检查依赖,别看到顺手就 npm install。项目跑了一年,依赖树里全是历史沉淀。

用 bundle-analyzer 类工具扫一下,找出没被引用的包。

polyfill

小程序端不需要那么多 polyfill。如果你的项目用了 core-js,检查下是不是全量引入的。

解法:按需引入,别全量打包。

sourcemap 和调试文件

这些应该只在本地保留,不要进构建产物。

// project.config.json
{
  "setting": {
    "packNpmManually": false,
    "minifyWXSS": true,    // 开启压缩
    "minifyJS": true,      // 开启压缩
    "disableUseStrict": true
  }
}

五、优化后的实际数据

贴一下这次的完整对照:

阶段主包体积主要动作
优化前1.87MB—
清静态资源后1.56MBiconfont 转 SVG、图片移 CDN
分包后890KB划分 3 个分包,公共组件重新分配
清理依赖后745KB删无用依赖、开启压缩
微调后412KBtabBar 图标合并、异步分包

从超限到 412KB,主包降到原来的 22%。

六、几个容易忽略的细节

1. 主包里的图片会被 base64 内联

小体积图片(< 8KB)在构建时会被转成 base64 塞进 CSS/CSSJS 里。

这意味着一个 5KB 的图标,实际可能让 CSS 涨了 7KB(base64 体积比原图大 33%)。

如果你的 CSS 莫名其妙很大,先去 dist 里搜一下有没有 data:image。

2. 自定义组件的分包归属

uni-app / Taro 这类框架,自定义组件在哪个分包,取决于它被谁引用。

如果主包引用了某个组件,那个组件就永远在主包,哪怕它只服务分包页面。

这一点很反直觉,但很关键——想彻底瘦身,要从主包的依赖链开始砍。

3. 别为了体积牺牲可维护性

我们删掉过一些"暂时没用但预留了"的代码,后来又要加回来,来回折腾。

建议:只删确定不用的,标了 TODO 或注释的先留着。几百 KB 换来的维护成本不划算。

七、写在最后

包体积优化的核心思路其实就一句:主包只放启动必需的东西。

围绕这个原则,具体做法可以很多——移资源、删依赖、合并图标、分包、按需加载。

但更重要的是:改完要真的量一遍。

微信开发者工具里有"代码质量分析"或者上传时会给体积报告,每次提交前看一眼,别等提审被拒了才回头找原因。

最后提一句,我们做完这次优化还有个附带好处:启动速度明显变快了。因为主包小了,首屏加载的资源少了。这个是意外收获——本来只是想解决超限,结果顺手优化了体验。

如果你的项目也卡在 2MB 上,可以按这个顺序试试:先查静态资源 → 再查依赖 → 最后才考虑分包。大部分项目前两步就能解决大半。