UniApp 分包设计实践
这篇文章记录了把 FF14Search 的配装器、市价模块等大型功能拆进分包的过程,以及踩过的几个坑。
为什么需要分包
微信小程序对主包有 2MB 的体积限制,总包(主包 + 所有分包)不超过 20MB。FF14Search 光是游戏基础数据文件就有几百 KB,主包如果再塞进配装器的计算逻辑和静态数据,基本已经超限。
UniApp 的分包(subPackages)机制让我们可以把不常用或体量大的功能延迟加载——用户首次进入该分包页面时才触发下载,不会在启动时一次性加载全部代码。
分包结构设计
现有的分包按功能领域划分,每个分包尽量自给自足:
src/subPackages/
├── master/ # 道具详情(Garland 详细数据)
├── universalis/ # 市价详情(Universalis API + 独立 Store)
├── gearing/ # 配装器(计算逻辑、数据、Store、4 个页面)
└── utilities/ # 副本招募等小工具划分原则:
- 按功能聚合,而非按页面数量——每个分包内相关的 API、Store、组件都放在一起
- 共享代码留主包,分包之间禁止互相引用
- 分包内允许有独立 Store,不强制把所有 Pinia Store 集中到
src/stores/
分包内的 Pinia Store
分包内使用 Pinia 有一个常见误区:以为 src/stores/ 里的 Store 才算"全局的",分包内定义的 Store 是"临时的"。
实际上 Pinia 的 Store 是单例的——无论在哪里调用 defineStore,只要 id 相同,拿到的就是同一个实例。分包内的 Store 只要在分包页面里 import 就会自动注册,和放在主包没有区别。
// src/subPackages/universalis/stores/user-preference.ts
import { defineStore } from 'pinia'
export const useUserPreferenceStore = defineStore('universalis-user-pref', () => {
const defaultServer = ref('紫水栈桥')
function setDefaultServer(server: string) {
defaultServer.value = server
uni.setStorageSync('universalis_default_server', server)
}
return { defaultServer, setDefaultServer }
})注意:分包 Store 的
id建议加上分包前缀(如universalis-user-pref),避免与主包或其他分包的 Store 命名冲突。
配装器的分包移植
配装器(subPackages/gearing)是最复杂的分包,来自开源配装工具的移植,包含:
- 完整的属性计算引擎(暴击、直击、意志、速度等各属性的公式换算)
- 所有职业的装备槽定义与限制规则
- 全版本装备数据(JSON,几百 KB)
- 魔晶石镶嵌收益计算(含超出上限后的溢出)
- ShareCode 编解码(Base62 BigInt,v5 格式)
移植的主要工作是把原版逻辑改写为 Vue 3 Composition API,同时适配 UniApp 的运行环境(无 DOM、无 window 对象等)。
装备数据的加载策略
装备数据体积较大,不适合随代码打包。最终方案是把数据文件放在分包的 data/ 目录下,在用户首次进入配装器页面时动态 import:
// 懒加载配装数据,只在进入配装页面时执行
const { items } = await import('../data/items.json')这样数据只随分包下载,不影响主包启动速度。
分包路由跳转
从主包跳转到分包页面,路径需要带上分包根目录:
// 跳转到配装器(gearing 分包)
uni.navigateTo({
url: '/subPackages/gearing/pages/gearset-list/index'
})
// 带参数跳转到市价详情(universalis 分包)
uni.navigateTo({
url: `/subPackages/universalis/pages/market-price-detail/market-price-detail?itemId=${itemId}`
})分包页面跳回主包则直接用主包路径,没有限制。
几个坑
1. 分包之间不能互相引用
如果 gearing 分包里的代码 import 了 universalis 分包的模块,小程序会在运行时报路径找不到的错误。共享工具函数必须放在 src/utils/(主包)。
2. 条件编译要小心
UniApp 的条件编译(#ifdef MP-WEIXIN)在 .vue 文件的 <template> 和 <script> 块里都能工作,但在 .ts 文件里只在经过 Vite 处理的情况下生效。某些工具链版本会让条件编译在分包的 .ts 文件里失效,踩到的话可以改用运行时判断 uni.getSystemInfoSync().platform。
3. 分包 Store 的初始化时机
分包 Store 在分包代码被加载前不会执行,所以如果主包里有逻辑依赖分包 Store 的数据,会拿到 undefined。解决方法是把共享状态留在主包 Store 里,分包 Store 只管分包自己用的偏好设置。
小结
UniApp 分包本质上是一种按需加载机制,设计时只需要想清楚两件事:
- 哪些功能是核心路径(启动就要),哪些是按需访问的(可以延迟加载)
- 分包内的代码如何与主包共享必要的工具和状态,同时不造成循环依赖
把这两点想清楚,分包带来的麻烦就不多,收益(包体积控制、启动速度)相当明显。