Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

feat(nuxt): config options for default keepalive, page & layout transitions #5859

Merged
merged 20 commits into from
Aug 23, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/content/2.guide/3.directory-structure/10.pages.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ Of course, you are welcome to define metadata for your own use throughout your a

Nuxt will automatically wrap your page in [the Vue `<KeepAlive>` component](https://vuejs.org/guide/built-ins/keep-alive.html#keepalive) if you set `keepalive: true` in your `definePageMeta`. This might be useful to do, for example, in a parent route that has dynamic child routes, if you want to preserve page state across route changes. You can also set props to be passed to `<KeepAlive>` (see a full list [here](https://vuejs.org/api/built-in-components.html#keepalive)).

You can set a default value for this property [in your `nuxt.config`](/api/configuration/nuxt.config#keepalive).

#### `key`

[See above](#child-route-keys).
Expand All @@ -283,6 +285,8 @@ You can define middleware to apply before loading this page. It will be merged w

You can define transition properties for the `<transition>` component that wraps your pages and layouts, or pass `false` to disable the `<transition>` wrapper for that route. You can see a list of options that can be passed [here](https://vuejs.org/api/built-in-components.html#transition) or read [more about how transitions work](https://vuejs.org/guide/built-ins/transition.html#transition).

You can set default values for these properties [in your `nuxt.config`](/api/configuration/nuxt.config#layouttransition).

#### `alias`

You can define page aliases. They allow you to access the same page from different paths. It can be either a string or an array of strings as defined [here](https://router.vuejs.org/guide/essentials/redirect-and-alias.html#alias) on vue-router documentation.
Expand Down
7 changes: 3 additions & 4 deletions packages/nuxt/src/app/components/layout.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { defineComponent, isRef, Ref, Transition } from 'vue'
import { _wrapIf } from './utils'
import { useRoute } from '#app'
import { useAppConfig, useRoute } from '#app'
// @ts-ignore
import layouts from '#build/layouts'

const defaultLayoutTransition = { name: 'layout', mode: 'out-in' }

export default defineComponent({
props: {
name: {
Expand All @@ -15,6 +13,7 @@ export default defineComponent({
},
setup (props, context) {
const route = useRoute()
const appConfig = useAppConfig()

return () => {
const layout = (isRef(props.name) ? props.name.value : props.name) ?? route.meta.layout as string ?? 'default'
Expand All @@ -25,7 +24,7 @@ export default defineComponent({
}

// We avoid rendering layout transition if there is no layout to render
return _wrapIf(Transition, hasLayout && (route.meta.layoutTransition ?? defaultLayoutTransition),
return _wrapIf(Transition, hasLayout && (route.meta.layoutTransition ?? appConfig._nuxt.layoutTransition),
_wrapIf(layouts[layout], hasLayout, context.slots)
).default()
}
Expand Down
6 changes: 5 additions & 1 deletion packages/nuxt/src/core/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,17 @@ export const appConfigTemplate: NuxtTemplate = {
filename: 'app.config.mjs',
write: true,
getContents: ({ app, nuxt }) => {
const nuxtKeysToOmit = ['baseURL', 'buildAssetsDir', 'assetsPath', 'cdnURL']
return `
import defu from 'defu'

const inlineConfig = ${JSON.stringify(nuxt.options.appConfig, null, 2)}
const nuxtConfig = {
nuxt: ${JSON.stringify({ ...nuxt.options.app, ...Object.fromEntries(nuxtKeysToOmit.map(k => [k])) }, null, 2)},
}

${app.configs.map((id: string, index: number) => `import ${`cfg${index}`} from ${JSON.stringify(id)}`).join('\n')}
export default defu(${app.configs.map((_id: string, index: number) => `cfg${index}`).concat(['inlineConfig']).join(', ')})
export default defu(${app.configs.map((_id: string, index: number) => `cfg${index}`).concat(['inlineConfig', 'nuxtConfig']).join(', ')})
`
}
}
Expand Down
8 changes: 1 addition & 7 deletions packages/nuxt/src/head/module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { resolve } from 'pathe'
import { addPlugin, addTemplate, defineNuxtModule } from '@nuxt/kit'
import { addPlugin, defineNuxtModule } from '@nuxt/kit'
import { distDir } from '../dirs'

export default defineNuxtModule({
Expand All @@ -15,12 +15,6 @@ export default defineNuxtModule({
// Add #head alias
nuxt.options.alias['#head'] = runtimeDir

// Add global meta configuration
addTemplate({
filename: 'meta.config.mjs',
getContents: () => 'export default ' + JSON.stringify({ globalMeta: nuxt.options.app.head })
})

// Add generic plugin
addPlugin({ src: resolve(runtimeDir, 'plugin') })

Expand Down
8 changes: 3 additions & 5 deletions packages/nuxt/src/head/runtime/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { computed, getCurrentInstance, markRaw } from 'vue'
import * as Components from './components'
import { useHead } from './composables'
import { defineNuxtPlugin, useNuxtApp } from '#app'
// @ts-ignore
import metaConfig from '#build/meta.config.mjs'
import { defineNuxtPlugin, useAppConfig, useNuxtApp } from '#app'

type MetaComponents = typeof Components
declare module 'vue' {
export interface GlobalComponents extends MetaComponents {}
export interface GlobalComponents extends MetaComponents { }
}

const metaMixin = {
Expand All @@ -28,7 +26,7 @@ const metaMixin = {
}

export default defineNuxtPlugin((nuxtApp) => {
useHead(markRaw({ title: '', ...metaConfig.globalMeta }))
useHead(markRaw({ title: '', ...useAppConfig()._nuxt.head }))

nuxtApp.vueApp.mixin(metaMixin)

Expand Down
11 changes: 5 additions & 6 deletions packages/nuxt/src/pages/runtime/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { computed, DefineComponent, defineComponent, h, inject, provide, reactiv
import { RouteLocation, RouteLocationNormalized, RouteLocationNormalizedLoaded, RouterView } from 'vue-router'

import { generateRouteKey, RouterViewSlotProps, wrapInKeepAlive } from './utils'
import { useNuxtApp } from '#app'
import { useAppConfig, useNuxtApp } from '#app'
import { _wrapIf } from '#app/components/utils'

const isNestedKey = Symbol('isNested')
Expand All @@ -24,6 +24,7 @@ export default defineComponent({
},
setup (props, { attrs }) {
const nuxtApp = useNuxtApp()
const appConfig = useAppConfig()

const isNested = inject(isNestedKey, false)
provide(isNestedKey, true)
Expand All @@ -35,9 +36,9 @@ export default defineComponent({

const key = generateRouteKey(props.pageKey, routeProps)

return _wrapIf(Transition, routeProps.route.meta.pageTransition ?? defaultPageTransition,
wrapInKeepAlive(routeProps.route.meta.keepalive, isNested && nuxtApp.isHydrating
// Include route children in parent suspense
return _wrapIf(Transition, routeProps.route.meta.pageTransition ?? appConfig._nuxt.pageTransition,
Copy link
Member

@pi0 pi0 Aug 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this, we could have a bundler flag (like NUXT_PAGE_TRANSITION) that replaces value to false on build time otherwise setting app config to false won't be able to tree-shake... (but can be later. pointing to make clear about false handling with this PR)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This PR does not enable tree-shaking out; it just sets default behaviour. We can't tree shake this way as user may wish to enable transitions/keepalive on a per-page basis.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to be able to tree-shake out the Transition/Keepalive components but will it would need a different approach than this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. We use appConfig currently only to specify default but could be also used to specify feature flags. (swapping condition order when value is false to always disable transitions which I think would make more sense for false handling)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe support an additional option, e.g. keepalive: 'disabled' as the user may simply wish to disable them by default but not for all routes?

wrapInKeepAlive(routeProps.route.meta.keepalive ?? appConfig._nuxt.keepalive, isNested && nuxtApp.isHydrating
// Include route children in parent suspense
? h(Component, { key, routeProps, pageKey: key } as {})
: h(Suspense, {
onPending: () => nuxtApp.callHook('page:start', routeProps.Component),
Expand All @@ -55,8 +56,6 @@ export default defineComponent({
[key: string]: any
}>

const defaultPageTransition = { name: 'page', mode: 'out-in' }

const Component = defineComponent({
// TODO: Type props
// eslint-disable-next-line vue/require-prop-types
Expand Down
34 changes: 32 additions & 2 deletions packages/schema/src/config/_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default {
* }
* }
* ```
* @type {typeof import('../src/types/meta').MetaObject}
* @type {typeof import('../src/types/config').NuxtAppConfig['head']}
* @version 3
*/
head: {
Expand All @@ -123,7 +123,37 @@ export default {

return resolved
}
}
},
/**
* Default values for layout transitions.
*
* This can be overridden with `definePageMeta` on an individual page.
* Only JSON-serializable values are allowed.
*
* @see https://vuejs.org/api/built-in-components.html#transition
* @type {typeof import('../src/types/config').NuxtAppConfig['layoutTransition']}
*/
layoutTransition: { name: 'layout', mode: 'out-in' },
/**
* Default values for page transitions.
*
* This can be overridden with `definePageMeta` on an individual page.
* Only JSON-serializable values are allowed.
*
* @see https://vuejs.org/api/built-in-components.html#transition
* @type {typeof import('../src/types/config').NuxtAppConfig['pageTransition']}
*/
pageTransition: { name: 'page', mode: 'out-in' },
/**
* Default values for KeepAlive configuration between pages.
*
* This can be overridden with `definePageMeta` on an individual page.
* Only JSON-serializable values are allowed.
*
* @see https://vuejs.org/api/built-in-components.html#keepalive
* @type {typeof import('../src/types/config').NuxtAppConfig['keepalive']}
*/
keepalive: false,
},
/**
* The path to an HTML template file for rendering Nuxt responses.
Expand Down
17 changes: 14 additions & 3 deletions packages/schema/src/types/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { KeepAliveProps, TransitionProps } from 'vue'
import { ConfigSchema } from '../../schema/config'
import type { UserConfig as ViteUserConfig } from 'vite'
import type { Options as VuePluginOptions } from '@vitejs/plugin-vue'
import type { MetaObject } from './meta'

type DeepPartial<T> = T extends Function ? T : T extends Record<string, any> ? { [P in keyof T]?: DeepPartial<T[P]> } : T

Expand All @@ -13,8 +15,8 @@ export interface NuxtConfig extends DeepPartial<Omit<ConfigSchema, 'vite'>> {

// TODO: Expose ConfigLayer<T> from c12
interface ConfigLayer<T> {
config: T;
cwd: string;
config: T
cwd: string
configFile: string
}
export type NuxtConfigLayer = ConfigLayer<NuxtConfig & {
Expand Down Expand Up @@ -71,4 +73,13 @@ export interface AppConfigInput extends Record<string, any> {
nitro?: never
}

export interface AppConfig { }
export interface NuxtAppConfig {
head: MetaObject
layoutTransition: boolean | TransitionProps
pageTransition: boolean | TransitionProps
keepalive: boolean | KeepAliveProps
}

export interface AppConfig {
_nuxt: NuxtAppConfig
}
6 changes: 6 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,12 @@ describe('app config', () => {
const html = await $fetch('/app-config')

const expectedAppConfig = {
nuxt: {
head: { meta: [], link: [], style: [], script: [], noscript: [], charset: 'utf-8', viewport: 'width=1024, initial-scale=1' },
layoutTransition: { name: 'layout', mode: 'out-in' },
pageTransition: { name: 'page', mode: 'out-in' },
keepalive: false
},
fromNuxtConfig: true,
nested: {
val: 2
Expand Down