Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vue3): Add Vue3 KeepAlive Component Support #2158

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 54 additions & 16 deletions packages/vue3/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,31 @@ import {
PropType,
reactive,
ref,
shallowRef,
KeepAlive,
} from 'vue'
import remember from './remember'
import { VuePageHandlerArgs } from './types'
import useForm from './useForm'

interface KeepAliveOptions {
enabled: boolean
includes?: string[]
maxLength?: number
}

export interface InertiaAppProps {
initialPage: Page
initialComponent?: object
resolveComponent?: (name: string) => DefineComponent | Promise<DefineComponent>
titleCallback?: (title: string) => string
onHeadUpdate?: (elements: string[]) => void
keepAliveResolver?: (component: DefineComponent) => KeepAliveOptions
}

export type InertiaApp = DefineComponent<InertiaAppProps>

const component = ref(null)
const page = ref<Page<any>>(null)
const layout = shallowRef(null)
const key = ref(null)
let headManager = null

Expand Down Expand Up @@ -56,23 +62,42 @@ const App: InertiaApp = defineComponent({
required: false,
default: () => () => {},
},
keepAliveResolver: {
type: Function as PropType<(component: DefineComponent) => KeepAliveOptions>,
required: false,
default: () => ({
enabled: false,
}),
},
},
setup({ initialPage, initialComponent, resolveComponent, titleCallback, onHeadUpdate }) {
component.value = initialComponent ? markRaw(initialComponent) : null
page.value = initialPage
setup(props) {
component.value = props.initialComponent ? markRaw(props.initialComponent) : null
page.value = props.initialPage
key.value = null

const isServer = typeof window === 'undefined'
headManager = createHeadManager(isServer, titleCallback, onHeadUpdate)
headManager = createHeadManager(isServer, props.titleCallback, props.onHeadUpdate)

if (!isServer) {
router.init({
initialPage,
resolveComponent,
initialPage: props.initialPage,
resolveComponent: props.resolveComponent,
swapComponent: async (args: VuePageHandlerArgs) => {
component.value = markRaw(args.component)
page.value = args.page
key.value = args.preserveState ? key.value : Date.now()

const urlParams = new URLSearchParams(args.page.url.split('?')[1] || '')
const id = urlParams.get('KeepAliveId')
const componentName = (args.component as any)?.name || args.page.component

if (id) {
key.value = `${componentName}-${id}`
} else if (args.preserveState) {
key.value = key.value
} else {
// Use a combination of timestamp and random number to ensure uniqueness
key.value = `${Date.now()}-${Math.random()}`
}
},
})

Expand All @@ -83,31 +108,44 @@ const App: InertiaApp = defineComponent({
if (component.value) {
component.value.inheritAttrs = !!component.value.inheritAttrs

const componentName = (component.value as any)?.name ||
(component.value as any)?.__name ||
page.value?.component

const componentKey = componentName || key.value

const child = h(component.value, {
...page.value.props,
key: key.value,
key: componentKey,
})

if (layout.value) {
component.value.layout = layout.value
layout.value = null
const renderPage = () => {
const keepAliveOptions = props.keepAliveResolver(component.value)

if (keepAliveOptions.enabled) {
return h(KeepAlive, {
max: keepAliveOptions.maxLength || 10,
include: keepAliveOptions.includes || [],
}, () => child)
}
return child
}

if (component.value.layout) {
if (typeof component.value.layout === 'function') {
return component.value.layout(h, child)
return component.value.layout(h, renderPage())
}

return (Array.isArray(component.value.layout) ? component.value.layout : [component.value.layout])
.concat(child)
.concat(renderPage())
.reverse()
.reduce((child, layout) => {
layout.inheritAttrs = !!layout.inheritAttrs
return h(layout, { ...page.value.props }, () => child)
})
}

return child
return renderPage()
}
}
},
Expand Down
7 changes: 7 additions & 0 deletions packages/vue3/src/createInertiaApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ interface CreateInertiaAppProps {
}
page?: Page
render?: (app: VueApp) => Promise<string>
keepAliveResolver?: (component: DefineComponent) => {
enabled: boolean
includes?: string[]
maxLength?: number
}
}

export default async function createInertiaApp({
Expand All @@ -27,6 +32,7 @@ export default async function createInertiaApp({
progress = {},
page,
render,
keepAliveResolver,
}: CreateInertiaAppProps): Promise<{ head: string[]; body: string }> {
const isServer = typeof window === 'undefined'
const el = isServer ? null : document.getElementById(id)
Expand All @@ -48,6 +54,7 @@ export default async function createInertiaApp({
resolveComponent,
titleCallback: title,
onHeadUpdate: isServer ? (elements) => (head = elements) : null,
keepAliveResolver,
},
plugin,
})
Expand Down
6 changes: 4 additions & 2 deletions packages/vue3/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { createHeadManager, Page, PageHandler, router } from '@inertiajs/core'
import { ComponentPublicInstance } from 'vue'
import useForm from './useForm'
import { DefineComponent } from 'vue'

export type VuePageHandlerArgs = Parameters<PageHandler>[0] & {
component: ComponentPublicInstance | Promise<ComponentPublicInstance>
component: DefineComponent & { name?: string }
page: Page
preserveState: boolean
}

declare module '@inertiajs/core' {
Expand Down
Loading