点「上传」,开发者工具弹出一行红字:主包大小超过 2MB 限制。版本发不出去,需求还在排队。
这篇文章不讲原理大全,只按「省得多、改得少」的顺序,列出能把主包压回 2MB 以内的办法。
先搞清楚:2MB 到底卡的是什么
微信对小程序代码包有两条硬上限:
| 限制 | 上限 |
|---|---|
| 主包(含 app.js、tabBar 页面、公共资源等) | 2MB |
| 单个分包 | 2MB |
| 整个小程序所有包加起来 | 20MB |
注意这里的「大小」是上传后的代码包体积,不只是 JS。项目目录里所有会被打包进去的文件都算:.js、.wxml、.wxss、.json、字体、音频,以及——通常占大头的——图片。
这是一条「压不下去就发不出版本」的刚性限制,不是「优化一下更好」。所以第一步不是动手,而是先看清钱花在哪了。
第 0 步:看清主包里是什么在占地方
微信开发者工具右上角「详情 → 基本信息」能看到本地代码包的总大小;更细的在工具栏的 「代码依赖分析」,能按文件列出体积,并标出哪些文件没有被任何页面引用。
大多数项目看完这张表都会发现同一件事:图片、字体这类静态资源比业务代码大得多。JS 写几万行也就几百 KB,一张没处理过的 banner 图就可能上百 KB,一个 images/ 目录轻松吃掉主包一半。
下面按收益从大到小排。
办法一:删掉根本没用的文件
最省事,也最常被忽略。
- 「代码依赖分析」里标记为未引用的文件:历史改版留下的旧图标、废弃页面、测试图,直接删。
- 不该进包的文件:设计稿、README、
.psd、原始素材、Mock 数据。能挪出项目目录就挪出去;挪不了的,在project.config.json里用packOptions.ignore排除:
json
{
"packOptions": {
"ignore": [
{ "type": "folder", "value": "design" },
{ "type": "suffix", "value": ".psd" }
]
}
}
- 组件库全量引入:只用了三个组件却把整个 UI 库打进主包,是常见的「隐形胖子」。改成按需引入,只保留用到的组件目录。
办法二:分包——把不是首屏的页面挪出主包
这是官方给的正解。主包只留启动页、tabBar 页面和它们真正需要的公共代码,其余页面按业务拆进分包:
json
{
"pages": ["pages/index/index", "pages/mine/mine"],
"subPackages": [
{ "root": "packageOrder", "pages": ["list/list", "detail/detail"] },
{ "root": "packageActivity", "pages": ["index/index"] }
],
"preloadRule": {
"pages/index/index": { "network": "all", "packages": ["packageOrder"] }
}
}
几个要点:
- 资源跟着页面走:分包页面自己用的图片、组件,要放进分包目录。放在主包的公共
images/里,分包再怎么拆,体积还是算在主包头上。 preloadRule分包预下载:进首页时顺手把下一步大概率会用到的分包拉下来,用户点进去时基本感觉不到延迟。- 独立分包(
"independent": true):适合活动页、落地页这类可以脱离主包单独打开的页面,启动时不依赖主包。 - 分包异步化:跨分包引用组件或 JS 时,可以用占位组件和
require.async,避免为了「共用」把代码又提回主包。
分包的代价是要改目录结构和跳转路径。老项目拆起来工作量不小,这也是为什么值得先把下面这一步做了——很多时候,做完它主包就已经回到 2MB 以内了。
办法三:把图片压小(改动最小、收益最直接)
图片是主包里最容易「白白胖着」的部分。设计师导出的 PNG 带着多余的数据块,JPG 用的是偏高的质量参数,这些字节用户看不出来,但每一个都计入 2MB。
哪些图必须留在包里
不是所有图都能挪到 CDN:
- tabBar 图标:
iconPath/selectedIconPath只能是本地路径,不支持网络图片。 - 启动页、首屏 Logo、兜底占位图:弱网或离线时也得能显示。
- 频繁出现的小图标:每次都走网络请求并不划算。
这些图只能留在包里,所以唯一的办法就是让它们变小。
顺带避开一个坑:wxss 里的背景图
在 .wxss 里用 background-image 引用本地图片,真机上是不生效的,常见的绕法是转成 base64 内联。但 base64 编码会让体积膨胀约三分之一,而且它藏在样式文件里,依赖分析里不太显眼。能改成 <image> 组件或网络图片的,就别内联;非内联不可的,先把原图压小再转。
用 ImgZilla 原地压缩整个资源目录
做小程序最怕的,是压完图还要回头改路径——wxml 里、wxss 里、JS 配置里、tabBar 配置里,到处是 /images/xxx.png。很多压缩工具会另存为 xxx-min.png,或者要求你导出到别的文件夹,压完还得手动替换、再核对一遍有没有漏。
ImgZilla 是一款 macOS 图片压缩工具,它的做法是原地压缩:
- 文件名、路径、格式都不变。
icon-home.png压完还是icon-home.png,代码里一行引用都不用改。 - 整个目录拖进去即可。它会递归扫描所有子目录,并自动跳过隐藏文件和
node_modules。把images/、static/甚至整个项目目录拖进去,压完直接在开发者工具里看包体积变化。 - 原图默认移入废纸篓,觉得哪张不满意,右键「放回原处」即可还原。项目在 Git 里的话,还多一层保险。
- 全程本地运行,不联网上传。公司项目的素材不会离开你的电脑,也没有在线压缩站那种单次张数、单张大小的限制。
画质方面,不同格式的处理方式不一样,这里说清楚:
- PNG:使用 oxipng 做无损压缩,像素一点不变。小程序里大量的图标、切图都是 PNG,这部分可以放心压。
- SVG:清理冗余标记,也是无损的。
- JPG / WebP / GIF 等:属于有损重编码,参数已经针对每种格式调校在视觉无损区间——肉眼难以察觉差异。不必凭信任接受,内置对比窗口(
⌘D)可以左右分屏、放大到实际像素逐张对照。
另外两点也值得知道:
- 已经压到头的图它不会硬压:压缩率不足 0.4% 的文件会标为「已最小」,原样保留,不会为了数字好看把图搞糊。所以如果你的图之前已经被认真优化过,这一步能省的可能不多——这很正常,也说明该转向分包了。
- 它不转格式、不改分辨率。PNG 压完还是 PNG,尺寸不变。如果你想把图换成 WebP,或者把 3 倍图降成 2 倍图,那是另一件事,需要另外处理。
能省多少,取决于图原来是怎么导出的,我们不给一个笼统的百分比。作为参考,我们做过两次公开实测:一批网站入库时已经压过一遍的 JPG,再压一次仍然省了 46.8%;一批相机直出的 JPG 省了 76.8%(详见《ImgZilla 实测》系列)。小程序里的图往往是设计工具直接导出的,通常没被认真压过,所以值得先跑一遍看看。
ImgZilla 目前只有 macOS 版(macOS 12.3 及以上),Mac App Store 可下载,每天可免费压缩 10 张。用 Windows 开发的同学,这一节的思路同样适用,换一个同样能「保留原文件名」的工具即可。
办法四:把大图挪到 CDN
压小之后仍然很大的图——比如活动 banner、详情页长图、商品大图——本来就不适合放在包里。上传到对象存储或 CDN,代码里换成网络地址,主包立刻轻一截。
但这不是免费的:
- 首次加载要走网络,弱网下会有空白期,最好配合占位图或骨架屏。
- 要维护一套上传、缓存、更新的流程。
- CDN 按流量计费。图片每被加载一次都在花钱,流量费约等于「图片体积 × 访问次数」。所以挪到 CDN 之前,同样值得先把图压小——压一次,之后每一次访问都在省流量。
办法五:代码层面的收尾
图和结构处理完,剩下的零碎空间可以从代码里抠:
- 开发者工具「详情 → 本地设置」里勾选上传时压缩脚本、样式、WXML。
app.json开启"lazyCodeLoading": "requiredComponents"(按需注入)。它主要改善的是启动速度而不是包体积,但既然在做性能优化,一起开上。- 检查
miniprogram_npm:构建出的 npm 包是否有整包引入却只用了一两个函数的情况。 - 大段写死在 JS 里的静态数据(城市列表、配置表)考虑改为接口下发。
一张表总结
| 办法 | 能省多少 | 要改多少 | 建议顺序 |
|---|---|---|---|
| 删除无用文件 | 看项目历史包袱 | 很少 | 1 |
| 原地压缩图片 | 图片越多、导出越随意,省得越多 | 几乎为零,路径不变 | 2 |
| 分包 | 可以很多 | 目录和路由都要动 | 3 |
| 大图挪 CDN | 很多 | 要改引用、维护上传流程 | 4 |
| 代码压缩与按需引入 | 较少 | 视情况 | 5 |
顺序的逻辑很简单:先做改动小的,再做改动大的。删文件和压图片几乎不碰业务代码,做完再看主包还差多少——如果已经回到 2MB 以内,今天的版本就能发;如果还不够,再动分包和 CDN,心里也有数了。
主包超限往往发生在上线前最紧张的时候。把图片压缩放进日常流程——每次加新图之前先压一遍——下次就不用在截止日前对着那行红字发愁了。
