vue-preload-router-link
Version:
vue-router RouterLink with preload
128 lines (124 loc) • 3.83 kB
JavaScript
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 };