nuxt-vitalizer
Version:
Instantly better LCP scores in Google Lighthouse
70 lines (69 loc) • 2.36 kB
JavaScript
// @license MIT
import { createStaticVNode, ref, onMounted, defineComponent, getCurrentInstance } from "vue";
import { useNuxtApp } from "#imports";
import { getFragmentHTML } from "#app/components/utils";
import { delayHydrationOptions } from "#build/module/nuxt-vitalizer";
export default defineComponent({
setup(_, { slots }) {
const nuxtApp = useNuxtApp();
const instance = getCurrentInstance();
const supportsIdleCallback = import.meta.client ? "requestIdleCallback" in window : false;
const shouldRender = ref(import.meta.server || !supportsIdleCallback || !nuxtApp.isHydrating);
let vnode;
if (import.meta.client && !shouldRender.value) {
if (instance.vnode.el) {
const fragment = getFragmentHTML(instance.vnode.el, false) ?? [];
vnode = createStaticVNode(fragment.join(""), fragment.length);
}
onMounted(async () => {
const triggers = [idleListener(), eventListeners()];
nuxtApp._delayHydrationPromise ??= Promise.race(
triggers.map((t) => t.promise)
).finally(() => {
for (const t of triggers) t.cleanup();
});
await nuxtApp._delayHydrationPromise;
shouldRender.value = true;
});
}
return () => shouldRender.value ? slots.default?.() : vnode;
}
});
function eventListeners() {
const abortController = new AbortController();
const promise = new Promise((resolve) => {
const listener = (e) => {
for (const e2 of delayHydrationOptions.hydrateOnEvents) window.removeEventListener(e2, listener);
requestAnimationFrame(() => resolve(e));
};
for (const e of delayHydrationOptions.hydrateOnEvents) {
window.addEventListener(e, listener, {
capture: true,
once: true,
passive: true,
signal: abortController.signal
});
}
});
return {
promise,
cleanup: () => abortController.abort()
};
}
function idleListener() {
let idleId;
const promise = new Promise((resolve) => {
const timeoutDelay = () => {
setTimeout(() => {
requestAnimationFrame(() => resolve());
}, delayHydrationOptions.postIdleTimeout);
};
idleId = requestIdleCallback(timeoutDelay, {
timeout: delayHydrationOptions.idleCallbackTimeout
});
});
return {
promise,
cleanup: () => cancelIdleCallback(idleId)
};
}