fix(nuxt): remove div in client-only pages

This commit is contained in:
Julien Huang 2024-12-31 00:24:58 +01:00
parent c1b8b921f1
commit 29923890f9
3 changed files with 68 additions and 38 deletions

View File

@ -1,32 +1,73 @@
import { defineAsyncComponent, defineComponent, h } from 'vue' import { isPromise } from 'node:util/types'
import type { AsyncComponentLoader } from 'vue' import { type AsyncComponentLoader, type ComponentOptions, h, onMounted, ref } from 'vue'
import ClientOnly from '#app/components/client-only' import { useNuxtApp } from '#app'
import { useNuxtApp } from '#app/nuxt' import ServerPlaceholder from '#app/components/server-placeholder'
/* @__NO_SIDE_EFFECTS__ */ /* @__NO_SIDE_EFFECTS__ */
export const createClientPage = (loader: AsyncComponentLoader) => { export const createClientPage = (loader: AsyncComponentLoader) => {
const page = defineAsyncComponent(import.meta.dev // Vue-router: Write "() => import('./MyPage.vue')" instead of "defineAsyncComponent(() => import('./MyPage.vue'))".
? () => loader().then((m) => { return loader().then((m) => {
const c = m.default || m
if (import.meta.dev) {
// mark component as client-only for `definePageMeta` // mark component as client-only for `definePageMeta`
(m.default || m).__clientOnlyPage = true c.__clientOnlyPage = true
return m.default || m
})
: loader)
return defineComponent({
inheritAttrs: false,
setup (_, { attrs }) {
const nuxtApp = useNuxtApp()
if (import.meta.server || nuxtApp.isHydrating) {
// wrapped with div to avoid Transition issues
// @see https://github.com/nuxt/nuxt/pull/25037#issuecomment-1877423894
return () => h('div', [
h(ClientOnly, undefined, {
default: () => h(page, attrs),
}),
])
} }
return () => h(page, attrs) return pageToClientOnly(c)
},
}) })
} }
const cache = new WeakMap()
function pageToClientOnly<T extends ComponentOptions> (component: T) {
if (import.meta.server) {
return ServerPlaceholder
}
if (cache.has(component)) {
return cache.get(component)
}
const clone = { ...component }
if (clone.render) {
// override the component render (non script setup component) or dev mode
clone.render = (ctx: any, cache: any, $props: any, $setup: any, $data: any, $options: any) => ($setup.mounted$ ?? ctx.mounted$)
? h(component.render?.bind(ctx)(ctx, cache, $props, $setup, $data, $options))
: h('div')
} else if (clone.template) {
// handle runtime-compiler template
clone.template = `
<template v-if="mounted$">${component.template}</template>
<template v-else><div></div></template>
`
}
clone.setup = (props, ctx) => {
const nuxtApp = useNuxtApp()
const mounted$ = ref(nuxtApp.isHydrating === false)
onMounted(() => {
mounted$.value = true
})
const setupState = component.setup?.(props, ctx) || {}
if (isPromise(setupState)) {
return Promise.resolve(setupState).then((setupState: any) => {
if (typeof setupState !== 'function') {
setupState = setupState || {}
setupState.mounted$ = mounted$
return setupState
}
return (...args: any[]) => (mounted$.value || !nuxtApp.isHydrating) ? h(setupState(...args)) : h('div')
})
} else {
return typeof setupState === 'function'
? (...args: any[]) => (mounted$.value || !nuxtApp.isHydrating)
? h(setupState(...args))
: h('div')
: Object.assign(setupState, { mounted$ })
}
}
cache.set(component, clone)
return clone
}

View File

@ -1,10 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({
// Disable page transition for this page to avoid having multiple time the same page during transition
pageTransition: false,
layoutTransition: false,
})
const state = useState('test', () => { const state = useState('test', () => {
let hasAccessToWindow = null as null | boolean let hasAccessToWindow = null as null | boolean

View File

@ -1,10 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
const renderedOnServer = useState(() => import.meta.server) const renderedOnServer = useState(() => import.meta.server)
definePageMeta({
// Disable page transition for this page to avoid having multiple time the same page during transition
pageTransition: false,
layoutTransition: false,
})
</script> </script>
<template> <template>