Add profiler
This commit is contained in:
@@ -13,9 +13,12 @@ import {
|
||||
type NavigationFailure,
|
||||
type RouteLocationNormalizedLoaded,
|
||||
type RouteLocationRaw,
|
||||
type RouteLocationResolved,
|
||||
} from 'vue-router'
|
||||
import type {
|
||||
NativeDirection,
|
||||
NativeDiagnosticEvent,
|
||||
NativeDiagnosticEventType,
|
||||
NativeEvictionReason,
|
||||
NativeGestureKind,
|
||||
NativeNavigationOptions,
|
||||
@@ -36,6 +39,12 @@ function now() {
|
||||
return typeof performance === 'undefined' ? Date.now() : performance.now()
|
||||
}
|
||||
|
||||
function diagnosticRoute(route: RouteLocationNormalizedLoaded | RouteLocationResolved) {
|
||||
return route.name != null
|
||||
? String(route.name)
|
||||
: route.matched.at(-1)?.path ?? route.path
|
||||
}
|
||||
|
||||
function entryFor(route: RouteLocationNormalizedLoaded, status: NativeViewEntry['status'], synthetic = false): NativeViewEntry {
|
||||
return {
|
||||
key: `${route.fullPath}::${++entrySequence}`,
|
||||
@@ -101,6 +110,7 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
private totalEvictions = 0
|
||||
private lastEviction?: { key: string; route: string; reason: NativeEvictionReason }
|
||||
private memoryPressureCleanup?: () => void
|
||||
private readonly diagnosticListeners = new Set<(event: NativeDiagnosticEvent) => void>()
|
||||
|
||||
constructor(options: NativeRouterOptions) {
|
||||
this.router = options.router
|
||||
@@ -171,7 +181,7 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
|
||||
async preload(to: RouteLocationRaw) {
|
||||
const resolved = this.router.resolve(to)
|
||||
return await loadRouteLocation(resolved)
|
||||
return await this.loadResolvedRoute(resolved)
|
||||
}
|
||||
|
||||
async push(to: RouteLocationRaw, options: NativeNavigationOptions = {}) {
|
||||
@@ -237,14 +247,14 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
const parent = from.route.meta.native?.parent
|
||||
if (!parent) return null
|
||||
const parentLocation = typeof parent === 'function' ? parent(from.route) : parent
|
||||
const route = await this.preload(parentLocation)
|
||||
const route = await this.loadResolvedRoute(this.router.resolve(parentLocation), attempt)
|
||||
if (attempt !== this.beginAttemptSequence || this.mutableTransaction.value) return null
|
||||
target = entryFor(route, 'preview', true)
|
||||
synthetic = true
|
||||
needsMount = true
|
||||
this.mutableEntries.value = [...this.mutableEntries.value, target]
|
||||
} else if (!target.mounted) {
|
||||
const route = await this.preload(target.route.fullPath)
|
||||
const route = await this.loadResolvedRoute(this.router.resolve(target.route.fullPath), attempt)
|
||||
if (attempt !== this.beginAttemptSequence || this.mutableTransaction.value) return null
|
||||
target.route = route
|
||||
target.mounted = true
|
||||
@@ -257,7 +267,7 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
if (!to) return null
|
||||
const resolved = this.router.resolve(to)
|
||||
if (resolved.fullPath === from.route.fullPath) return null
|
||||
const route = await loadRouteLocation(resolved)
|
||||
const route = await this.loadResolvedRoute(resolved, attempt)
|
||||
if (attempt !== this.beginAttemptSequence || this.mutableTransaction.value) return null
|
||||
const replace = options.replace ?? (route.meta.native?.siblingHistory === 'replace')
|
||||
target = replace ? this.findHistoryEntry(route.fullPath) : undefined
|
||||
@@ -307,9 +317,32 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
}
|
||||
if (attempt !== this.beginAttemptSequence || this.mutableTransaction.value) return null
|
||||
this.mutableTransaction.value = transaction
|
||||
this.emitDiagnostic('transaction-start', {
|
||||
attempt,
|
||||
transactionId: transaction.id,
|
||||
route: diagnosticRoute(target.route),
|
||||
details: {
|
||||
kind: transaction.kind,
|
||||
direction: transaction.direction,
|
||||
presentation: transaction.presentation,
|
||||
cold: needsMount,
|
||||
},
|
||||
})
|
||||
if (synthetic) target.synthetic = true
|
||||
if (needsMount) {
|
||||
const prepareStarted = now()
|
||||
this.emitDiagnostic('view-prepare-start', {
|
||||
attempt,
|
||||
transactionId: transaction.id,
|
||||
route: diagnosticRoute(target.route),
|
||||
})
|
||||
await this.prepareMountedView()
|
||||
this.emitDiagnostic('view-prepare-end', {
|
||||
attempt,
|
||||
transactionId: transaction.id,
|
||||
route: diagnosticRoute(target.route),
|
||||
duration: now() - prepareStarted,
|
||||
})
|
||||
if (!this.isCurrent(transaction.id)) return null
|
||||
}
|
||||
return transaction.id
|
||||
@@ -335,6 +368,10 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
}
|
||||
|
||||
this.mutableTransaction.value = { ...current, phase: 'committing' }
|
||||
this.emitDiagnostic('commit-start', {
|
||||
transactionId: current.id,
|
||||
route: this.entryByKey(current.toKey) ? diagnosticRoute(this.entryByKey(current.toKey)!.route) : undefined,
|
||||
})
|
||||
void this.platform?.haptic?.('commit')
|
||||
const navigation = this.commitRoute(current)
|
||||
this.pendingNavigations.set(current.id, navigation)
|
||||
@@ -390,6 +427,11 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
this.touchEntries()
|
||||
}
|
||||
|
||||
onDiagnostic(listener: (event: NativeDiagnosticEvent) => void) {
|
||||
this.diagnosticListeners.add(listener)
|
||||
return () => this.diagnosticListeners.delete(listener)
|
||||
}
|
||||
|
||||
registerPresentation(definition: NativePresentationDefinition) {
|
||||
this.presentations.set(definition.name, definition)
|
||||
}
|
||||
@@ -485,6 +527,12 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
|
||||
private finalizeCancelled(transaction: NativeTransaction, evictTarget = false) {
|
||||
if (!this.isCurrent(transaction.id)) return
|
||||
const target = this.entryByKey(transaction.toKey)
|
||||
this.emitDiagnostic('transaction-end', {
|
||||
transactionId: transaction.id,
|
||||
route: target ? diagnosticRoute(target.route) : undefined,
|
||||
details: { outcome: evictTarget ? 'rejected' : 'cancelled' },
|
||||
})
|
||||
this.clearTransaction()
|
||||
if (evictTarget) this.discardTarget(transaction.toKey, 'navigation-rejected')
|
||||
else this.removePreview(transaction.toKey)
|
||||
@@ -497,6 +545,11 @@ 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.emitDiagnostic('transaction-end', {
|
||||
transactionId: transaction.id,
|
||||
route: diagnosticRoute(target.route),
|
||||
details: { outcome: 'redirected' },
|
||||
})
|
||||
this.clearTransaction()
|
||||
this.discardTarget(transaction.toKey, 'navigation-rejected')
|
||||
this.markStatuses()
|
||||
@@ -510,6 +563,11 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
target.lastUsed = now()
|
||||
this.mutableActiveKey.value = target.key
|
||||
}
|
||||
this.emitDiagnostic('transaction-end', {
|
||||
transactionId: transaction.id,
|
||||
route: target ? diagnosticRoute(target.route) : undefined,
|
||||
details: { outcome: 'committed' },
|
||||
})
|
||||
this.clearTransaction()
|
||||
this.markStatuses()
|
||||
this.enforceCache()
|
||||
@@ -643,6 +701,10 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
entry.evictionReason = reason
|
||||
this.totalEvictions += 1
|
||||
this.lastEviction = { key: entry.key, route: entry.route.fullPath, reason }
|
||||
this.emitDiagnostic('view-evicted', {
|
||||
route: diagnosticRoute(entry.route),
|
||||
details: { reason },
|
||||
})
|
||||
}
|
||||
|
||||
private shouldRetainInactive(entry: NativeViewEntry) {
|
||||
@@ -679,6 +741,30 @@ class NativeRouterRuntimeImpl implements NativeRouterRuntime {
|
||||
await new Promise<void>((resolve) => window.requestAnimationFrame(() => resolve()))
|
||||
}
|
||||
|
||||
private async loadResolvedRoute(route: RouteLocationResolved, attempt?: number) {
|
||||
const started = now()
|
||||
const label = diagnosticRoute(route)
|
||||
this.emitDiagnostic('route-load-start', { attempt, route: label })
|
||||
try {
|
||||
return await loadRouteLocation(route)
|
||||
} finally {
|
||||
this.emitDiagnostic('route-load-end', {
|
||||
attempt,
|
||||
route: label,
|
||||
duration: now() - started,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private emitDiagnostic(
|
||||
type: NativeDiagnosticEventType,
|
||||
event: Omit<NativeDiagnosticEvent, 'type' | 'timestamp'> = {},
|
||||
) {
|
||||
if (!this.diagnosticListeners.size) return
|
||||
const diagnostic = { type, timestamp: now(), ...event }
|
||||
for (const listener of this.diagnosticListeners) listener(diagnostic)
|
||||
}
|
||||
|
||||
private animateProgress(target: number, initialVelocity: number) {
|
||||
const transaction = this.mutableTransaction.value
|
||||
if (!transaction) return Promise.resolve()
|
||||
|
||||
Reference in New Issue
Block a user