uni-app 小程序主包从 2.3M 降到 1.1M:一次真实的分包瘦身记录

2026-09-12 0 599

上周四准备发版,预览码生成到一半微信弹出提示,主包体积 2357KB 超过 2MB 限制,错误码 80051。当时有点懵,因为上一个版本还是 1.9M,中间只加了一个订单详情页和几个图标,怎么会一下多出 400 多 KB。翻了下提交记录,把一个 UI 组件库整包引入的改动,确实是那次合进去的。

这篇文章就是把后面折腾的两天记下来,包括怎么定位、怎么拆、拆完之后又踩了哪些坑。如果你手上的小程序也快顶到 2M 了,照着做一遍基本能腾出不少空间。

第一步:先搞清楚主包里到底装了什么

很多人一上来就想着怎么分包,其实应该先做体积分析。微信开发者工具自带的”代码依赖分析”就够用,入口在”详情 → 本地设置 → 代码依赖分析”,点一下会生成一张依赖树。

我们那次分析出来的结果是:

  • 主包 JS 代码 1.1M,其中第三方库占了 780KB
  • 静态资源 890KB,主要是 static 目录下的图片和图标
  • 其他(配置、样式)约 360KB

看到这个数字其实心里就有数了。第三方库是大头,静态资源第二,业务代码反而只占两三成。所以后面所有动作都是围绕这两块做。

顺便说一句,如果你用的是 uni-app 的 vue3 + vite 版本,还可以在 package.json 的构建脚本里加上 --report,会在 dist 目录生成一个体积报告页面,粒度比微信的更细,能看到每个模块的压缩前后大小。

第三方库:按需引入而不是整包

那次真正的元凶是一个 UI 库。我们原来在 main.js 里是这么写的:

import uView from 'uview-plus'
import { createSSRApp } from 'vue'

export function createApp() {
  const app = createSSRApp(App)
  app.use(uView)
  return { app }
}

app.use(uView) 这一句会把整个库注册到全局,无论你用不用到里面的组件,打包的时候都会被算进去。我们那个版本实际上只用了六七个组件,却把整个库背了进来。

改造成按需引入,有两种做法。

一种是配合 easycom 规则。uni-app 的 easycom 会自动扫描组件路径,哪个页面用了哪个组件才编译进对应分包。配置文件写在 pages.json 里:

{
  "easycom": {
    "autoscan": true,
    "custom": {
      "^u-(.*)": "uview-plus/components/u-$1/u-$1.vue"
    }
  }
}

然后在页面里写 <u-button> 就能直接用,不需要 import。关键是 autoscan 这一层只对用到的组件生效,没用到的不会被打包。

另一种是在具体页面里显式 import:

<script setup>
import { UButton, UInput } from 'uview-plus'
</script>

这种写法更直观,缺点是每个页面都要写一遍。我们最后两个方案混用了,公共页面用 easycom,只在一处出现的地方就单独 import。

改完之后第三方库从 780KB 降到了 320KB,这一刀砍掉了大概 460KB,主包立刻退到了 1.9M 以内。不过这只是回到了原来水平,还是要继续往下降。

静态资源:能挪走的都挪走

主包里 890KB 的静态资源,仔细一看基本全是 static 目录下的图。这里面其实混了两类东西。

一类是图标,几十个小 PNG,加起来 200 多 KB。这类东西最适合用字体图标或者 SVG 替代。我们换成了 iconfont 的一个子集,只保留项目里用到的 40 个图标,体积降到了 12KB。

另一类是页面插图,比如活动页的背景图、详情页的占位图,一张就 100 多 KB。这类图片有两个处理方式:

一是丢到 CDN,代码里直接引用 https 链接。微信小程序对 CDN 图片有白名单要求,需要在 project.config.jsonurlCheck 关掉或者在后台配置合法域名。

二是如果图片小于 4KB,可以考虑转 base64 内联。但要注意 base64 会让体积膨胀约 33%,太小的图片(比如 loading 动图)不适合单独发一个请求,转 base64 反而合算。

我们最后是这么处理的:插图和背景图全部走 CDN,图标换成字体,只留下一些兜底用的占位图在 static 里。主包资源部分从 890KB 降到了 180KB。

分包:该切出去的业务页面

