@vercube/vue-lazy-hydration
Version:
Optimize Vue 3 SSR with lazy hydration, delaying HTML hydration until it's needed for better performance
133 lines (126 loc) • 4.22 kB
JavaScript
'use strict';
var vue = require('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 vue.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 vue.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 vue.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 vue.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 vue.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 = vue.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 vue.h(slotDefault);
}
const comp = createHydrationBlocker(slotDefault, this.$props);
return vue.h(comp);
}
});
const LazyHydration = vue.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 vue.defineAsyncComponent({
loader,
hydrate: getHydrationStrategy(props)
});
}
exports.LazyHydration = LazyHydration;
exports.useLazyHydration = useLazyHydration;