mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-10 10:48:10 +00:00
fix(nuxt): prevent keepalive
cache reset (#30807)
This commit is contained in:
parent
4be52d341f
commit
5e7d4938cd
@ -1,5 +1,6 @@
|
||||
// For pnpm typecheck:docs to generate correct types
|
||||
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { addPluginTemplate, addRouteMiddleware } from 'nuxt/kit'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
@ -17,6 +18,9 @@ export default defineNuxtConfig({
|
||||
},
|
||||
],
|
||||
pages: process.env.DOCS_TYPECHECK === 'true',
|
||||
dir: {
|
||||
app: fileURLToPath(new URL('./test/runtime/app', import.meta.url)),
|
||||
},
|
||||
typescript: {
|
||||
shim: process.env.DOCS_TYPECHECK === 'true',
|
||||
hoist: ['@vitejs/plugin-vue', 'vue-router'],
|
||||
|
@ -3,7 +3,8 @@ import type { Ref, VNode } from 'vue'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { PageRouteSymbol } from './injections'
|
||||
|
||||
export const RouteProvider = defineComponent({
|
||||
export const defineRouteProvider = (name = 'RouteProvider') => defineComponent({
|
||||
name,
|
||||
props: {
|
||||
vnode: {
|
||||
type: Object as () => VNode,
|
||||
@ -55,3 +56,5 @@ export const RouteProvider = defineComponent({
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const RouteProvider = defineRouteProvider()
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Fragment, Suspense, defineComponent, h, inject, nextTick, ref, watch } from 'vue'
|
||||
import type { AllowedComponentProps, ComponentCustomProps, ComponentPublicInstance, KeepAliveProps, TransitionProps, VNode, VNodeProps } from 'vue'
|
||||
import type { AllowedComponentProps, Component, ComponentCustomProps, ComponentPublicInstance, KeepAliveProps, Slot, TransitionProps, VNode, VNodeProps } from 'vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import { defu } from 'defu'
|
||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouterViewProps } from 'vue-router'
|
||||
|
||||
import { generateRouteKey, toArray, wrapInKeepAlive } from './utils'
|
||||
import type { RouterViewSlotProps } from './utils'
|
||||
import { RouteProvider } from '#app/components/route-provider'
|
||||
import { RouteProvider, defineRouteProvider } from '#app/components/route-provider'
|
||||
import { useNuxtApp } from '#app/nuxt'
|
||||
import { useRouter } from '#app/composables/router'
|
||||
import { _wrapInTransition } from '#app/components/utils'
|
||||
@ -83,6 +83,9 @@ export default defineComponent({
|
||||
nuxtApp._isNuxtPageUsed = true
|
||||
}
|
||||
let pageLoadingEndHookAlreadyCalled = false
|
||||
|
||||
const routerProviderLookup = new WeakMap<Component, ReturnType<typeof defineRouteProvider> | undefined>()
|
||||
|
||||
return () => {
|
||||
return h(RouterView, { name: props.name, route: props.route, ...attrs }, {
|
||||
default: (routeProps: RouterViewSlotProps) => {
|
||||
@ -128,7 +131,7 @@ export default defineComponent({
|
||||
default: () => {
|
||||
const providerVNode = h(RouteProvider, {
|
||||
key: key || undefined,
|
||||
vnode: slots.default ? h(Fragment, undefined, slots.default(routeProps)) : routeProps.Component,
|
||||
vnode: slots.default ? normalizeSlot(slots.default, routeProps) : routeProps.Component,
|
||||
route: routeProps.route,
|
||||
renderKey: key || undefined,
|
||||
vnodeRef: pageRef,
|
||||
@ -141,7 +144,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// Client side rendering
|
||||
|
||||
const hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition)
|
||||
const transitionProps = hasTransition && _mergeTransitionProps([
|
||||
props.transition,
|
||||
@ -165,18 +167,28 @@ export default defineComponent({
|
||||
},
|
||||
}, {
|
||||
default: () => {
|
||||
const providerVNode = h(RouteProvider, {
|
||||
const routeProviderProps = {
|
||||
key: key || undefined,
|
||||
vnode: slots.default ? h(Fragment, undefined, slots.default(routeProps)) : routeProps.Component,
|
||||
vnode: slots.default ? normalizeSlot(slots.default, routeProps) : routeProps.Component,
|
||||
route: routeProps.route,
|
||||
renderKey: key || undefined,
|
||||
trackRootNodes: hasTransition,
|
||||
vnodeRef: pageRef,
|
||||
})
|
||||
if (keepaliveConfig) {
|
||||
(providerVNode.type as any).name = (routeProps.Component.type as any).name || (routeProps.Component.type as any).__name || 'RouteProvider'
|
||||
}
|
||||
return providerVNode
|
||||
|
||||
if (!keepaliveConfig) {
|
||||
return h(RouteProvider, routeProviderProps)
|
||||
}
|
||||
|
||||
const routerComponentType = routeProps.Component.type as any
|
||||
let PageRouteProvider = routerProviderLookup.get(routerComponentType)
|
||||
|
||||
if (!PageRouteProvider) {
|
||||
PageRouteProvider = defineRouteProvider(routerComponentType.name || routerComponentType.__name)
|
||||
routerProviderLookup.set(routerComponentType, PageRouteProvider)
|
||||
}
|
||||
|
||||
return h(PageRouteProvider, routeProviderProps)
|
||||
},
|
||||
}),
|
||||
)).default()
|
||||
@ -232,3 +244,8 @@ function hasChildrenRoutes (fork: RouteLocationNormalizedLoaded | null, newRoute
|
||||
const index = newRoute.matched.findIndex(m => m.components?.default === Component?.type)
|
||||
return index < newRoute.matched.length - 1
|
||||
}
|
||||
|
||||
function normalizeSlot (slot: Slot, data: RouterViewSlotProps) {
|
||||
const slotContent = slot(data)
|
||||
return slotContent.length === 1 ? h(slotContent[0]!) : h(Fragment, undefined, slotContent)
|
||||
}
|
||||
|
@ -661,14 +661,13 @@ describe('routing utilities: `encodeURL`', () => {
|
||||
})
|
||||
|
||||
describe('routing utilities: `useRoute`', () => {
|
||||
it('should show provide a mock route', () => {
|
||||
it('should provide a route', () => {
|
||||
expect(useRoute()).toMatchObject({
|
||||
fullPath: '/',
|
||||
hash: '',
|
||||
href: '/',
|
||||
matched: [],
|
||||
matched: expect.arrayContaining([]),
|
||||
meta: {},
|
||||
name: undefined,
|
||||
name: 'catchall',
|
||||
params: {},
|
||||
path: '/',
|
||||
query: {},
|
||||
|
97
test/nuxt/nuxt-page.test.ts
Normal file
97
test/nuxt/nuxt-page.test.ts
Normal file
@ -0,0 +1,97 @@
|
||||
/// <reference path="../fixtures/basic/.nuxt/nuxt.d.ts" />
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { mountSuspended } from '@nuxt/test-utils/runtime'
|
||||
import { NuxtLayout, NuxtPage } from '#components'
|
||||
|
||||
describe('NuxtPage should work with keepalive options', () => {
|
||||
let visits = 0
|
||||
const router = useRouter()
|
||||
beforeEach(() => {
|
||||
visits = 0
|
||||
router.addRoute({
|
||||
name: 'home',
|
||||
path: '/home',
|
||||
component: defineComponent({
|
||||
name: 'home',
|
||||
setup () {
|
||||
visits++
|
||||
return () => h('div', 'home')
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
afterEach(() => {
|
||||
router.removeRoute('home')
|
||||
})
|
||||
// include/exclude/boolean
|
||||
it('should reload setup every time a page is visited, without keepalive', async () => {
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(2)
|
||||
el.unmount()
|
||||
})
|
||||
|
||||
it('should not remount a page when keepalive is enabled', async () => {
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage, { keepalive: true }) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(1)
|
||||
el.unmount()
|
||||
})
|
||||
|
||||
it('should not remount a page when keepalive is granularly enabled (with include)', async () => {
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage, { keepalive: { include: ['home'] } }) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(1)
|
||||
el.unmount()
|
||||
})
|
||||
|
||||
it('should not remount a page when keepalive is granularly enabled (with exclude)', async () => {
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage, { keepalive: { exclude: ['catchall'] } }) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(1)
|
||||
el.unmount()
|
||||
})
|
||||
|
||||
it('should not remount a page when keepalive options are modified', async () => {
|
||||
const pages = ref('home')
|
||||
const el = await mountSuspended({
|
||||
setup () {
|
||||
return () => h(NuxtLayout, {}, { default: () => h(NuxtPage, { keepalive: { include: pages.value } }) })
|
||||
},
|
||||
})
|
||||
await navigateTo('/home')
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
pages.value = 'home,catchall'
|
||||
await navigateTo('/')
|
||||
await navigateTo('/home')
|
||||
expect(visits).toBe(1)
|
||||
el.unmount()
|
||||
})
|
||||
})
|
17
test/runtime/app/router.options.ts
Normal file
17
test/runtime/app/router.options.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import type { RouterOptions } from 'nuxt/schema'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default <RouterOptions> {
|
||||
routes (_routes) {
|
||||
return [
|
||||
{
|
||||
name: 'catchall',
|
||||
path: '/:catchAll(.*)*',
|
||||
component: defineComponent({
|
||||
name: 'catchall',
|
||||
setup: () => () => ({}),
|
||||
}),
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
@ -13,6 +13,7 @@ export default defineVitestConfig({
|
||||
environmentOptions: {
|
||||
nuxt: {
|
||||
overrides: {
|
||||
pages: true,
|
||||
runtimeConfig: {
|
||||
app: {
|
||||
buildId: 'override',
|
||||
|
Loading…
Reference in New Issue
Block a user