2023-07-05 10:39:39 +00:00
|
|
|
import { Suspense, Transition, defineComponent, h, inject, nextTick, ref } from 'vue'
|
2023-04-07 16:02:47 +00:00
|
|
|
import type { KeepAliveProps, TransitionProps, VNode } from 'vue'
|
2023-05-09 17:08:07 +00:00
|
|
|
import { RouterView } from '#vue-router'
|
2022-10-19 12:43:03 +00:00
|
|
|
import { defu } from 'defu'
|
2023-07-05 10:39:39 +00:00
|
|
|
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from '#vue-router'
|
2022-01-25 14:32:09 +00:00
|
|
|
|
2022-12-11 21:44:52 +00:00
|
|
|
import type { RouterViewSlotProps } from './utils'
|
|
|
|
import { generateRouteKey, wrapInKeepAlive } from './utils'
|
2023-07-05 10:39:39 +00:00
|
|
|
import { RouteProvider } from '#app/components/route-provider'
|
2023-02-09 06:26:41 +00:00
|
|
|
import { useNuxtApp } from '#app/nuxt'
|
2022-03-14 10:47:24 +00:00
|
|
|
import { _wrapIf } from '#app/components/utils'
|
2023-07-05 10:39:39 +00:00
|
|
|
import { LayoutMetaSymbol, PageRouteSymbol } from '#app/components/injections'
|
2023-04-14 12:53:21 +00:00
|
|
|
// @ts-expect-error virtual file
|
2023-04-07 16:02:47 +00:00
|
|
|
import { appKeepalive as defaultKeepaliveConfig, appPageTransition as defaultPageTransition } from '#build/nuxt.config.mjs'
|
2022-01-25 14:32:09 +00:00
|
|
|
|
|
|
|
export default defineComponent({
|
|
|
|
name: 'NuxtPage',
|
2022-06-03 14:01:46 +00:00
|
|
|
inheritAttrs: false,
|
2022-02-07 11:32:04 +00:00
|
|
|
props: {
|
2022-06-03 14:01:46 +00:00
|
|
|
name: {
|
|
|
|
type: String
|
|
|
|
},
|
2022-09-14 10:34:16 +00:00
|
|
|
transition: {
|
|
|
|
type: [Boolean, Object] as any as () => boolean | TransitionProps,
|
|
|
|
default: undefined
|
|
|
|
},
|
|
|
|
keepalive: {
|
|
|
|
type: [Boolean, Object] as any as () => boolean | KeepAliveProps,
|
|
|
|
default: undefined
|
|
|
|
},
|
2022-06-03 14:01:46 +00:00
|
|
|
route: {
|
|
|
|
type: Object as () => RouteLocationNormalized
|
|
|
|
},
|
2022-02-07 11:32:04 +00:00
|
|
|
pageKey: {
|
|
|
|
type: [Function, String] as unknown as () => string | ((route: RouteLocationNormalizedLoaded) => string),
|
|
|
|
default: null
|
|
|
|
}
|
|
|
|
},
|
2023-06-10 22:13:33 +00:00
|
|
|
setup (props, { attrs, expose }) {
|
2022-01-25 14:32:09 +00:00
|
|
|
const nuxtApp = useNuxtApp()
|
2023-06-10 22:13:33 +00:00
|
|
|
const pageRef = ref()
|
2023-07-05 10:39:39 +00:00
|
|
|
const forkRoute = inject(PageRouteSymbol, null)
|
2023-06-10 22:13:33 +00:00
|
|
|
|
|
|
|
expose({ pageRef })
|
|
|
|
|
2023-06-23 10:02:01 +00:00
|
|
|
const _layoutMeta = inject(LayoutMetaSymbol, null)
|
|
|
|
let vnode: VNode
|
|
|
|
|
2022-01-25 14:32:09 +00:00
|
|
|
return () => {
|
2022-06-03 14:01:46 +00:00
|
|
|
return h(RouterView, { name: props.name, route: props.route, ...attrs }, {
|
2022-08-02 09:58:03 +00:00
|
|
|
default: (routeProps: RouterViewSlotProps) => {
|
2023-07-05 10:39:39 +00:00
|
|
|
const isRenderingNewRouteInOldFork = process.client && haveParentRoutesRendered(forkRoute, routeProps.route, routeProps.Component)
|
|
|
|
const hasSameChildren = process.client && forkRoute && forkRoute.matched.length === routeProps.route.matched.length
|
|
|
|
|
|
|
|
if (!routeProps.Component) {
|
|
|
|
// If we're rendering a `<NuxtPage>` child route on navigation to a route which lacks a child page
|
|
|
|
// we'll render the old vnode until the new route finishes resolving
|
|
|
|
if (process.client && vnode && !hasSameChildren) {
|
|
|
|
return vnode
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2022-08-02 09:58:03 +00:00
|
|
|
|
2023-06-23 10:02:01 +00:00
|
|
|
// Return old vnode if we are rendering _new_ page suspense fork in _old_ layout suspense fork
|
2023-07-05 10:39:39 +00:00
|
|
|
if (process.client && vnode && _layoutMeta && !_layoutMeta.isCurrent(routeProps.route)) {
|
2023-06-23 10:02:01 +00:00
|
|
|
return vnode
|
|
|
|
}
|
|
|
|
|
2023-07-05 10:39:39 +00:00
|
|
|
if (process.client && isRenderingNewRouteInOldFork && forkRoute && (!_layoutMeta || _layoutMeta?.isCurrent(forkRoute))) {
|
|
|
|
// if leaving a route with an existing child route, render the old vnode
|
|
|
|
if (hasSameChildren) {
|
|
|
|
return vnode
|
|
|
|
}
|
|
|
|
// If _leaving_ null child route, return null vnode
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2022-11-21 13:03:22 +00:00
|
|
|
const key = generateRouteKey(routeProps, props.pageKey)
|
2022-10-08 14:18:57 +00:00
|
|
|
const done = nuxtApp.deferHydration()
|
|
|
|
|
2022-10-19 12:43:03 +00:00
|
|
|
const hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition)
|
|
|
|
const transitionProps = hasTransition && _mergeTransitionProps([
|
|
|
|
props.transition,
|
|
|
|
routeProps.route.meta.pageTransition,
|
|
|
|
defaultPageTransition,
|
|
|
|
{ onAfterLeave: () => { nuxtApp.callHook('page:transition:finish', routeProps.Component) } }
|
|
|
|
].filter(Boolean))
|
|
|
|
|
2023-06-23 10:02:01 +00:00
|
|
|
vnode = _wrapIf(Transition, hasTransition && transitionProps,
|
2022-10-08 14:18:57 +00:00
|
|
|
wrapInKeepAlive(props.keepalive ?? routeProps.route.meta.keepalive ?? (defaultKeepaliveConfig as KeepAliveProps), h(Suspense, {
|
2023-05-11 17:57:18 +00:00
|
|
|
suspensible: true,
|
2022-10-08 14:18:57 +00:00
|
|
|
onPending: () => nuxtApp.callHook('page:start', routeProps.Component),
|
2022-10-19 12:43:03 +00:00
|
|
|
onResolve: () => { nextTick(() => nuxtApp.callHook('page:finish', routeProps.Component).finally(done)) }
|
2023-07-05 10:39:39 +00:00
|
|
|
}, {
|
|
|
|
// @ts-expect-error seems to be an issue in vue types
|
|
|
|
default: () => h(RouteProvider, {
|
|
|
|
key,
|
|
|
|
vnode: routeProps.Component,
|
|
|
|
route: routeProps.route,
|
|
|
|
renderKey: key,
|
|
|
|
trackRootNodes: hasTransition,
|
|
|
|
vnodeRef: pageRef
|
|
|
|
})
|
|
|
|
})
|
2022-08-02 09:58:03 +00:00
|
|
|
)).default()
|
2023-06-23 10:02:01 +00:00
|
|
|
|
|
|
|
return vnode
|
2022-08-02 09:58:03 +00:00
|
|
|
}
|
2022-01-25 14:32:09 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-01-25 08:44:59 +00:00
|
|
|
})
|
2022-01-25 14:32:09 +00:00
|
|
|
|
2022-10-19 12:43:03 +00:00
|
|
|
function _toArray (val: any) {
|
|
|
|
return Array.isArray(val) ? val : (val ? [val] : [])
|
|
|
|
}
|
|
|
|
|
|
|
|
function _mergeTransitionProps (routeProps: TransitionProps[]): TransitionProps {
|
|
|
|
const _props: TransitionProps[] = routeProps.map(prop => ({
|
|
|
|
...prop,
|
|
|
|
onAfterLeave: _toArray(prop.onAfterLeave)
|
|
|
|
}))
|
2023-04-14 12:53:21 +00:00
|
|
|
return defu(..._props as [TransitionProps, TransitionProps])
|
2022-10-19 12:43:03 +00:00
|
|
|
}
|
|
|
|
|
2023-07-05 10:39:39 +00:00
|
|
|
function haveParentRoutesRendered (fork: RouteLocationNormalizedLoaded | null, newRoute: RouteLocationNormalizedLoaded, Component?: VNode) {
|
|
|
|
if (!fork) { return false }
|
2022-08-04 11:30:18 +00:00
|
|
|
|
2023-07-05 10:39:39 +00:00
|
|
|
const index = newRoute.matched.findIndex(m => m.components?.default === Component?.type)
|
|
|
|
if (!index || index === -1) { return false }
|
2022-08-23 10:25:48 +00:00
|
|
|
|
2023-07-05 10:39:39 +00:00
|
|
|
// we only care whether the parent route components have had to rerender
|
|
|
|
return newRoute.matched.slice(0, index)
|
|
|
|
.some(
|
|
|
|
(c, i) => c.components?.default !== fork.matched[i]?.components?.default) ||
|
|
|
|
(Component && generateRouteKey({ route: newRoute, Component }) !== generateRouteKey({ route: fork, Component }))
|
|
|
|
}
|