UNPKG

vue-preload-router-link

Version:

vue-router RouterLink with preload

128 lines (124 loc) 3.83 kB
import { defineComponent, ref, watch, watchEffect, h, resolveComponent, unref } from 'vue'; import { useRouter } from 'vue-router'; // src/index.ts async function preloadRouteComponents(to, router = useRouter()) { const { path, matched } = router.resolve(to); if (!matched.length) return; router._routePreloaded ?? (router._routePreloaded = /* @__PURE__ */ new Set()); if (router._routePreloaded.has(path)) return; const promises = router._preloadPromises = router._preloadPromises || []; if (promises.length > 4) { return Promise.all(promises).then(() => preloadRouteComponents(to, router)); } const components = matched.map((m) => m.components?.default).filter((component) => typeof component === "function"); for (const component of components) { const promise = Promise.resolve(component()).catch(() => { }).finally(() => promises.splice(promises.indexOf(promise))); promises.push(promise); } await Promise.all(promises); } function useObserver() { let observer = null; const callbacks = /* @__PURE__ */ new Map(); return function observe(el, callback) { if (!observer) { observer = new IntersectionObserver((entries) => { for (const entry of entries) { const callback2 = callbacks.get(entry.target); const isVisible = entry.isIntersecting || entry.intersectionRatio > 0; if (callback2 && isVisible) callback2(); } }); } callbacks.set(el, callback); observer.observe(el); return () => { callbacks.delete(el); observer?.unobserve(el); if (callbacks.size === 0) { observer?.disconnect(); observer = null; } }; }; } // src/index.ts var src_default = defineComponent({ name: "PreloadRouterLink", props: { to: { type: [String, Object], required: true }, prefetch: { type: Boolean, default: true, required: false }, prefetchOn: { type: String, default: "visibility", required: false }, prefetchedClass: { type: String, default: "", required: false } }, setup(props, { slots }) { const router = useRouter(); const prefetched = ref(false); const el = ref(null); const linkProps = ref({ ...props, ref(ref2) { el.value = ref2?.$el; } }); function shouldPrefetch(mode) { return !prefetched.value && props.prefetch === true && props.prefetchOn === mode; } async function prefetch() { if (prefetched.value) return; prefetched.value = true; await preloadRouteComponents(props.to, router).catch(() => { }); } watch(prefetched, (newVal) => { if (newVal) { linkProps.value.class = props.prefetchedClass; } }); watchEffect((onCleanup) => { if (!shouldPrefetch("visibility")) return; let idleId; let unobserve = null; onCleanup(() => { if (idleId) cancelIdleCallback(idleId); unobserve?.(); }); const observe = useObserver(); idleId = requestIdleCallback(() => { if (el.value?.tagName) { unobserve = observe(el.value, async () => { unobserve?.(); await prefetch(); }); } }); }); watchEffect((onCleanup) => { if (!shouldPrefetch("interaction")) return; onCleanup(() => { if (linkProps.value.onPointerenter) delete linkProps.value.onPointerenter; if (linkProps.value.onFocus) delete linkProps.value.onFocus; }); linkProps.value.onPointerenter = prefetch.bind(null); linkProps.value.onFocus = prefetch.bind(null); }); return () => h(resolveComponent("RouterLink"), unref(linkProps), slots.default); } }); export { src_default as default, preloadRouteComponents };