perf(路由预加载): 修复静态 import 导致入口文件膨胀问题

问题:
- 原实现使用静态 import() 映射表
- Rollup 静态分析时将所有 37 个视图组件引用打包进 index.js
- 导致首次加载时需要解析大量未使用的 import 语句

修复:
- 移除静态 import() 映射,改用纯路径字符串邻接表
- 通过 router.getRoutes() 动态获取组件的 import 函数
- 延迟初始化 routePrefetch,首次导航时才创建实例
- 更新测试文件使用 mock router

效果:
- index.js 中动态 import 引用从 37 个减少到 1 个
- 首次加载不再包含未使用的视图组件引用
- 41 个测试全部通过

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
yangjianbo
2026-01-16 22:07:39 +08:00
parent d9433699db
commit b0569d873a
3 changed files with 143 additions and 215 deletions

View File

@@ -2,20 +2,7 @@
* useRoutePrefetch 组合式函数单元测试 * useRoutePrefetch 组合式函数单元测试
*/ */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import type { RouteLocationNormalized } from 'vue-router' import type { RouteLocationNormalized, Router, RouteRecordNormalized } from 'vue-router'
// Mock 所有动态 import
vi.mock('@/views/admin/AccountsView.vue', () => ({ default: {} }))
vi.mock('@/views/admin/UsersView.vue', () => ({ default: {} }))
vi.mock('@/views/admin/DashboardView.vue', () => ({ default: {} }))
vi.mock('@/views/admin/GroupsView.vue', () => ({ default: {} }))
vi.mock('@/views/admin/SubscriptionsView.vue', () => ({ default: {} }))
vi.mock('@/views/admin/RedeemView.vue', () => ({ default: {} }))
vi.mock('@/views/user/KeysView.vue', () => ({ default: {} }))
vi.mock('@/views/user/UsageView.vue', () => ({ default: {} }))
vi.mock('@/views/user/DashboardView.vue', () => ({ default: {} }))
vi.mock('@/views/user/RedeemView.vue', () => ({ default: {} }))
vi.mock('@/views/user/ProfileView.vue', () => ({ default: {} }))
import { useRoutePrefetch, _adminPrefetchMap, _userPrefetchMap } from '../useRoutePrefetch' import { useRoutePrefetch, _adminPrefetchMap, _userPrefetchMap } from '../useRoutePrefetch'
@@ -32,11 +19,37 @@ const createMockRoute = (path: string): RouteLocationNormalized => ({
redirectedFrom: undefined redirectedFrom: undefined
}) })
// Mock Router
const createMockRouter = (): Router => {
const mockImportFn = vi.fn().mockResolvedValue({ default: {} })
const routes: Partial<RouteRecordNormalized>[] = [
{ path: '/admin/dashboard', components: { default: mockImportFn } },
{ path: '/admin/accounts', components: { default: mockImportFn } },
{ path: '/admin/users', components: { default: mockImportFn } },
{ path: '/admin/groups', components: { default: mockImportFn } },
{ path: '/admin/subscriptions', components: { default: mockImportFn } },
{ path: '/admin/redeem', components: { default: mockImportFn } },
{ path: '/dashboard', components: { default: mockImportFn } },
{ path: '/keys', components: { default: mockImportFn } },
{ path: '/usage', components: { default: mockImportFn } },
{ path: '/redeem', components: { default: mockImportFn } },
{ path: '/profile', components: { default: mockImportFn } }
]
return {
getRoutes: () => routes as RouteRecordNormalized[]
} as Router
}
describe('useRoutePrefetch', () => { describe('useRoutePrefetch', () => {
let originalRequestIdleCallback: typeof window.requestIdleCallback let originalRequestIdleCallback: typeof window.requestIdleCallback
let originalCancelIdleCallback: typeof window.cancelIdleCallback let originalCancelIdleCallback: typeof window.cancelIdleCallback
let mockRouter: Router
beforeEach(() => { beforeEach(() => {
mockRouter = createMockRouter()
// 保存原始函数 // 保存原始函数
originalRequestIdleCallback = window.requestIdleCallback originalRequestIdleCallback = window.requestIdleCallback
originalCancelIdleCallback = window.cancelIdleCallback originalCancelIdleCallback = window.cancelIdleCallback
@@ -58,14 +71,14 @@ describe('useRoutePrefetch', () => {
describe('_isAdminRoute', () => { describe('_isAdminRoute', () => {
it('应该正确识别管理员路由', () => { it('应该正确识别管理员路由', () => {
const { _isAdminRoute } = useRoutePrefetch() const { _isAdminRoute } = useRoutePrefetch(mockRouter)
expect(_isAdminRoute('/admin/dashboard')).toBe(true) expect(_isAdminRoute('/admin/dashboard')).toBe(true)
expect(_isAdminRoute('/admin/users')).toBe(true) expect(_isAdminRoute('/admin/users')).toBe(true)
expect(_isAdminRoute('/admin/accounts')).toBe(true) expect(_isAdminRoute('/admin/accounts')).toBe(true)
}) })
it('应该正确识别非管理员路由', () => { it('应该正确识别非管理员路由', () => {
const { _isAdminRoute } = useRoutePrefetch() const { _isAdminRoute } = useRoutePrefetch(mockRouter)
expect(_isAdminRoute('/dashboard')).toBe(false) expect(_isAdminRoute('/dashboard')).toBe(false)
expect(_isAdminRoute('/keys')).toBe(false) expect(_isAdminRoute('/keys')).toBe(false)
expect(_isAdminRoute('/usage')).toBe(false) expect(_isAdminRoute('/usage')).toBe(false)
@@ -74,7 +87,7 @@ describe('useRoutePrefetch', () => {
describe('_getPrefetchConfig', () => { describe('_getPrefetchConfig', () => {
it('管理员 dashboard 应该返回正确的预加载配置', () => { it('管理员 dashboard 应该返回正确的预加载配置', () => {
const { _getPrefetchConfig } = useRoutePrefetch() const { _getPrefetchConfig } = useRoutePrefetch(mockRouter)
const route = createMockRoute('/admin/dashboard') const route = createMockRoute('/admin/dashboard')
const config = _getPrefetchConfig(route) const config = _getPrefetchConfig(route)
@@ -82,7 +95,7 @@ describe('useRoutePrefetch', () => {
}) })
it('普通用户 dashboard 应该返回正确的预加载配置', () => { it('普通用户 dashboard 应该返回正确的预加载配置', () => {
const { _getPrefetchConfig } = useRoutePrefetch() const { _getPrefetchConfig } = useRoutePrefetch(mockRouter)
const route = createMockRoute('/dashboard') const route = createMockRoute('/dashboard')
const config = _getPrefetchConfig(route) const config = _getPrefetchConfig(route)
@@ -90,7 +103,7 @@ describe('useRoutePrefetch', () => {
}) })
it('未定义的路由应该返回空数组', () => { it('未定义的路由应该返回空数组', () => {
const { _getPrefetchConfig } = useRoutePrefetch() const { _getPrefetchConfig } = useRoutePrefetch(mockRouter)
const route = createMockRoute('/unknown-route') const route = createMockRoute('/unknown-route')
const config = _getPrefetchConfig(route) const config = _getPrefetchConfig(route)
@@ -100,7 +113,7 @@ describe('useRoutePrefetch', () => {
describe('triggerPrefetch', () => { describe('triggerPrefetch', () => {
it('应该在浏览器空闲时触发预加载', async () => { it('应该在浏览器空闲时触发预加载', async () => {
const { triggerPrefetch, prefetchedRoutes } = useRoutePrefetch() const { triggerPrefetch, prefetchedRoutes } = useRoutePrefetch(mockRouter)
const route = createMockRoute('/admin/dashboard') const route = createMockRoute('/admin/dashboard')
triggerPrefetch(route) triggerPrefetch(route)
@@ -112,7 +125,7 @@ describe('useRoutePrefetch', () => {
}) })
it('应该避免重复预加载同一路由', async () => { it('应该避免重复预加载同一路由', async () => {
const { triggerPrefetch, prefetchedRoutes } = useRoutePrefetch() const { triggerPrefetch, prefetchedRoutes } = useRoutePrefetch(mockRouter)
const route = createMockRoute('/admin/dashboard') const route = createMockRoute('/admin/dashboard')
triggerPrefetch(route) triggerPrefetch(route)
@@ -129,7 +142,7 @@ describe('useRoutePrefetch', () => {
describe('cancelPendingPrefetch', () => { describe('cancelPendingPrefetch', () => {
it('应该取消挂起的预加载任务', () => { it('应该取消挂起的预加载任务', () => {
const { triggerPrefetch, cancelPendingPrefetch, prefetchedRoutes } = useRoutePrefetch() const { triggerPrefetch, cancelPendingPrefetch, prefetchedRoutes } = useRoutePrefetch(mockRouter)
const route = createMockRoute('/admin/dashboard') const route = createMockRoute('/admin/dashboard')
triggerPrefetch(route) triggerPrefetch(route)
@@ -142,7 +155,7 @@ describe('useRoutePrefetch', () => {
describe('路由变化时取消之前的预加载', () => { describe('路由变化时取消之前的预加载', () => {
it('应该在路由变化时取消之前的预加载任务', async () => { it('应该在路由变化时取消之前的预加载任务', async () => {
const { triggerPrefetch, prefetchedRoutes } = useRoutePrefetch() const { triggerPrefetch, prefetchedRoutes } = useRoutePrefetch(mockRouter)
// 触发第一个路由的预加载 // 触发第一个路由的预加载
triggerPrefetch(createMockRoute('/admin/dashboard')) triggerPrefetch(createMockRoute('/admin/dashboard'))
@@ -160,7 +173,7 @@ describe('useRoutePrefetch', () => {
describe('resetPrefetchState', () => { describe('resetPrefetchState', () => {
it('应该重置所有预加载状态', async () => { it('应该重置所有预加载状态', async () => {
const { triggerPrefetch, resetPrefetchState, prefetchedRoutes } = useRoutePrefetch() const { triggerPrefetch, resetPrefetchState, prefetchedRoutes } = useRoutePrefetch(mockRouter)
const route = createMockRoute('/admin/dashboard') const route = createMockRoute('/admin/dashboard')
triggerPrefetch(route) triggerPrefetch(route)
@@ -194,7 +207,7 @@ describe('useRoutePrefetch', () => {
return setTimeout(() => cb({ didTimeout: true, timeRemaining: () => 0 }), timeout) return setTimeout(() => cb({ didTimeout: true, timeRemaining: () => 0 }), timeout)
}) })
const { triggerPrefetch, prefetchedRoutes } = useRoutePrefetch() const { triggerPrefetch, prefetchedRoutes } = useRoutePrefetch(mockRouter)
const route = createMockRoute('/dashboard') const route = createMockRoute('/dashboard')
triggerPrefetch(route) triggerPrefetch(route)
@@ -208,12 +221,24 @@ describe('useRoutePrefetch', () => {
describe('预加载失败处理', () => { describe('预加载失败处理', () => {
it('预加载失败时应该静默处理不影响页面功能', async () => { it('预加载失败时应该静默处理不影响页面功能', async () => {
// 这个测试验证预加载失败不会抛出异常 const { triggerPrefetch } = useRoutePrefetch(mockRouter)
const { triggerPrefetch } = useRoutePrefetch()
const route = createMockRoute('/admin/dashboard') const route = createMockRoute('/admin/dashboard')
// 不应该抛出异常 // 不应该抛出异常
expect(() => triggerPrefetch(route)).not.toThrow() expect(() => triggerPrefetch(route)).not.toThrow()
}) })
}) })
describe('无 router 时的行为', () => {
it('没有传入 router 时应该正常工作但不执行预加载', async () => {
const { triggerPrefetch, prefetchedRoutes } = useRoutePrefetch()
const route = createMockRoute('/admin/dashboard')
triggerPrefetch(route)
await new Promise((resolve) => setTimeout(resolve, 100))
// 没有 router无法获取组件所以不会预加载
expect(prefetchedRoutes.value.size).toBe(0)
})
})
}) })

View File

@@ -1,9 +1,14 @@
/** /**
* 路由预加载组合式函数 * 路由预加载组合式函数
* 在浏览器空闲时预加载可能访问的下一个页面,提升导航体验 * 在浏览器空闲时预加载可能访问的下一个页面,提升导航体验
*
* 优化说明:
* - 不使用静态 import() 映射表,避免增加入口文件大小
* - 通过路由配置动态获取组件的 import 函数
* - 只在实际需要预加载时才执行
*/ */
import { ref, readonly } from 'vue' import { ref, readonly } from 'vue'
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router' import type { RouteLocationNormalized, Router } from 'vue-router'
/** /**
* 组件导入函数类型 * 组件导入函数类型
@@ -11,32 +16,31 @@ import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
type ComponentImportFn = () => Promise<unknown> type ComponentImportFn = () => Promise<unknown>
/** /**
* 预加载配置类型 * 预加载邻接表:定义每个路由应该预加载哪些相邻路由
* 只存储路由路径,不存储 import 函数,避免打包问题
*/ */
interface PrefetchConfig { const PREFETCH_ADJACENCY: Record<string, string[]> = {
[path: string]: ComponentImportFn[] // Admin routes - 预加载最常访问的相邻页面
} '/admin/dashboard': ['/admin/accounts', '/admin/users'],
'/admin/accounts': ['/admin/dashboard', '/admin/users'],
/** '/admin/users': ['/admin/groups', '/admin/dashboard'],
* 路由预加载元数据扩展 '/admin/groups': ['/admin/subscriptions', '/admin/users'],
* 在路由 meta 中可以指定 prefetch 配置 '/admin/subscriptions': ['/admin/groups', '/admin/redeem'],
*/ // User routes
declare module 'vue-router' { '/dashboard': ['/keys', '/usage'],
interface RouteMeta { '/keys': ['/dashboard', '/usage'],
/** 需要预加载的路由路径列表 */ '/usage': ['/keys', '/redeem'],
prefetch?: string[] '/redeem': ['/usage', '/profile'],
} '/profile': ['/dashboard', '/keys']
} }
/** /**
* requestIdleCallback 的返回类型 * requestIdleCallback 的返回类型
* 在支持的浏览器中返回 numberpolyfill 中使用 ReturnType<typeof setTimeout>
*/ */
type IdleCallbackHandle = number | ReturnType<typeof setTimeout> type IdleCallbackHandle = number | ReturnType<typeof setTimeout>
/** /**
* requestIdleCallback polyfill * requestIdleCallback polyfill (Safari < 15)
* Safari < 15 不支持 requestIdleCallback
*/ */
const scheduleIdleCallback = ( const scheduleIdleCallback = (
callback: IdleRequestCallback, callback: IdleRequestCallback,
@@ -45,12 +49,8 @@ const scheduleIdleCallback = (
if (typeof window.requestIdleCallback === 'function') { if (typeof window.requestIdleCallback === 'function') {
return window.requestIdleCallback(callback, options) return window.requestIdleCallback(callback, options)
} }
// Fallback: 使用 setTimeout 模拟,延迟 1 秒执行
return setTimeout(() => { return setTimeout(() => {
callback({ callback({ didTimeout: false, timeRemaining: () => 50 })
didTimeout: false,
timeRemaining: () => 50
})
}, 1000) }, 1000)
} }
@@ -62,169 +62,46 @@ const cancelScheduledCallback = (handle: IdleCallbackHandle): void => {
} }
} }
/**
* 从路由配置自动生成预加载映射表
* 根据路由的 meta.prefetch 配置和同级路由自动生成
*
* @param routes - 路由配置数组
* @returns 预加载映射表
*/
export function generatePrefetchMap(routes: RouteRecordRaw[]): PrefetchConfig {
const prefetchMap: PrefetchConfig = {}
const routeComponentMap = new Map<string, ComponentImportFn>()
// 第一遍:收集所有路由的组件导入函数
const collectComponents = (routeList: RouteRecordRaw[], prefix = '') => {
for (const route of routeList) {
if (route.redirect) continue
const fullPath = prefix + route.path
if (route.component && typeof route.component === 'function') {
routeComponentMap.set(fullPath, route.component as ComponentImportFn)
}
// 递归处理子路由
if (route.children) {
collectComponents(route.children, fullPath)
}
}
}
collectComponents(routes)
// 第二遍:根据 meta.prefetch 或同级路由生成预加载映射
const generateMapping = (routeList: RouteRecordRaw[], siblings: RouteRecordRaw[] = []) => {
for (let i = 0; i < routeList.length; i++) {
const route = routeList[i]
if (route.redirect || !route.component) continue
const path = route.path
const prefetchPaths: string[] = []
// 优先使用 meta.prefetch 配置
if (route.meta?.prefetch && Array.isArray(route.meta.prefetch)) {
prefetchPaths.push(...route.meta.prefetch)
} else {
// 自动预加载相邻的同级路由(前后各一个)
const siblingRoutes = siblings.length > 0 ? siblings : routeList
const currentIndex = siblingRoutes.findIndex((r) => r.path === path)
if (currentIndex > 0) {
const prev = siblingRoutes[currentIndex - 1]
if (prev && !prev.redirect && prev.component) {
prefetchPaths.push(prev.path)
}
}
if (currentIndex < siblingRoutes.length - 1) {
const next = siblingRoutes[currentIndex + 1]
if (next && !next.redirect && next.component) {
prefetchPaths.push(next.path)
}
}
}
// 转换为组件导入函数
const importFns: ComponentImportFn[] = []
for (const prefetchPath of prefetchPaths) {
const importFn = routeComponentMap.get(prefetchPath)
if (importFn) {
importFns.push(importFn)
}
}
if (importFns.length > 0) {
prefetchMap[path] = importFns
}
// 递归处理子路由
if (route.children) {
generateMapping(route.children, route.children)
}
}
}
// 分别处理用户路由和管理员路由
const userRoutes = routes.filter(
(r) => !r.path.startsWith('/admin') && !r.path.startsWith('/auth') && !r.path.startsWith('/setup')
)
const adminRoutes = routes.filter((r) => r.path.startsWith('/admin'))
generateMapping(userRoutes, userRoutes)
generateMapping(adminRoutes, adminRoutes)
return prefetchMap
}
/**
* 默认预加载映射表(手动配置,优先级更高)
* 可以覆盖自动生成的映射
*/
const defaultAdminPrefetchMap: PrefetchConfig = {
'/admin/dashboard': [
() => import('@/views/admin/AccountsView.vue'),
() => import('@/views/admin/UsersView.vue')
],
'/admin/accounts': [
() => import('@/views/admin/DashboardView.vue'),
() => import('@/views/admin/UsersView.vue')
],
'/admin/users': [
() => import('@/views/admin/GroupsView.vue'),
() => import('@/views/admin/DashboardView.vue')
]
}
const defaultUserPrefetchMap: PrefetchConfig = {
'/dashboard': [
() => import('@/views/user/KeysView.vue'),
() => import('@/views/user/UsageView.vue')
],
'/keys': [
() => import('@/views/user/DashboardView.vue'),
() => import('@/views/user/UsageView.vue')
],
'/usage': [
() => import('@/views/user/KeysView.vue'),
() => import('@/views/user/RedeemView.vue')
]
}
/** /**
* 路由预加载组合式函数 * 路由预加载组合式函数
* *
* @param customPrefetchMap - 自定义预加载映射表(可选) * @param router - Vue Router 实例,用于获取路由组件
*/ */
export function useRoutePrefetch(customPrefetchMap?: PrefetchConfig) { export function useRoutePrefetch(router?: Router) {
// 合并预加载映射表:自定义 > 默认管理员 > 默认用户
const prefetchMap: PrefetchConfig = {
...defaultUserPrefetchMap,
...defaultAdminPrefetchMap,
...customPrefetchMap
}
// 当前挂起的预加载任务句柄 // 当前挂起的预加载任务句柄
const pendingPrefetchHandle = ref<IdleCallbackHandle | null>(null) const pendingPrefetchHandle = ref<IdleCallbackHandle | null>(null)
// 已预加载的路由集合(避免重复预加载) // 已预加载的路由集合
const prefetchedRoutes = ref<Set<string>>(new Set()) const prefetchedRoutes = ref<Set<string>>(new Set())
/** /**
* 判断是否为管理员路由 * 从路由配置中获取组件的 import 函数
*/ */
const isAdminRoute = (path: string): boolean => { const getComponentImporter = (path: string): ComponentImportFn | null => {
return path.startsWith('/admin') if (!router) return null
const routes = router.getRoutes()
const route = routes.find((r) => r.path === path)
if (route && route.components?.default) {
const component = route.components.default
// 检查是否是懒加载组件(函数形式)
if (typeof component === 'function') {
return component as ComponentImportFn
}
}
return null
} }
/** /**
* 获取当前路由对应的预加载配置 * 获取当前路由应该预加载的路由路径列表
*/ */
const getPrefetchConfig = (route: RouteLocationNormalized): ComponentImportFn[] => { const getPrefetchPaths = (route: RouteLocationNormalized): string[] => {
return prefetchMap[route.path] || [] return PREFETCH_ADJACENCY[route.path] || []
} }
/** /**
* 执行单个组件的预加载 * 执行单个组件的预加载
* 静默处理错误,不影响页面功能
*/ */
const prefetchComponent = async (importFn: ComponentImportFn): Promise<void> => { const prefetchComponent = async (importFn: ComponentImportFn): Promise<void> => {
try { try {
@@ -249,56 +126,77 @@ export function useRoutePrefetch(customPrefetchMap?: PrefetchConfig) {
/** /**
* 触发路由预加载 * 触发路由预加载
* 在浏览器空闲时执行,超时 2 秒后强制执行
*/ */
const triggerPrefetch = (route: RouteLocationNormalized): void => { const triggerPrefetch = (route: RouteLocationNormalized): void => {
// 取消之前的预加载任务
cancelPendingPrefetch() cancelPendingPrefetch()
const prefetchList = getPrefetchConfig(route) const prefetchPaths = getPrefetchPaths(route)
if (prefetchList.length === 0) { if (prefetchPaths.length === 0) return
return
}
// 在浏览器空闲时执行预加载
pendingPrefetchHandle.value = scheduleIdleCallback( pendingPrefetchHandle.value = scheduleIdleCallback(
() => { () => {
pendingPrefetchHandle.value = null pendingPrefetchHandle.value = null
// 过滤掉已预加载的组件
const routePath = route.path const routePath = route.path
if (prefetchedRoutes.value.has(routePath)) { if (prefetchedRoutes.value.has(routePath)) return
return
// 获取需要预加载的组件 import 函数
const importFns: ComponentImportFn[] = []
for (const path of prefetchPaths) {
const importFn = getComponentImporter(path)
if (importFn) {
importFns.push(importFn)
}
} }
// 执行预加载 if (importFns.length > 0) {
Promise.all(prefetchList.map(prefetchComponent)).then(() => { Promise.all(importFns.map(prefetchComponent)).then(() => {
prefetchedRoutes.value.add(routePath) prefetchedRoutes.value.add(routePath)
}) })
}
}, },
{ timeout: 2000 } // 2 秒超时 { timeout: 2000 }
) )
} }
/** /**
* 重置预加载状态(用于测试) * 重置预加载状态
*/ */
const resetPrefetchState = (): void => { const resetPrefetchState = (): void => {
cancelPendingPrefetch() cancelPendingPrefetch()
prefetchedRoutes.value.clear() prefetchedRoutes.value.clear()
} }
/**
* 判断是否为管理员路由
*/
const isAdminRoute = (path: string): boolean => {
return path.startsWith('/admin')
}
/**
* 获取预加载配置(兼容旧 API
*/
const getPrefetchConfig = (route: RouteLocationNormalized): ComponentImportFn[] => {
const paths = getPrefetchPaths(route)
const importFns: ComponentImportFn[] = []
for (const path of paths) {
const importFn = getComponentImporter(path)
if (importFn) importFns.push(importFn)
}
return importFns
}
return { return {
prefetchedRoutes: readonly(prefetchedRoutes), prefetchedRoutes: readonly(prefetchedRoutes),
triggerPrefetch, triggerPrefetch,
cancelPendingPrefetch, cancelPendingPrefetch,
resetPrefetchState, resetPrefetchState,
// 导出用于测试
_getPrefetchConfig: getPrefetchConfig, _getPrefetchConfig: getPrefetchConfig,
_isAdminRoute: isAdminRoute _isAdminRoute: isAdminRoute
} }
} }
// 导出预加载映射表(用于测试) // 兼容旧测试的导出
export const _adminPrefetchMap = defaultAdminPrefetchMap export const _adminPrefetchMap = PREFETCH_ADJACENCY
export const _userPrefetchMap = defaultUserPrefetchMap export const _userPrefetchMap = PREFETCH_ADJACENCY

View File

@@ -330,7 +330,8 @@ let authInitialized = false
// 初始化导航加载状态和预加载 // 初始化导航加载状态和预加载
const navigationLoading = useNavigationLoadingState() const navigationLoading = useNavigationLoadingState()
const routePrefetch = useRoutePrefetch() // 延迟初始化预加载,传入 router 实例
let routePrefetch: ReturnType<typeof useRoutePrefetch> | null = null
router.beforeEach((to, _from, next) => { router.beforeEach((to, _from, next) => {
// 开始导航加载状态 // 开始导航加载状态
@@ -414,6 +415,10 @@ router.afterEach((to) => {
// 结束导航加载状态 // 结束导航加载状态
navigationLoading.endNavigation() navigationLoading.endNavigation()
// 懒初始化预加载(首次导航时创建,传入 router 实例)
if (!routePrefetch) {
routePrefetch = useRoutePrefetch(router)
}
// 触发路由预加载(在浏览器空闲时执行) // 触发路由预加载(在浏览器空闲时执行)
routePrefetch.triggerPrefetch(to) routePrefetch.triggerPrefetch(to)
}) })