mirror of
https://github.com/nuxt/nuxt.git
synced 2025-02-12 03:38:07 +00:00
refactor: use vue native hydration strategies
This commit is contained in:
parent
cafc328ef2
commit
b64dca110e
@ -12,7 +12,7 @@ You can use this utility to customize the timeout of delayed hydration component
|
|||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
- `options`: `{ timeout }`
|
- `timeout` : `number`
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ If you would like to give a timeout of 5 seconds for the components:
|
|||||||
```vue [pages/index.vue]
|
```vue [pages/index.vue]
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<LazyIdleMyComponent :hydrate="createIdleLoader({timeout: 3000})"/>
|
<LazyIdleMyComponent :hydrate="createIdleLoader(5000)"/>
|
||||||
</div>
|
</div>
|
||||||
<template>
|
<template>
|
||||||
```
|
```
|
||||||
@ -29,5 +29,5 @@ If you would like to give a timeout of 5 seconds for the components:
|
|||||||
::
|
::
|
||||||
|
|
||||||
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback"}
|
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback"}
|
||||||
This is based on the `requestIdleCallback` web API, and therefore only accepts the timeout prop, which should be a number.
|
This is based on the `requestIdleCallback` web API, and therefore only accepts the time in milliseconds for the max idle callback duration, which should be a number.
|
||||||
::
|
::
|
||||||
|
@ -26,9 +26,9 @@ export const useHydration = <K extends keyof NuxtPayload, T = NuxtPayload[K]> (k
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A `requestIdleCallback` options utility, used to determine custom timeout for idle-callback based delayed hydration.
|
* A `requestIdleCallback` options utility, used to determine custom timeout for idle-callback based delayed hydration.
|
||||||
* @param opts the options object, containing the wanted timeout
|
* @param timeout the max timeout for the idle callback, in milliseconds
|
||||||
*/
|
*/
|
||||||
export const createIdleLoader = (opts: IdleRequestOptions) => opts
|
export const createIdleLoader = (timeout: number) => timeout
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An `IntersectionObserver` options utility, used to determine custom viewport-based delayed hydration behavior.
|
* An `IntersectionObserver` options utility, used to determine custom viewport-based delayed hydration behavior.
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { createStaticVNode, defineComponent, getCurrentInstance, h, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { defineAsyncComponent, defineComponent, getCurrentInstance, h, hydrateOnIdle, hydrateOnInteraction, hydrateOnVisible } from 'vue'
|
||||||
import type { Component, ComponentInternalInstance, Ref } from 'vue'
|
import type { AsyncComponentLoader } from 'vue'
|
||||||
// import ClientOnly from '#app/components/client-only'
|
// import ClientOnly from '#app/components/client-only'
|
||||||
import { getFragmentHTML } from '#app/components/utils'
|
|
||||||
import { useNuxtApp } from '#app/nuxt'
|
import { useNuxtApp } from '#app/nuxt'
|
||||||
import { cancelIdleCallback, requestIdleCallback } from '#app/compat/idle-callback'
|
|
||||||
import { onNuxtReady } from '#app'
|
|
||||||
import { useIntersectionObserver } from '#app/utils'
|
|
||||||
|
|
||||||
function elementIsVisibleInViewport (el: Element) {
|
function elementIsVisibleInViewport (el: Element) {
|
||||||
const { top, left, bottom, right } = el.getBoundingClientRect()
|
const { top, left, bottom, right } = el.getBoundingClientRect()
|
||||||
@ -16,7 +12,7 @@ function elementIsVisibleInViewport (el: Element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* @__NO_SIDE_EFFECTS__ */
|
/* @__NO_SIDE_EFFECTS__ */
|
||||||
export const createLazyIOComponent = (componentLoader: Component) => {
|
export const createLazyIOComponent = (componentLoader: AsyncComponentLoader) => {
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
setup (_, { attrs }) {
|
setup (_, { attrs }) {
|
||||||
@ -26,104 +22,48 @@ export const createLazyIOComponent = (componentLoader: Component) => {
|
|||||||
|
|
||||||
const nuxt = useNuxtApp()
|
const nuxt = useNuxtApp()
|
||||||
const instance = getCurrentInstance()!
|
const instance = getCurrentInstance()!
|
||||||
const hasIntersected = ref(false)
|
|
||||||
const el: Ref<Element | null> = ref(null)
|
|
||||||
let unobserve: (() => void) | null = null
|
|
||||||
|
|
||||||
// todo can be refactored
|
if (instance.vnode.el && nuxt.isHydrating && elementIsVisibleInViewport(instance.vnode.el as Element)) {
|
||||||
if (instance.vnode.el && nuxt.isHydrating) {
|
return h(componentLoader, attrs)
|
||||||
hasIntersected.value = elementIsVisibleInViewport(instance.vnode.el as Element)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasIntersected.value) {
|
return () => h(defineAsyncComponent({
|
||||||
onMounted(() => {
|
loader: componentLoader,
|
||||||
const observer = useIntersectionObserver(attrs.hydrate as Partial<IntersectionObserverInit> | undefined)
|
hydrate: hydrateOnVisible(attrs.hydrate as IntersectionObserverInit | undefined),
|
||||||
unobserve = observer!.observe(el.value as Element, () => {
|
}))
|
||||||
hasIntersected.value = true
|
|
||||||
unobserve?.()
|
|
||||||
unobserve = null
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
unobserve?.()
|
|
||||||
unobserve = null
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
return h('div', { ref: el }, [
|
|
||||||
hasIntersected.value ? h(componentLoader, attrs) : (instance.vnode.el && nuxt.isHydrating) ? createStaticVNode(getFragmentHTML(instance.vnode.el ?? null, true)?.join('') || '', 1) : null,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @__NO_SIDE_EFFECTS__ */
|
/* @__NO_SIDE_EFFECTS__ */
|
||||||
export const createLazyNetworkComponent = (componentLoader: Component) => {
|
export const createLazyNetworkComponent = (componentLoader: AsyncComponentLoader) => {
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
setup (_, { attrs }) {
|
setup (_, { attrs }) {
|
||||||
if (import.meta.server) {
|
if (import.meta.server) {
|
||||||
return () => h(componentLoader, attrs)
|
return () => h(componentLoader, attrs)
|
||||||
}
|
}
|
||||||
const nuxt = useNuxtApp()
|
return () => defineAsyncComponent({
|
||||||
const instance = getCurrentInstance()!
|
loader: componentLoader,
|
||||||
const isIdle = ref(false)
|
hydrate: hydrateOnIdle(attrs.hydrate as number | undefined),
|
||||||
let idleHandle: number | null = null
|
|
||||||
onMounted(() => {
|
|
||||||
onNuxtReady(() => {
|
|
||||||
idleHandle = requestIdleCallback(() => {
|
|
||||||
isIdle.value = true
|
|
||||||
cancelIdleCallback(idleHandle as unknown as number)
|
|
||||||
idleHandle = null
|
|
||||||
}, attrs.hydrate ?? { timeout: 10000 })
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
if (idleHandle) {
|
|
||||||
cancelIdleCallback(idleHandle as unknown as number)
|
|
||||||
idleHandle = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return () => isIdle.value ? h(componentLoader, attrs) : (instance.vnode.el && nuxt.isHydrating) ? createStaticVNode(getFragmentHTML(instance.vnode.el ?? null, true)?.join('') || '', 1) : null
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventsMapper = new WeakMap<ComponentInternalInstance, (() => void)[]>()
|
|
||||||
/* @__NO_SIDE_EFFECTS__ */
|
/* @__NO_SIDE_EFFECTS__ */
|
||||||
export const createLazyEventComponent = (componentLoader: Component) => {
|
export const createLazyEventComponent = (componentLoader: AsyncComponentLoader) => {
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
setup (_, { attrs }) {
|
setup (_, { attrs }) {
|
||||||
if (import.meta.server) {
|
if (import.meta.server) {
|
||||||
return () => h(componentLoader, attrs)
|
return () => h(componentLoader, attrs)
|
||||||
}
|
}
|
||||||
const nuxt = useNuxtApp()
|
const events: Array<keyof HTMLElementEventMap> = attrs.hydrate as Array<keyof HTMLElementEventMap> ?? ['mouseover']
|
||||||
const instance = getCurrentInstance()!
|
return () => h(defineAsyncComponent({
|
||||||
const isTriggered = ref(false)
|
loader: componentLoader,
|
||||||
const events: string[] = attrs.hydrate as string[] ?? ['mouseover']
|
hydrate: hydrateOnInteraction(events),
|
||||||
|
}))
|
||||||
const registeredEvents: (() => void)[] = []
|
|
||||||
if (!eventsMapper.has(instance)) {
|
|
||||||
onMounted(() => {
|
|
||||||
events.forEach((event) => {
|
|
||||||
const handler = () => {
|
|
||||||
isTriggered.value = true
|
|
||||||
registeredEvents.forEach(remove => remove())
|
|
||||||
eventsMapper.delete(instance)
|
|
||||||
}
|
|
||||||
instance.vnode.el?.addEventListener(event, handler)
|
|
||||||
registeredEvents.push(() => instance.vnode.el?.removeEventListener(event, handler))
|
|
||||||
})
|
|
||||||
eventsMapper.set(instance, registeredEvents)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
registeredEvents?.forEach(remove => remove())
|
|
||||||
eventsMapper.delete(instance)
|
|
||||||
})
|
|
||||||
return () => isTriggered.value ? h(componentLoader, attrs) : (instance.vnode.el && nuxt.isHydrating) ? createStaticVNode(getFragmentHTML(instance.vnode.el ?? null, true)?.join('') || '', 1) : null
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user