feat(nuxt3): make layout and other page meta reactive (#2926)

This commit is contained in:
Daniel Roe 2022-01-26 11:56:24 +00:00 committed by GitHub
parent 696138794b
commit 944464781d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 44 additions and 19 deletions

View File

@ -79,12 +79,10 @@ You can also use a ref or computed property for your layout.
<script setup> <script setup>
const route = useRoute() const route = useRoute()
function enableCustomLayout () { function enableCustomLayout () {
// Note: because it's within a ref, it will persist if route.meta.layout = "custom"
// you navigate away and then back to the page.
route.meta.layout.value = "custom"
} }
definePageMeta({ definePageMeta({
layout: ref(false), layout: false,
}); });
</script> </script>
``` ```

View File

@ -112,8 +112,27 @@ export default defineNuxtModule({
} }
}) })
addTemplate({
filename: 'layouts.d.ts',
write: true,
getContents: async () => {
const composablesFile = resolve(runtimeDir, 'composables')
const layouts = await resolveLayouts(nuxt)
return [
'import { ComputedRef, Ref } from \'vue\'',
`export type LayoutKey = ${layouts.map(layout => `"${layout.name}"`).join(' | ') || 'string'}`,
`declare module '${composablesFile}' {`,
' interface PageMeta {',
' layout?: false | LayoutKey | Ref<LayoutKey> | ComputedRef<LayoutKey>',
' }',
'}'
].join('\n')
}
})
nuxt.hook('prepare:types', ({ references }) => { nuxt.hook('prepare:types', ({ references }) => {
references.push({ path: resolve(nuxt.options.buildDir, 'middleware.d.ts') }) references.push({ path: resolve(nuxt.options.buildDir, 'middleware.d.ts') })
references.push({ path: resolve(nuxt.options.buildDir, 'layouts.d.ts') })
}) })
// Add layouts template // Add layouts template

View File

@ -1,4 +1,4 @@
import { ComputedRef, KeepAliveProps, Ref, TransitionProps } from 'vue' import { KeepAliveProps, TransitionProps, UnwrapRef } from 'vue'
import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw } from 'vue-router' import type { Router, RouteLocationNormalizedLoaded, NavigationGuard, RouteLocationNormalized, RouteLocationRaw } from 'vue-router'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
@ -14,13 +14,12 @@ export interface PageMeta {
[key: string]: any [key: string]: any
pageTransition?: false | TransitionProps pageTransition?: false | TransitionProps
layoutTransition?: false | TransitionProps layoutTransition?: false | TransitionProps
layout?: false | string | Ref<false | string> | ComputedRef<false | string>
key?: string | ((route: RouteLocationNormalizedLoaded) => string) key?: string | ((route: RouteLocationNormalizedLoaded) => string)
keepalive?: false | KeepAliveProps keepalive?: false | KeepAliveProps
} }
declare module 'vue-router' { declare module 'vue-router' {
interface RouteMeta extends PageMeta {} interface RouteMeta extends UnwrapRef<PageMeta> {}
} }
const warnRuntimeUsage = (method: string) => const warnRuntimeUsage = (method: string) =>

View File

@ -25,26 +25,34 @@ export default defineComponent({
const hasLayout = props.layout ?? route.meta.layout ?? 'default' in layouts const hasLayout = props.layout ?? route.meta.layout ?? 'default' in layouts
return h(RouterView, {}, { return h(RouterView, {}, {
default: ({ Component }: RouterViewSlotProps) => Component && wrapIf(Transition, hasLayout && (route.meta.layoutTransition ?? defaultLayoutTransition), { default: ({ Component }: RouterViewSlotProps) => Component &&
default: () => wrapIf(NuxtLayout, hasLayout && { layout: props.layout ?? route.meta.layout }, { wrapIf(Transition, hasLayout && (route.meta.layoutTransition ?? defaultLayoutTransition),
default: () => wrapIf(Transition, route.meta.pageTransition ?? defaultPageTransition, { wrapIf(NuxtLayout, hasLayout && { name: props.layout ?? route.meta.layout },
default: () => wrapIf(KeepAlive, process.client && route.meta.keepalive, h(Suspense, { wrapIf(Transition, route.meta.pageTransition ?? defaultPageTransition,
wrapInKeepAlive(route.meta.keepalive, h(Suspense, {
onPending: () => nuxtApp.callHook('page:start', Component), onPending: () => nuxtApp.callHook('page:start', Component),
onResolve: () => nuxtApp.callHook('page:finish', Component) onResolve: () => nuxtApp.callHook('page:finish', Component)
}, { default: () => h(Component) })) }, { default: () => h(Component) })
}) )
}) )
}) )).default()
}) })
} }
} }
}) })
const wrapIf = (component: Component, props: any, slotsOrChildren: any) => { const Fragment = {
if (props) { setup (props, { slots }) {
return h(component, props === true ? {} : props, slotsOrChildren) return () => slots.default()
} }
return slotsOrChildren.default?.() || slotsOrChildren }
const wrapIf = (component: Component, props: any, slots: any) => {
return { default: () => props ? h(component, props === true ? {} : props, slots) : h(Fragment, {}, slots) }
}
const wrapInKeepAlive = (props: any, children: any) => {
return { default: () => process.client && props ? h(KeepAlive, props === true ? {} : props, children) : children }
} }
const defaultLayoutTransition = { name: 'layout', mode: 'out-in' } const defaultLayoutTransition = { name: 'layout', mode: 'out-in' }

View File

@ -66,6 +66,7 @@ export default defineNuxtPlugin((nuxtApp) => {
} }
router.beforeEach(async (to, from) => { router.beforeEach(async (to, from) => {
to.meta = reactive(to.meta)
nuxtApp._processingMiddleware = true nuxtApp._processingMiddleware = true
type MiddlewareDef = string | NavigationGuard type MiddlewareDef = string | NavigationGuard