问题背景: 小程序主包限制2MB,总包限制20MB,复杂项目容易超限。需要合理规划分包策略。
{
"pages": [
"pages/index/index",
"pages/home/home",
"pages/cart/cart",
"pages/user/user"
],
"subpackages": [
{
"root": "packages/product",
"name": "product",
"pages": [
"pages/list/list",
"pages/detail/detail",
"pages/search/search"
]
},
{
"root": "packages/order",
"name": "order",
"pages": [
"pages/list/list",
"pages/detail/detail",
"pages/confirm/confirm"
]
},
{
"root": "packages/activity",
"name": "activity",
"pages": [
"pages/seckill/seckill",
"pages/groupon/groupon"
],
"independent": true
}
],
"preloadRule": {
"pages/index/index": {
"network": "wifi",
"packages": ["product"]
},
"packages/product/pages/list/list": {
"network": "all",
"packages": ["order"]
}
},
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/cart/cart",
"text": "购物车"
},
{
"pagePath": "pages/user/user",
"text": "我的"
}
]
}
}
// utils/subpackage.js
/**
* 分包加载管理器
*/
class SubpackageManager {
constructor() {
this.loadedPackages = new Set()
this.loadingPackages = new Map()
}
/**
* 加载分包
*/
loadSubpackage(name) {
// 已加载
if (this.loadedPackages.has(name)) {
return Promise.resolve()
}
// 正在加载
if (this.loadingPackages.has(name)) {
return this.loadingPackages.get(name)
}
// 开始加载
const promise = new Promise((resolve, reject) => {
wx.loadSubpackage({
name,
success: () => {
console.log(`分包 ${name} 加载成功`)
this.loadedPackages.add(name)
this.loadingPackages.delete(name)
resolve()
},
fail: (err) => {
console.error(`分包 ${name} 加载失败:`, err)
this.loadingPackages.delete(name)
reject(err)
}
})
})
this.loadingPackages.set(name, promise)
return promise
}
/**
* 预加载分包
*/
async preloadSubpackages(names) {
const promises = names.map(name => this.loadSubpackage(name))
return Promise.all(promises)
}
/**
* 检查分包是否已加载
*/
isLoaded(name) {
return this.loadedPackages.has(name)
}
/**
* 获取加载进度
*/
getProgress(name) {
return new Promise((resolve) => {
const task = wx.loadSubpackage({
name,
success: () => {
resolve(100)
},
fail: () => {
resolve(0)
}
})
task.onProgressUpdate((res) => {
resolve(res.progress)
})
})
}
}
export default new SubpackageManager()
// config/routes.js
export const routes = {
// 主包路由
index: '/pages/index/index',
home: '/pages/home/home',
cart: '/pages/cart/cart',
user: '/pages/user/user',
// 商品分包
productList: '/packages/product/pages/list/list',
productDetail: '/packages/product/pages/detail/detail',
productSearch: '/packages/product/pages/search/search',
// 订单分包
orderList: '/packages/order/pages/list/list',
orderDetail: '/packages/order/pages/detail/detail',
orderConfirm: '/packages/order/pages/confirm/confirm',
// 活动分包(独立分包)
seckill: '/packages/activity/pages/seckill/seckill',
groupon: '/packages/activity/pages/groupon/groupon'
}
// 分包映射
export const packageMap = {
'/packages/product': 'product',
'/packages/order': 'order',
'/packages/activity': 'activity'
}
/**
* 获取路径所属分包
*/
export function getPackageName(path) {
for (const [root, name] of Object.entries(packageMap)) {
if (path.startsWith(root)) {
return name
}
}
return null
}
// utils/router.js
import subpackageManager from './subpackage'
import { getPackageName } from '../config/routes'
class Router {
/**
* 导航到页面(自动处理分包加载)
*/
async navigateTo(url, options = {}) {
const packageName = getPackageName(url)
// 如果是分包页面,先加载分包
if (packageName && !subpackageManager.isLoaded(packageName)) {
wx.showLoading({ title: '加载中...' })
try {
await subpackageManager.loadSubpackage(packageName)
wx.hideLoading()
} catch (error) {
wx.hideLoading()
wx.showToast({
title: '加载失败',
icon: 'none'
})
return
}
}
// 跳转页面
wx.navigateTo({
url,
...options,
fail: (err) => {
console.error('页面跳转失败:', err)
wx.showToast({
title: '页面不存在',
icon: 'none'
})
}
})
}
/**
* 重定向
*/
async redirectTo(url, options = {}) {
const packageName = getPackageName(url)
if (packageName && !subpackageManager.isLoaded(packageName)) {
wx.showLoading({ title: '加载中...' })
await subpackageManager.loadSubpackage(packageName)
wx.hideLoading()
}
wx.redirectTo({ url, ...options })
}
/**
* 返回
*/
navigateBack(delta = 1) {
wx.navigateBack({ delta })
}
/**
* 切换Tab
*/
switchTab(url) {
wx.switchTab({ url })
}
/**
* 重启到页面
*/
reLaunch(url) {
wx.reLaunch({ url })
}
}
export default new Router()
问题背景: 小程序页面栈最多10层,超过后无法使用navigateTo,影响用户体验。
// utils/pageStack.js
class PageStackManager {
constructor() {
this.maxStack = 10 // 最大页面栈深度
this.stackHistory = [] // 栈历史记录
}
/**
* 获取当前页面栈
*/
getCurrentPages() {
return getCurrentPages()
}
/**
* 获取当前页面栈深度
*/
getStackDepth() {
return this.getCurrentPages().length
}
/**
* 检查是否可以push
*/
canPush() {
return this.getStackDepth() < this.maxStack
}
/**
* 智能导航
*/
navigate(url, params = {}) {
const fullUrl = this.buildUrl(url, params)
if (this.canPush()) {
// 可以push
return this.push(fullUrl)
} else {
// 栈满,使用redirect
return this.replace(fullUrl)
}
}
/**
* Push页面
*/
push(url) {
return new Promise((resolve, reject) => {
wx.navigateTo({
url,
success: () => {
this.recordStack('push', url)
resolve()
},
fail: reject
})
})
}
/**
* Replace页面
*/
replace(url) {
return new Promise((resolve, reject) => {
wx.redirectTo({
url,
success: () => {
this.recordStack('replace', url)
resolve()
},
fail: reject
})
})
}
/**
* 返回
*/
back(delta = 1) {
return new Promise((resolve, reject) => {
wx.navigateBack({
delta,
success: () => {
this.recordStack('back', delta)
resolve()
},
fail: reject
})
})
}
/**
* 返回到指定页面
*/
backToPage(url) {
const pages = this.getCurrentPages()
let targetIndex = -1
// 查找目标页面
for (let i = pages.length - 1; i >= 0; i--) {
if (pages[i].route === url || pages[i].route === url.replace(/^\//, '')) {
targetIndex = i
break
}
}
if (targetIndex === -1) {
// 目标页面不在栈中,直接跳转
return this.replace(url)
} else {
// 计算需要返回的层数
const delta = pages.length - 1 - targetIndex
return this.back(delta)
}
}
/**
* 清空栈并跳转
*/
reLaunch(url) {
return new Promise((resolve, reject) => {
wx.reLaunch({
url,
success: () => {
this.stackHistory = []
this.recordStack('relaunch', url)
resolve()
},
fail: reject
})
})
}
/**
* 记录栈操作
*/
recordStack(action, data) {
this.stackHistory.push({
action,
data,
depth: this.getStackDepth(),
timestamp: Date.now()
})
// 保存到storage(用于跨会话恢复)
wx.setStorageSync('page_stack_history', this.stackHistory.slice(-20))
}
/**
* 构建URL
*/
buildUrl(url, params = {}) {
if (Object.keys(params).length === 0) {
return url
}
const query = Object.entries(params)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&')
return `${url}?${query}`
}
/**
* 获取栈历史
*/
getHistory() {
return this.stackHistory
}
/**
* 获取当前页面信息
*/
getCurrentPage() {
const pages = this.getCurrentPages()
return pages[pages.length - 1]
}
/**
* 获取上一页面信息
*/
getPreviousPage() {
const pages = this.getCurrentPages()
return pages[pages.length - 2]
}
}
export default new PageStackManager()
// utils/routerGuard.js
import pageStack from './pageStack'
class RouterGuard {
constructor() {
this.guards = []
}
/**
* 注册全局前置守卫
*/
beforeEach(guard) {
this.guards.push(guard)
}
/**
* 执行守卫
*/
async runGuards(to, from) {
for (const guard of this.guards) {
const result = await guard(to, from)
if (result === false) {
// 取消导航
return false
} else if (typeof result === 'string') {
// 重定向到其他页面
return result
}
}
return true
}
/**
* 拦截导航
*/
async intercept(url) {
const from = pageStack.getCurrentPage()
const to = { url }
const result = await this.runGuards(to, from)
if (result === false) {
return null
} else if (typeof result === 'string') {
return result
} else {
return url
}
}
}
// 创建全局路由守卫
const routerGuard = new RouterGuard()
// 注册登录检查守卫
routerGuard.beforeEach((to, from) => {
const needLogin = [
'/packages/order',
'/packages/user'
]
const needAuth = needLogin.some(path => to.url.startsWith(path))
if (needAuth) {
const isLogin = wx.getStorageSync('token')
if (!isLogin) {
// 保存目标页面,登录后跳转
wx.setStorageSync('redirect_url', to.url)
wx.showToast({
title: '请先登录',
icon: 'none'
})
// 跳转到登录页
return '/pages/login/login'
}
}
return true
})
export default routerGuard
// utils/router.js
import pageStack from './pageStack'
import routerGuard from './routerGuard'
class Router {
constructor() {
this.paramCache = new Map() // 参数缓存池
}
/**
* 导航到页面
*/
async push(url, params = {}) {
// 执行路由守卫
const finalUrl = await routerGuard.intercept(this.buildUrl(url, params))
if (!finalUrl) return
// 智能导航
return pageStack.navigate(finalUrl)
}
/**
* 替换当前页面
*/
async replace(url, params = {}) {
const finalUrl = await routerGuard.intercept(this.buildUrl(url, params))
if (!finalUrl) return
return pageStack.replace(finalUrl)
}
/**
* 返回
*/
back(delta = 1) {
return pageStack.back(delta)
}
/**
* 返回到指定页面
*/
backTo(url) {
return pageStack.backToPage(url)
}
/**
* 切换Tab
*/
switchTab(url) {
wx.switchTab({ url })
}
/**
* 重启
*/
reLaunch(url, params = {}) {
return pageStack.reLaunch(this.buildUrl(url, params))
}
/**
* 构建URL(支持复杂参数)
*/
buildUrl(url, params = {}) {
if (Object.keys(params).length === 0) {
return url
}
// 简单参数直接拼接
const simpleParams = {}
const complexParams = {}
Object.entries(params).forEach(([key, value]) => {
if (typeof value === 'object' || typeof value === 'function') {
complexParams[key] = value
} else {
simpleParams[key] = value
}
})
// 复杂参数存入缓存
let paramKey = ''
if (Object.keys(complexParams).length > 0) {
paramKey = this.generateParamKey()
this.paramCache.set(paramKey, complexParams)
simpleParams._paramKey = paramKey
}
// 构建query string
const query = Object.entries(simpleParams)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&')
return `${url}?${query}`
}
/**
* 获取页面参数
*/
getParams(options) {
const params = { ...options }
// 如果有缓存的复杂参数,取出来
if (params._paramKey) {
const complexParams = this.paramCache.get(params._paramKey)
if (complexParams) {
Object.assign(params, complexParams)
this.paramCache.delete(params._paramKey)
}
delete params._paramKey
}
return params
}
/**
* 生成参数key
*/
generateParamKey() {
return `param_${Date.now()}_${Math.random().toString(36).slice(2)}`
}
/**
* 获取当前页面
*/
getCurrentPage() {
return pageStack.getCurrentPage()
}
/**
* 获取上一页面
*/
getPreviousPage() {
return pageStack.getPreviousPage()
}
}
export default new Router()
// utils/basePage.js
/**
* 创建增强页面
*/
export function createPage(config) {
const {
data = {},
onLoad,
onShow,
onHide,
onUnload,
onShareAppMessage,
...rest
} = config
return {
data: {
...data,
_loading: false,
_error: null
},
// 资源清理列表
_disposers: [],
/**
* 页面加载
*/
onLoad(options) {
console.log('[Page] onLoad:', this.route, options)
// 解析参数
this._options = this._parseOptions(options)
// 登录检查
this._checkLogin()
// 埋点上报
this._reportPageView()
// 执行页面的onLoad
if (onLoad) {
onLoad.call(this, this._options)
}
},
/**
* 页面显示
*/
onShow() {
console.log('[Page] onShow:', this.route)
// 埋点上报
this._reportPageShow()
// 执行页面的onShow
if (onShow) {
onShow.call(this)
}
},
/**
* 页面隐藏
*/
onHide() {
console.log('[Page] onHide:', this.route)
// 执行页面的onHide
if (onHide) {
onHide.call(this)
}
},
/**
* 页面卸载
*/
onUnload() {
console.log('[Page] onUnload:', this.route)
// 清理资源
this._dispose()
// 执行页面的onUnload
if (onUnload) {
onUnload.call(this)
}
},
/**
* 分享
*/
onShareAppMessage(options) {
console.log('[Page] onShareAppMessage:', options)
// 默认分享配置
const defaultShare = {
title: '推荐给你',
path: `/${this.route}`,
imageUrl: ''
}
// 执行页面的分享配置
if (onShareAppMessage) {
const customShare = onShareAppMessage.call(this, options)
return { ...defaultShare, ...customShare }
}
return defaultShare
},
/**
* 解析参数
*/
_parseOptions(options) {
// 支持router传递的复杂参数
if (options._paramKey) {
const router = require('./router').default
return router.getParams(options)
}
return options
},
/**
* 登录检查
*/
_checkLogin() {
// 如果页面需要登录,检查登录状态
if (config.needLogin) {
const token = wx.getStorageSync('token')
if (!token) {
wx.showToast({
title: '请先登录',
icon: 'none'
})
setTimeout(() => {
wx.navigateTo({
url: '/pages/login/login'
})
}, 1500)
}
}
},
/**
* 页面访问埋点
*/
_reportPageView() {
// 上报页面访问
wx.reportAnalytics('page_view', {
page: this.route,
...this._options
})
},
/**
* 页面显示埋点
*/
_reportPageShow() {
// 上报页面显示
wx.reportAnalytics('page_show', {
page: this.route
})
},
/**
* 设置loading状态
*/
setLoading(loading) {
this.setData({ _loading: loading })
if (loading) {
wx.showLoading({ title: '加载中...' })
} else {
wx.hideLoading()
}
},
/**
* 设置错误
*/
setError(error) {
this.setData({ _error: error })
wx.showToast({
title: error.message || '操作失败',
icon: 'none'
})
},
/**
* 注册定时器
*/
setTimeout(callback, delay) {
const timer = setTimeout(callback, delay)
this._disposers.push(() => clearTimeout(timer))
return timer
},
/**
* 注册interval
*/
setInterval(callback, delay) {
const timer = setInterval(callback, delay)
this._disposers.push(() => clearInterval(timer))
return timer
},
/**
* 注册网络请求
*/
request(options) {
const task = wx.request(options)
this._disposers.push(() => task.abort())
return task
},
/**
* 清理资源
*/
_dispose() {
this._disposers.forEach(disposer => {
try {
disposer()
} catch (error) {
console.error('清理资源失败:', error)
}
})
this._disposers = []
},
// 合并其他方法
...rest
}
}
// pages/product/detail.js
import { createPage } from '@/utils/basePage'
import router from '@/utils/router'
createPage({
// 需要登录
needLogin: false,
data: {
product: null,
loading: true
},
onLoad(options) {
const { id } = options
this.loadProduct(id)
},
async loadProduct(id) {
this.setLoading(true)
try {
const res = await this.request({
url: `/api/product/${id}`,
method: 'GET'
})
this.setData({
product: res.data,
loading: false
})
} catch (error) {
this.setError(error)
this.setLoading(false)
}
},
// 倒计时
startCountdown() {
let count = 60
this.setInterval(() => {
count--
this.setData({ countdown: count })
if (count === 0) {
// interval会自动清理
}
}, 1000)
},
// 购买
handleBuy() {
router.push('/packages/order/pages/confirm/confirm', {
productId: this.data.product.id,
quantity: 1
})
},
// 分享
onShareAppMessage() {
return {
title: this.data.product.name,
path: `/pages/product/detail?id=${this.data.product.id}`,
imageUrl: this.data.product.image
}
}
})