Improve caching
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
} from 'vue-router'
|
||||
import type {
|
||||
NativeDirection,
|
||||
NativeEvictionReason,
|
||||
NativeGestureKind,
|
||||
NativeNavigationOptions,
|
||||
NativePlatformAdapter,
|
||||
@@ -81,6 +82,7 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
readonly activeKey
|
||||
readonly transaction
|
||||
readonly canGoBack
|
||||
readonly cacheStats
|
||||
|
||||
private readonly mutableEntries = shallowRef<NativeViewEntry[]>([])
|
||||
private readonly mutableActiveKey = ref('')
|
||||
@@ -95,14 +97,32 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
private removeAfterEach?: () => void
|
||||
private platformCleanup?: () => void
|
||||
private pendingPop?: (failure?: NavigationFailure | void) => void
|
||||
private totalEvictions = 0
|
||||
private lastEviction?: { key: string; route: string; reason: NativeEvictionReason }
|
||||
private memoryPressureCleanup?: () => void
|
||||
|
||||
constructor(options: NativeRouterOptions) {
|
||||
this.router = options.router
|
||||
this.maxInactive = options.cache?.maxInactive ?? 8
|
||||
this.maxInactive = Math.max(0, options.cache?.maxInactive ?? 4)
|
||||
this.platform = options.platform
|
||||
this.entries = computed(() => this.mutableEntries.value)
|
||||
this.activeKey = computed(() => this.mutableActiveKey.value)
|
||||
this.transaction = computed(() => this.mutableTransaction.value)
|
||||
this.cacheStats = computed(() => {
|
||||
const entries = this.mutableEntries.value
|
||||
const mounted = entries.filter((entry) => entry.mounted)
|
||||
const inactive = mounted.filter((entry) => entry.key !== this.mutableActiveKey.value)
|
||||
return {
|
||||
maxInactive: this.maxInactive,
|
||||
descriptors: entries.length,
|
||||
mounted: mounted.length,
|
||||
inactive: inactive.length,
|
||||
pinned: inactive.filter((entry) => entry.route.meta.native?.cache === 'pin').length,
|
||||
evicted: entries.filter((entry) => !entry.mounted).length,
|
||||
totalEvictions: this.totalEvictions,
|
||||
lastEviction: this.lastEviction,
|
||||
}
|
||||
})
|
||||
this.canGoBack = computed(() => {
|
||||
const transaction = this.mutableTransaction.value
|
||||
const committingForwardEntry = transaction
|
||||
@@ -141,6 +161,11 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
if (cleanup) this.platformCleanup = cleanup
|
||||
})
|
||||
}
|
||||
if (typeof window !== 'undefined') {
|
||||
const trimForPressure = () => this.trimCache({ reason: 'memory-pressure' })
|
||||
window.addEventListener('memorypressure', trimForPressure)
|
||||
this.memoryPressureCleanup = () => window.removeEventListener('memorypressure', trimForPressure)
|
||||
}
|
||||
}
|
||||
|
||||
async preload(to: RouteLocationRaw) {
|
||||
@@ -221,6 +246,7 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
target.route = route
|
||||
target.mounted = true
|
||||
target.status = 'inactive'
|
||||
target.evictionReason = undefined
|
||||
this.touchEntries()
|
||||
}
|
||||
} else {
|
||||
@@ -236,6 +262,7 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
target.status = 'preview'
|
||||
target.synthetic = false
|
||||
target.lastUsed = now()
|
||||
target.evictionReason = undefined
|
||||
this.touchEntries()
|
||||
} else {
|
||||
target = entryFor(route, 'preview')
|
||||
@@ -309,7 +336,7 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
if (!this.isCurrent(current.id)) return !failed
|
||||
if (failed) {
|
||||
await this.animateProgress(0, 0)
|
||||
if (this.isCurrent(current.id)) this.finalizeCancelled(current)
|
||||
if (this.isCurrent(current.id)) this.finalizeCancelled(current, true)
|
||||
return false
|
||||
}
|
||||
this.finalizeCommitted(current)
|
||||
@@ -326,6 +353,16 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
this.finalizeCancelled(current)
|
||||
}
|
||||
|
||||
trimCache(options: { includePinned?: boolean; reason?: NativeEvictionReason } = {}) {
|
||||
const reason = options.reason ?? 'trimmed'
|
||||
for (const entry of this.mutableEntries.value) {
|
||||
if (entry.key === this.mutableActiveKey.value || !entry.mounted || entry.status !== 'inactive') continue
|
||||
if (!options.includePinned && entry.route.meta.native?.cache === 'pin') continue
|
||||
this.evictEntry(entry, reason)
|
||||
}
|
||||
this.touchEntries()
|
||||
}
|
||||
|
||||
registerPresentation(definition: NativePresentationDefinition) {
|
||||
this.presentations.set(definition.name, definition)
|
||||
}
|
||||
@@ -350,6 +387,7 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
dispose() {
|
||||
this.removeAfterEach?.()
|
||||
this.platformCleanup?.()
|
||||
this.memoryPressureCleanup?.()
|
||||
}
|
||||
|
||||
private activeEntry() {
|
||||
@@ -414,15 +452,17 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
velocity: 0,
|
||||
phase: 'settling',
|
||||
}
|
||||
if (failed) this.finalizeCancelled(transaction)
|
||||
if (failed) this.finalizeCancelled(transaction, true)
|
||||
else this.finalizeCommitted(transaction)
|
||||
}
|
||||
|
||||
private finalizeCancelled(transaction: NativeTransaction) {
|
||||
private finalizeCancelled(transaction: NativeTransaction, evictTarget = false) {
|
||||
if (!this.isCurrent(transaction.id)) return
|
||||
this.removePreview(transaction.toKey)
|
||||
this.clearTransaction()
|
||||
if (evictTarget) this.discardTarget(transaction.toKey, 'navigation-rejected')
|
||||
else this.removePreview(transaction.toKey)
|
||||
this.markStatuses()
|
||||
this.enforceCache()
|
||||
}
|
||||
|
||||
private finalizeCommitted(transaction: NativeTransaction) {
|
||||
@@ -430,9 +470,10 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
const target = this.entryByKey(transaction.toKey)
|
||||
if (target && this.router.currentRoute.value.fullPath !== target.route.fullPath) {
|
||||
// A redirect is already authoritative; discard only the stale preview.
|
||||
this.removePreview(transaction.toKey)
|
||||
this.clearTransaction()
|
||||
this.discardTarget(transaction.toKey, 'navigation-rejected')
|
||||
this.markStatuses()
|
||||
this.enforceCache()
|
||||
return
|
||||
}
|
||||
if (target) {
|
||||
@@ -486,11 +527,13 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
target.status = 'active'
|
||||
target.committed = true
|
||||
target.lastUsed = now()
|
||||
target.evictionReason = undefined
|
||||
this.touchEntries()
|
||||
}
|
||||
this.mutableActiveKey.value = target.key
|
||||
this.acceptHistory(target, transaction)
|
||||
this.markStatuses()
|
||||
if (!transaction) this.enforceCache()
|
||||
}
|
||||
|
||||
private acceptHistory(target: NativeViewEntry, transaction: NativeTransaction | null) {
|
||||
@@ -542,16 +585,49 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
private discardTarget(key: string, reason: NativeEvictionReason) {
|
||||
const entry = this.entryByKey(key)
|
||||
if (!entry || entry.key === this.mutableActiveKey.value) return
|
||||
if (!entry.committed || entry.synthetic) {
|
||||
this.mutableEntries.value = this.mutableEntries.value.filter((candidate) => candidate.key !== key)
|
||||
return
|
||||
}
|
||||
this.evictEntry(entry, reason)
|
||||
}
|
||||
|
||||
private evictEntry(entry: NativeViewEntry, reason: NativeEvictionReason) {
|
||||
if (!entry.mounted || entry.key === this.mutableActiveKey.value) return
|
||||
entry.mounted = false
|
||||
entry.status = 'evicted'
|
||||
entry.evictionReason = reason
|
||||
this.totalEvictions += 1
|
||||
this.lastEviction = { key: entry.key, route: entry.route.fullPath, reason }
|
||||
}
|
||||
|
||||
private shouldRetainInactive(entry: NativeViewEntry) {
|
||||
const policy = entry.route.meta.native?.cache
|
||||
if (policy === false) return false
|
||||
if (policy === 'pin') return true
|
||||
if (this.mutableHistoryKeys.value.includes(entry.key)) return true
|
||||
return entry.route.meta.native?.siblingHistory === 'replace'
|
||||
}
|
||||
|
||||
private enforceCache() {
|
||||
const inactive = this.mutableEntries.value
|
||||
.filter((entry) => entry.key !== this.mutableActiveKey.value && entry.mounted && entry.status === 'inactive')
|
||||
.sort((a, b) => b.lastUsed - a.lastUsed)
|
||||
for (const entry of inactive.slice(this.maxInactive)) {
|
||||
if (entry.route.meta.native?.cache === false || inactive.length > this.maxInactive) {
|
||||
entry.mounted = false
|
||||
entry.status = 'evicted'
|
||||
for (const entry of inactive) {
|
||||
if (!this.shouldRetainInactive(entry)) {
|
||||
const reason: NativeEvictionReason = entry.route.meta.native?.cache === false
|
||||
? 'cache-disabled'
|
||||
: 'popped'
|
||||
this.evictEntry(entry, reason)
|
||||
}
|
||||
}
|
||||
const retained = inactive.filter((entry) => entry.mounted && entry.route.meta.native?.cache !== 'pin')
|
||||
for (const entry of retained.slice(this.maxInactive)) {
|
||||
this.evictEntry(entry, 'cache-limit')
|
||||
}
|
||||
this.touchEntries()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user