ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

uniapp组件分包实战:告别主包2MB超限,压减体积不踩坑

uniapp组件分包实战:告别主包2MB超限,压减体积不踩坑 做小程序的人看到source size 2612kb exceed max limit 2mb这种报错心里多少都得咯噔一下。如果你的 uniapp 项目也卡在微信小程序打包体积超限这一步十有八九是主包被塞进了太多不该塞的东西。这篇文章围绕 uniapp 里的组件分包展开讲清楚它和页面分包的区别、到底解决了什么问题、怎么把 uview-plus 这种大组件库真正拆进分包里以及我在这个过程中实际踩过、也帮别人填平过的那些坑。适合正在被包体积折磨、或者打算提前给项目做减重的 uniapp 开发者。1. 组件分包这件事先想清楚再动手1.1 2MB 限制背后的包结构逻辑微信小程序对包体积的限制不是拍脑袋定的主包不能超过 2MB单个分包不能超过 2MB整个小程序所有分包加起来目前是 30MB 左右。这个“主包 2MB”才是真正的卡脖子指标因为小程序冷启动时只下载主包用户点进某个分包页面的时候才去下载对应的分包。也就是说主包体积直接决定首屏能多快弹出来平台死死压着这个数是有道理的。很多人有个误解觉得分包就是把 pages.json 里的页面挪到 subPackages 里就完事了。页面分包确实能解决“页面文件占体积”的问题但真正让主包爆炸的往往不是页面而是组件、公共 JS、静态资源和第三方 UI 库。举个例子一个页面可能只有 20KB但它引用的 uview-plus 组件、echarts 图表、业务工具函数动辄几百 KB这些全被编译进了主包。组件分包解决的就是这个场景把体积大的、只被分包页面用到的组件放到对应分包目录里让它们跟随分包按需加载而不是一股脑堆在主包。还有个容易忽略的规则普通分包可以引用主包里的页面、组件和资源但主包页面不能引用分包里的组件分包之间互相也不能引用。这意味着组件分包不是简单的“文件搬个家”而是架构层面的重新规划。我在实际项目里见过有人把某个大组件塞进分包结果主包一个 tabBar 页面也要用编译直接报“组件未找到”这就是没搞懂依赖方向导致的。1.2 组件分包和页面分包的边界组件分包可以理解成页面分包的下沉操作。页面分包管的是“哪些页面可以晚点加载”组件分包管的是“哪些组件可以跟着特定分包走”。两者经常一起出现但解决的问题不同。一个健康的 uniapp 小程序结构主包应该只保留这几类东西tabBar 页面、全局公共逻辑App.vue、main.js 里引用的东西、所有页面都要用的公共组件、公共静态资源。其他的一切包括业务页面、业务组件、重型第三方组件库都应该尝试按业务模块拆进分包。判断一个组件该不该下沉我一般问三个问题这个组件只有分包页面使用吗如果主包页面也在用下沉后主包页面就引用不到了。这个组件体积大吗一个 Button 组件才几 KB下沉没意义反而增加配置复杂度。组件内部依赖了哪些东西它引用的子组件、工具函数、样式文件也得一起能进分包否则就是个半吊子。2. 动手前的准备工作先找出项目里的“重量级选手”2.1 三步摸清主包到底是谁吃掉的做组件分包以前我强烈建议你先把主包的体积构成摸清楚不然就是盲人摸象。我常用的手段有三个第一步HBuilderX 点击“发行 - 小程序-微信”编译完成后看控制台输出。如果主包超限编译器会直接打出 warning列出所有分包的大小分布。虽然没有细到单文件但能看出哪个分包目录膨胀了。第二步打开unpackage/dist/dev/mp-weixin目录直接在系统文件管理器里按文件大小排序。这个目录就是小程序构建后的产物一级目录一般就是主包和各分包 root。你会很直观地看到哪个目录占了几百 KB哪个子目录里的组件清单特别长。这一步我每次都会做比任何分析工具都直观。第三步微信开发者工具里打开“详情 - 本地代码 - 代码质量分析”里面能看到按目录统计的包体占用还能看到哪些文件被重复打包了。这个面板对于定位“同一个组件同时存在于主包和分包”这类问题特别好使。2.2 三种分包形态别选错微信小程序的包不只有普通分包这一种形态在 uniapp 的 pages.json 里可以通过配置玩出不同花样。我自己按使用频率排个序放在表里方便你对照分包类型关键特点适用场景uniapp 支持情况跨端注意普通分包可引用主包资源不能引用其他分包绝大多数业务模块微信、支付宝、百度、抖音等主流平台基本都支持独立分包不依赖主包独立加载运行不能引用主包任何资源活动页、特殊功能页追求极致启动速度微信、QQ、百度支持App 端不支持独立分包概念分包异步化跨分包引用组件/JS动态加载主包页面想用分包里的组件或者分包间互相调用主要面向微信小程序uni-app 跨端场景下兼容性一般谨慎使用普通分包是默认形态独立分包是不依赖主包的“孤岛”异步化则是打破隔离的“桥”。做组件分包的时候默认优先用普通分包。独立分包里如果要用自定义组件组件必须完整复制进独立分包目录不能指望引主包的东西否则编译能过真机一跑就白屏。异步化我一向建议少碰后面在踩坑部分会细说。3. 实操把 uview-plus 这类大组件库真正拆进分包3.1 先说结论uni_modules 里的大组件库默认就是主包杀手uview-plus 是个典型的例子。它作为 uni_modules 插件被安装后会被 uniapp 的 easycom 机制自动扫描注册所有模板里用到的u-xxx组件都会被编译进主包。一个成熟的组件库全量编译下来几百 KB 甚至上 MB 都不奇怪主包不超限才怪。这里需要先理解 easycom 的扫描规则它默认自动扫描src/components/和src/uni_modules/目录组件名必须和目录名一致。注意它不会扫描你为分包新建的src/package-xxx/components/目录。所以组件分包的一个核心矛盾就是uni_modules 里的大组件库想进分包但 easycom 不给它自动扫进去。解决办法要么是配置 easycom 的 custom 规则指定这些组件指向分包目录要么更简单粗暴——把组件源码复制到分包目录里手动维护。我推荐把“复制到分包目录 手动 import”作为首选方案。原因后面说。3.2 目录结构和 pages.json 配置全流程假设项目结构是这样主包有 5 个 tabBar 页面分包有一个商品详情包package-product和一个会员中心包package-member。商品详情页需要 uview-plus 里的轮播、弹窗、空状态、步进器这几个组件。目标是把这几个组件放进package-product分包主包只保留公共按钮、图标这类基础组件。先改目录把用到的组件复制到分包里src/ ├─ components/ # 主包公共组件保留 u-button、u-icon │ ├─ u-button/ │ └─ u-icon/ ├─ pages/ # 主包 tabBar 页面 ├─ package-product/ │ ├─ components/ │ │ ├─ u-swiper/ # 从 uni_modules 复制过来的 │ │ ├─ u-popup/ │ │ ├─ u-empty/ │ │ └─ u-number-box/ │ ├─ pages/ │ │ └─ detail/index.vue │ └─ static/ ├─ package-member/ │ ├─ components/ │ ├─ pages/ │ └─ static/ ├─ static/ ├─ App.vue ├─ main.js ├─ manifest.json └─ pages.json然后配置 pages.json。subPackages 负责声明分包页面preloadRule 负责预加载easycom 负责组件自动注册。注意/在 uniapp 里指向源码根目录{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 首页 } } ], subPackages: [ { root: package-product, pages: [ { path: pages/detail/index, style: { navigationBarTitleText: 商品详情 } } ] }, { root: package-member, pages: [ { path: pages/center/index, style: { navigationBarTitleText: 会员中心 } } ] } ], preloadRule: { pages/index/index: { network: all, packages: [package-product] } }, easycom: { autoscan: true, custom: { ^u-(.*): /components/u-$1/u-$1.vue, ^sp-(.*): /package-product/components/sp-$1/sp-$1.vue } } }注意一个细节我把复制进分包目录的组件加了sp-前缀比如sp-swiper、sp-popup。为什么要这么做如果分组件还叫u-swiper而 easycom 的^u-(.*)规则指向主包的/components/u-$1/u-$1.vue那么两个规则就会冲突编译器到底匹配哪个完全看心情。加前缀虽然看起来不优雅但能一劳永逸地避免命名撞车。3.3 三种引用方式跨端兼容性真的不一样组件放进分包目录后页面里怎么引用我在项目里试过三种方式效果差挺多。第一种easycom custom 规则。上面的配置里我已经写了^sp-(.*)的规则这样package-product分包页面里写sp-swiper /编译器会自动映射到/package-product/components/sp-swiper/sp-swiper.vue。这种方式最省事模板里直接用就行不用在 script 里手动注册。但有两个前提一是组件文件名必须和目录名一致二是路径模板里的正则捕获要写对。我吃过一次亏路径忘写$1结果所有组件都指向同一个文件编译不报错页面白屏。第二种手动 import components 注册。这种方式最保险、最可控也是我遇到疑难杂症时回退的首选方案template view sp-swiper :listbannerList / /view /template script setup import SpSwiper from /package-product/components/sp-swiper/sp-swiper.vue /script组件必须用未改名的文件。这样无论 easycom 规则怎么变页面都能准确找到组件。缺点是要在每个页面里写 import代码稍微多几行。不过对于已经要拆分包的项目来说这点重复完全可以接受。第三种动态加载/异步引用。微信小程序的分包异步化支持require.async这类跨分包动态加载能力但 uniapp 是跨端框架这套东西到了 App 端和其他平台可能就失灵了。我见过团队为了“优化”在主包页面里异步加载分包组件结果小程序端没事App 端直接空白最后被迫用条件编译写了一堆分支。我的态度很明确除非项目只做微信小程序且短期不跨端否则别在 uniapp 里搞分包异步化老老实实通过目录结构调整依赖就完了。3.4 复制组件时内部依赖链才是真正的坑把 uview-plus 的组件从uni_modules复制到分包目录不是简单的复制粘贴。这些组件内部往往还依赖其他东西。拿u-popup举例它可能引用了u-overlay、u-transition还可能引用了组件库内部的工具函数模块libs/function/index.js、混入libs/mixin、公共样式变量等等。我建议复制完组件后先全局搜索组件源码里的 import 语句把相对路径的依赖一个个跟着复制过去。如果依赖指向/uni_modules/uview-plus/libs/...那也得把 libs 目录按需复制到分包里或者改成相对路径。这一步很琐碎但漏一个依赖编译的时候就会报模块找不到或者页面渲染样式错乱。这里就体现出“手动复制组件到分包”的代价了。如果组件依赖特别深比如一个图表类组件拖了十多个内部依赖维护成本会很高。遇到这种情况我会评估一下是不是干脆把整个组件库按需处理能删的删能替换的替换。比如 uview-plus 里我只用了四五个组件那完全没必要保留整个库把用到的组件连同它们的依赖一起剪出来才能真正瘦身。4. 踩坑实录组件分包上线前后最容易踩的 5 个坑4.1 “未找到组件”报错但代码看着没问题这个报错我在拆包第一周遇到不下三次。最常见的三个原因easycom 缓存没刷新、路径大小写不一致、组件目录名和文件名不一致。HBuilderX 自带编译缓存改了 pages.json 里的 easycom 规则后有时候热更新没触发重新编译页面就一直读旧配置。我的处理方式是改完配置后手动执行一次“重新编译”而不是依赖热更新。路径大小写问题更隐蔽macOS 下文件系统不区分大小写但微信小程序的构建链路是区分大小写的SpSwiper和spSwiper目录名不一样会导致真机找不到组件。建议所有组件目录统一用小写加连字符。4.2 明明做了分包主包还是超 2MB这种情况通常不是组件没拆干净而是静态资源和公共逻辑没跟着拆。常见元凶就这几类static/目录下全量图片、图标库被打进主包App.vue里 import 了全局组件或工具库main.js里挂载了 Vue 插件、全局混入组件虽然复制进了分包但组件内部 import 的公共模块还在主包编码里排查方法很朴素看unpackage/dist/dev/mp-weixin目录里主包各目录的大小排行然后从大到小一个一个找源头。图片资源、字体文件这种能压缩的压缩能转 CDN 的转 CDN能放进分包的跟着页面走。公共 JS 如果被多个分包使用看看能不能抽成独立分包或者用 npm 包管理避免每个分包各存一份。还有一个许多人不知道的选项HBuilderX 的 manifest.json 里微信小程序配置下有个“开启分包优化”开关。开启后编译器会自动提取分包之间的公共代码减少重复打包。但不能只靠它它治标不治本手动拆分才是根本。4.3 组件样式乱了、图标字体变成方块这是复制组件到分包后最常见的“隐藏炸弹”。样式错乱的原因一般是组件内部的import路径指向了原组件库的位置或者引用了不存在的相对路径导致 wxss 编译异常。图标字体变方块的问题更典型。uview-plus 里很多组件依赖图标字体文件字体文件的 url 路径如果写的是/uni_modules/uview-plus/...复制到分包后路径就失效了。微信小程序里字体的引用方式和 H5 不一样不建议用本地字体的绝对路径改用 base64 编码嵌入或者把字体文件放到分包的static目录下再修改组件样式里的font-face路径。我在自己的项目里处理过两次最后都统一改成 base64 字体的方式彻底绝了后患。4.4 独立分包的白屏问题组件依赖主包工具函数独立分包的初衷是“不下载主包也能跑”所以独立分包内的组件和 JS 不能引用主包的任何资源。这句话听上去很好理解但实际项目里总有漏网之鱼。比如某个组件里写了一句import { formatTime } from /utils/index这个utils/index在主包里独立分包加载时根本拿不到页面就白屏了。而且编译阶段大概率不会报错因为编译器只做静态检查发现不了这种运行时依赖。解决办法有两条路要么把用到的工具函数直接复制进独立分包里要么把该分包从独立分包改成普通分包。我个人的判断标准是看这个分包对启动速度的要求到底有多高如果只是一个普通的会员中心页面完全没有必要强行设置 independent自找麻烦。4.5 明明已经分包了首屏还是慢分包解决了“能不能发版”的问题但不能保证“体验好不好”。用户进小程序先下主包再进分包页面时还得现场下载分包。如果分包做得比较碎或者从首页跳分包页面的场景特别频繁会明显感到页面打开变慢。我最终的解法是配合preloadRule做分包预加载。在首页加载后等网络空闲了先下载商品详情分包这样用户点进去的时候基本无缝切换。配置很简单preloadRule: { pages/index/index: { network: all, packages: [package-product] } }这里的network字段可以选all或wifi不差流量的话直接选all。预加载的目的不是让小程序的包更小而是让用户感知不到分包的存在。组件分包把体积问题解决了预加载把性能问题兜住了两者配合才是一套完整的优化方案。说了这么多回到最初那个报错截图。组件分包不是 PPT 里的概念它就是每个 uniapp 项目在包体积失控前最实际的一道防线。我在多个项目里把它落地成了标准流程先分析体积构成再规划主包和分包边界然后把重型组件按依赖链完整下沉最后用预加载把体验兜住。这套流程跑顺之后主包体积从我接手时的 2.8MB 一直压到 1.2MB 左右发版再也没被 2MB 限制卡过脖子。你如果正准备对项目动刀建议从最小的业务分包开始试别一上来就拆组件库先把目录结构和依赖方向理顺组件分包就是水到渠成的事。
返回列表