资源和第三方库这两块理清楚之后,主包已经降到 1.4M 左右,但还不够安全。接下来要把一些业务页面切到分包里。

分包设计的第一步是想清楚哪些放主包。经验是:tabBar 页面必须放主包,这是微信的硬性限制;启动页和登录页也必须放主包,因为这是用户进入小程序的第一跳。其余的都可以放。

我们的小程序有四个 tab:首页、分类、购物车、我的。这四个必须留在主包。剩下的订单模块、设置模块、活动模块全部切到分包。

pages.json 改成这样:

{
  "pages": [
    { "path": "pages/index/index", "style": { "navigationBarTitleText": "首页" } },
    { "path": "pages/category/category", "style": { "navigationBarTitleText": "分类" } },
    { "path": "pages/cart/cart", "style": { "navigationBarTitleText": "购物车" } },
    { "path": "pages/user/user", "style": { "navigationBarTitleText": "我的" } }
  ],
  "subPackages": [
    {
      "root": "subpkg-order",
      "pages": [
        { "path": "list/list", "style": { "navigationBarTitleText": "订单列表" } },
        { "path": "detail/detail", "style": { "navigationBarTitleText": "订单详情" } },
        { "path": "after-sale/after-sale", "style": { "navigationBarTitleText": "售后" } }
      ]
    },
    {
      "root": "subpkg-settings",
      "pages": [
        { "path": "profile/profile" },
        { "path": "address/address" },
        { "path": "about/about" }
      ]
    }
  ]
}

分包目录的命名建议用 subpkg- 前缀,一眼能看出来这是分包,不然页面多了很容易搞混。

跳转写法跟主包没区别,uni-app 会用完整路径解析:

uni.navigateTo({
  url: '/subpkg-order/list/list?status=unpaid'
})

有一个坑要注意:分包间不能相互引用组件和 JS。如果 subpkg-ordersubpkg-settings 都要用同一个组件,这个组件只能放在主包,或者每个分包各复制一份。前者会让主包变大,后者会让代码重复。我们最后是把这个公共组件做得尽量小,放在主包,两边的引用都不影响。

独立分包:首页里的活动入口

有一种情况值得单独拿出来说:首页上有一个固定入口跳到活动页,但活动页本身就很大,经常单独更新。这种用独立分包最划算。

独立分包的特点是它可以不依赖主包直接启动,比如用户从朋友圈分享的活动页二维码扫码进来,微信会直接加载这个独立分包,跳过主包。这对于营销活动页来说体验提升很明显。

配置就是在 subPackages 的某一项里加 "independent": true

{
  "root": "subpkg-activity",
  "independent": true,
  "pages": [
    { "path": "index/index" }
  ]
}

但是独立分包有个很麻烦的限制:它不能用主包的 app.wxss 全局样式,也不能用主包里注册的全局组件和 store。所以独立分包的页面基本要自给自足,所有样式和依赖都得自己带进来。

我们最后只在那个活动页上用了独立分包,并且给它的样式单独写了一份精简版的全局声明。面积上它确实省了不少,但增加了一点维护成本,所以没有大规模铺开用。

预下载:让分包在需要之前就准备好

分包切出去之后会有一个新问题:用户第一次进订单页的时候会有几百毫秒的空白,因为分包正在下载。这体验不好。

微信提供了预下载机制,你可以在主包某个页面加载完成后,主动去把这个分包提前下载下来。配置写在 pages.json 里:

"preloadRule": {
  "pages/index/index": {
    "network": "all",
    "packages": ["subpkg-order"]
  },
  "pages/user/user": {
    "network": "wifi",
    "packages": ["subpkg-settings"]
  }
}

意思是:进入首页后,不限网络类型,提前把 subpkg-order 下载好;进入”我的”页面后,如果在 WiFi 下,把 subpkg-settings 下载好。

有几个点得注意。预下载不是立刻执行,而是进入页面后的空闲时机才触发,所以你没法靠它在启动阶段就把包准备好。同一个分包只能被一个页面预下载,如果两个页面都写了同一个分包,后面那个配置会被忽略。

另外预下载本身会消耗流量,虽然是异步的,但在弱网环境下还是可能影响当前请求。所以我们把订单模块的预下载放在 WiFi 环境下才触发,只在 4G 下让用户接受第一次进入时的下载等待——订单页的用户通常是有明确目的进来的,几百毫秒的空白可以接受。

