mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-11 03:08:16 +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
|
// For pnpm typecheck:docs to generate correct types
|
||||||
|
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
import { addPluginTemplate, addRouteMiddleware } from 'nuxt/kit'
|
import { addPluginTemplate, addRouteMiddleware } from 'nuxt/kit'
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
@ -17,6 +18,9 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
pages: process.env.DOCS_TYPECHECK === 'true',
|
pages: process.env.DOCS_TYPECHECK === 'true',
|
||||||
|
dir: {
|
||||||
|
app: fileURLToPath(new URL('./test/runtime/app', import.meta.url)),
|
||||||
|
},
|
||||||
typescript: {
|
typescript: {
|
||||||
shim: process.env.DOCS_TYPECHECK === 'true',
|
shim: process.env.DOCS_TYPECHECK === 'true',
|
||||||
hoist: ['@vitejs/plugin-vue', 'vue-router'],
|
hoist: ['@vitejs/plugin-vue', 'vue-router'],
|
||||||
|
@ -3,7 +3,8 @@ import type { Ref, VNode } from 'vue'
|
|||||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
import { PageRouteSymbol } from './injections'
|
import { PageRouteSymbol } from './injections'
|
||||||
|
|
||||||
export const RouteProvider = defineComponent({
|
export const defineRouteProvider = (name = 'RouteProvider') => defineComponent({
|
||||||
|
name,
|
||||||
props: {
|
props: {
|
||||||
vnode: {
|
vnode: {
|
||||||
type: Object as () => 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 { 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 { RouterView } from 'vue-router'
|
||||||
import { defu } from 'defu'
|
import { defu } from 'defu'
|
||||||
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouterViewProps } from 'vue-router'
|
import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouterViewProps } from 'vue-router'
|
||||||
|
|
||||||
import { generateRouteKey, toArray, wrapInKeepAlive } from './utils'
|
import { generateRouteKey, toArray, wrapInKeepAlive } from './utils'
|
||||||
import type { RouterViewSlotProps } 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 { useNuxtApp } from '#app/nuxt'
|
||||||
import { useRouter } from '#app/composables/router'
|
import { useRouter } from '#app/composables/router'
|
||||||
import { _wrapInTransition } from '#app/components/utils'
|
import { _wrapInTransition } from '#app/components/utils'
|
||||||
@ -83,6 +83,9 @@ export default defineComponent({
|
|||||||
nuxtApp._isNuxtPageUsed = true
|
nuxtApp._isNuxtPageUsed = true
|
||||||
}
|
}
|
||||||
let pageLoadingEndHookAlreadyCalled = false
|
let pageLoadingEndHookAlreadyCalled = false
|
||||||
|
|
||||||
|
const routerProviderLookup = new WeakMap<Component, ReturnType<typeof defineRouteProvider> | undefined>()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
return h(RouterView, { name: props.name, route: props.route, ...attrs }, {
|
return h(RouterView, { name: props.name, route: props.route, ...attrs }, {
|
||||||
default: (routeProps: RouterViewSlotProps) => {
|
default: (routeProps: RouterViewSlotProps) => {
|
||||||
@ -128,7 +131,7 @@ export default defineComponent({
|
|||||||
default: () => {
|
default: () => {
|
||||||
const providerVNode = h(RouteProvider, {
|
const providerVNode = h(RouteProvider, {
|
||||||
key: key || undefined,
|
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,
|
route: routeProps.route,
|
||||||
renderKey: key || undefined,
|
renderKey: key || undefined,
|
||||||
vnodeRef: pageRef,
|
vnodeRef: pageRef,
|
||||||
@ -141,7 +144,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Client side rendering
|
// Client side rendering
|
||||||
|
|
||||||
const hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition)
|
const hasTransition = !!(props.transition ?? routeProps.route.meta.pageTransition ?? defaultPageTransition)
|
||||||
const transitionProps = hasTransition && _mergeTransitionProps([
|
const transitionProps = hasTransition && _mergeTransitionProps([
|
||||||
props.transition,
|
props.transition,
|
||||||
@ -165,18 +167,28 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
default: () => {
|
default: () => {
|
||||||
const providerVNode = h(RouteProvider, {
|
const routeProviderProps = {
|
||||||
key: key || undefined,
|
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,
|
route: routeProps.route,
|
||||||
renderKey: key || undefined,
|
renderKey: key || undefined,
|
||||||
trackRootNodes: hasTransition,
|
trackRootNodes: hasTransition,
|
||||||
vnodeRef: pageRef,
|
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()
|
)).default()
|
||||||
@ -232,3 +244,8 @@ function hasChildrenRoutes (fork: RouteLocationNormalizedLoaded | null, newRoute
|
|||||||
const index = newRoute.matched.findIndex(m => m.components?.default === Component?.type)
|
const index = newRoute.matched.findIndex(m => m.components?.default === Component?.type)
|
||||||
return index < newRoute.matched.length - 1
|
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`', () => {
|
describe('routing utilities: `useRoute`', () => {
|
||||||
it('should show provide a mock route', () => {
|
it('should provide a route', () => {
|
||||||
expect(useRoute()).toMatchObject({
|
expect(useRoute()).toMatchObject({
|
||||||
fullPath: '/',
|
fullPath: '/',
|
||||||
hash: '',
|
hash: '',
|
||||||
href: '/',
|
matched: expect.arrayContaining([]),
|
||||||
matched: [],
|
|
||||||
meta: {},
|
meta: {},
|
||||||
name: undefined,
|
name: 'catchall',
|
||||||
params: {},
|
params: {},
|
||||||
path: '/',
|
path: '/',
|
||||||
query: {},
|
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: {
|
environmentOptions: {
|
||||||
nuxt: {
|
nuxt: {
|
||||||
overrides: {
|
overrides: {
|
||||||
|
pages: true,
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
app: {
|
app: {
|
||||||
buildId: 'override',
|
buildId: 'override',
|
||||||
|
Loading…
Reference in New Issue
Block a user