@vercube/vue-lazy-hydration
Version:
Optimize Vue 3 SSR with lazy hydration, delaying HTML hydration until it's needed for better performance
130 lines (124 loc) • 4.25 kB
JavaScript
import { hydrateOnIdle, hydrateOnVisible, hydrateOnInteraction, hydrateOnMediaQuery, defineAsyncComponent, defineComponent, h, markRaw } from 'vue';
/**
* Get the hydration strategy based on the hydration properties.
*
* @param {IHydrationProps} props - The hydration properties.
* @returns {HydrationStrategy} The hydration strategy.
*/
function getHydrationStrategy(props) {
// Hydrate on idle strategy
// This is useful for components that should only be hydrated when the user is idle
// e.g. when the user is not interacting with the component
if (props.whenIdle) {
return hydrateOnIdle(props.idleTimeout);
}
// Hydrate on visible strategy
// This is useful for components that should only be hydrated when they become visible
// e.g. when the user scrolls to the component
if (props.whenVisible) {
return hydrateOnVisible(typeof props.whenVisible !== 'boolean' ? props.whenVisible : undefined);
}
// Hydrate on interaction strategy
// This is useful for components that should only be hydrated when the user interacts with them
// e.g. when the user hovers over the component
if (props.onInteraction) {
return hydrateOnInteraction(Array.isArray(props.onInteraction) ? props.onInteraction : [props.onInteraction]);
}
// SSR only hydration strategy
// This is useful for components that should only be hydrated on the server
// and not on the client
if (props.ssrOnly) {
return hydrateOnMediaQuery('width >= 999999999999999px');
}
// Default hydration strategy
return (hydrate, forEach) => () => {
forEach(hydrate);
};
}
/**
* Creates a hydration blocker component.
* @param {VNode} slot - The element to render.
* @returns {} The async component definition.
*/
function createHydrationBlocker(slot, props) {
return defineAsyncComponent({
loader: () => new Promise(resolve => resolve(slot)),
hydrate: getHydrationStrategy(props)
});
}
/**
* This component allows to defer DOM hydration by vue. Whatever is wrapped with this component
* will not be hydrated when front is loaded, instead it will be deferred.
*
* We have to override lazy-hydration-wrapper to backport the feature to Vue 2.
* We also have to use `defineComponent` instead of Factory to use `setup` function.
* `setup` is not available in Factory system, because the instance of class is created on BeforeCreate hook,
* that executes after `setup` is called.
*
* This components is implementation of Vue Core functionality - hydration
* Hydration management is available from Vue >= 3.5
* @see: https://github.com/vuejs/core/pull/11458
*/
const LazyHydrationComponent = defineComponent({
name: 'CubeLazyHydration',
/** Disable inherit attributes */
inheritAttrs: false,
/** Define Emits */
emits: ['hydrated'],
/** Define props */
props: {
triggerHydration: {
type: Boolean,
required: false
},
ssrOnly: {
type: Boolean,
default: false,
required: false
},
whenVisible: {
type: [Boolean, Object],
required: false
},
whenIdle: {
type: Boolean,
required: false
},
idleTimeout: {
type: Number,
default: 2000,
required: false
},
onInteraction: {
type: [Array, String],
required: false
}
},
render() {
const slotDefault = this?.$slots?.default?.()?.[0];
if (!slotDefault) {
throw new Error('CubeLazyHydration component must have a single root element');
}
if (this.$props.triggerHydration) {
return h(slotDefault);
}
const comp = createHydrationBlocker(slotDefault, this.$props);
return h(comp);
}
});
const LazyHydration = markRaw(LazyHydrationComponent);
/**
* A composable function that returns a lazy hydrated Vue component.
*
* @template T - The type of the component.
* @param {AsyncComponentLoader<T>} loader - The async component loader function.
* @param {IHydrationProps} props - The hydration properties.
* @returns {ReturnType<typeof defineAsyncComponent>} The lazy hydrated component.
*/
function useLazyHydration(loader, props) {
return defineAsyncComponent({
loader,
hydrate: getHydrationStrategy(props)
});
}
export { LazyHydration, useLazyHydration };