主包里的”隐形负担”

上面几步做完,我们已经降到 1.15M 了。这时候继续分析,发现主包里还有一些常被忽略的东西。

moment.js。这个库本身 200 多 KB,我们只用了格式化一个功能。换成 dayjs 之后立刻降到 8KB,而且是同一个风格,迁移成本接近零。

lodash。整包引入是 500 多 KB,但我们实际只用了 debouncethrottlecloneDeep 三个方法。改成单独 import 具体方法就行:

import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
import cloneDeep from 'lodash/cloneDeep'

如果不想用 lodash,也可以自己写这几行,debounce 和 throttle 都不复杂,cloneDeep 用 structuredClone 就能替代,浏览器和小程序环境都支持。

多余的 polyfill。uni-app 有时候会为了一些老环境塞进去一堆 polyfill。在 manifest.json 里可以把不需要的目标平台关掉。我们只发微信和支付宝小程序,就把 H5、App 相关的构建分支去掉了,主包又少了大概 60KB。

跳转路径的坑

分包改造过程中,最容易被忽略的是路径。分完包之后,原来所有页面路径都会变,如果项目里有硬编码的字符串,就很容易漏掉。

我们做法是把所有路由集中到一个 routes.js 里:

export const ROUTES = {
  HOME: '/pages/index/index',
  ORDER_LIST: '/subpkg-order/list/list',
  ORDER_DETAIL: '/subpkg-order/detail/detail'
}

export function goOrderList(status) {
  uni.navigateTo({
    url: `${ROUTES.ORDER_LIST}?status=${status}`
  })
}

这样路径只在一个文件里维护,下次再调整分包的时候,改一处就行。

还有几个地方特别容易出问题:

分享卡片的 path。分享给好友的链接里带的是具体路径,分完包之后必须同步更新,否则用户点进去就是 404。

二维码/小程序码里的 path。如果二维码是后端生成的,那后端也得跟着改。我们那次差点漏掉了一处,是测试环境扫码才发现。

wx.navigateBack 的 delta。分包页面栈的返回逻辑和主包没什么区别,但如果中间跳过了独立分包,返回栈的层数可能会跟你预期的不一样,最好手动数一下。

最终效果和几个事后想法

发布之后的数字:主包从 2357KB 降到 1113KB,减少了约 53%。加上分包之后小程序总体积是 3.4M,没有超限。冷启动时间从 1.8 秒降到了 1.2 秒左右,主要是主包少了差不多一半资源需要解析。

事后复盘的几条经验,给还在挣扎的同行参考。

第一,体积优化是持续的事,不是版本发版前临时救火的。我们的教训是上游开发经常随手引一个大库,PR 评审的时候没人看体积报告。后来加了一条 CI 规则,主包超过 1.5M 直接构建失败,比事后救火省事得多。

第二,分包不是越细越好。一开始我们分了五个分包,结果发现两个小组件被复制了四次,维护起来特别难受。后来合并成两个,代码复用反而更好。分包的核心目的是减少主包,不是减少代码重复,这两个目标有时候是对立的,得取舍。

第三,真的要上独立分包之前,先做个试验页面测一下。独立分包对所有公共资源的限制比较大,有些组件搬进去就报找不到样式,前期投入的时间和实际收益要评估清楚。

第四,微信开发者工具的体积分析只是一个参考,上传的时候实际压缩效果可能不一样。我们本地分析显示 1.13M,上传之后微信后台报的是 1.10M,因为上传时会再做一次 gzip。但这个差异是往好的方向的,不用担心。

如果项目还没开始分包,建议先把 tabBar 页面以外的页面尽量往后放,一上来就把分包结构设计好,比后面再拆要轻松很多。尤其是那些大页面(详情页、活动页),虽然只有一两个,但占体积很凶,早点切出去省心。

uni-app 小程序主包从 2.3M 降到 1.1M:一次真实的分包瘦身记录
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请发送邮件1506151422@qq.com联系,将会及时下架删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp uni-app 小程序主包从 2.3M 降到 1.1M:一次真实的分包瘦身记录 https://www.taomawang.com/web/uniapp/2750.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务