const debug = import.meta.env.DEV ? console.debug : void 0; const inBrowser = import.meta.env.SSR === false; const prefetchedUrls = /* @__PURE__ */ new Set(); const listenedAnchors = /* @__PURE__ */ new WeakSet(); let prefetchAll = __PREFETCH_PREFETCH_ALL__; let defaultStrategy = __PREFETCH_DEFAULT_STRATEGY__; let clientPrerender = __EXPERIMENTAL_CLIENT_PRERENDER__; let inited = false; function init(defaultOpts) { if (!inBrowser) return; if (inited) return; inited = true; debug?.(`[astro] Initializing prefetch script`); prefetchAll ??= defaultOpts?.prefetchAll ?? false; defaultStrategy ??= defaultOpts?.defaultStrategy ?? "hover"; initTapStrategy(); initHoverStrategy(); initViewportStrategy(); initLoadStrategy(); } function initTapStrategy() { for (const event of ["touchstart", "mousedown"]) { document.body.addEventListener( event, (e) => { if (elMatchesStrategy(e.target, "tap")) { prefetch(e.target.href, { with: "fetch", ignoreSlowConnection: true }); } }, { passive: true } ); } } function initHoverStrategy() { let timeout; document.body.addEventListener( "focusin", (e) => { if (elMatchesStrategy(e.target, "hover")) { handleHoverIn(e); } }, { passive: true } ); document.body.addEventListener("focusout", handleHoverOut, { passive: true }); onPageLoad(() => { for (const anchor of document.getElementsByTagName("a")) { if (listenedAnchors.has(anchor)) continue; if (elMatchesStrategy(anchor, "hover")) { listenedAnchors.add(anchor); anchor.addEventListener("mouseenter", handleHoverIn, { passive: true }); anchor.addEventListener("mouseleave", handleHoverOut, { passive: true }); } } }); function handleHoverIn(e) { const href = e.target.href; if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { prefetch(href, { with: "fetch" }); }, 80); } function handleHoverOut() { if (timeout) { clearTimeout(timeout); timeout = 0; } } } function initViewportStrategy() { let observer; onPageLoad(() => { for (const anchor of document.getElementsByTagName("a")) { if (listenedAnchors.has(anchor)) continue; if (elMatchesStrategy(anchor, "viewport")) { listenedAnchors.add(anchor); observer ??= createViewportIntersectionObserver(); observer.observe(anchor); } } }); } function createViewportIntersectionObserver() { const timeouts = /* @__PURE__ */ new WeakMap(); return new IntersectionObserver((entries, observer) => { for (const entry of entries) { const anchor = entry.target; const timeout = timeouts.get(anchor); if (entry.isIntersecting) { if (timeout) { clearTimeout(timeout); } timeouts.set( anchor, setTimeout(() => { observer.unobserve(anchor); timeouts.delete(anchor); prefetch(anchor.href, { with: "link" }); }, 300) ); } else { if (timeout) { clearTimeout(timeout); timeouts.delete(anchor); } } } }); } function initLoadStrategy() { onPageLoad(() => { for (const anchor of document.getElementsByTagName("a")) { if (elMatchesStrategy(anchor, "load")) { prefetch(anchor.href, { with: "link" }); } } }); } function prefetch(url, opts) { const ignoreSlowConnection = opts?.ignoreSlowConnection ?? false; if (!canPrefetchUrl(url, ignoreSlowConnection)) return; prefetchedUrls.add(url); const priority = opts?.with ?? "link"; debug?.(`[astro] Prefetching ${url} with ${priority}`); if (clientPrerender && HTMLScriptElement.supports && HTMLScriptElement.supports("speculationrules")) { appendSpeculationRules(url); } else if (priority === "link") { const link = document.createElement("link"); link.rel = "prefetch"; link.setAttribute("href", url); document.head.append(link); } else { fetch(url).catch((e) => { console.log(`[astro] Failed to prefetch ${url}`); console.error(e); }); } } function canPrefetchUrl(url, ignoreSlowConnection) { if (!navigator.onLine) return false; if (!ignoreSlowConnection && isSlowConnection()) return false; try { const urlObj = new URL(url, location.href); return location.origin === urlObj.origin && (location.pathname !== urlObj.pathname || location.search !== urlObj.search) && !prefetchedUrls.has(url); } catch { } return false; } function elMatchesStrategy(el, strategy) { if (el?.tagName !== "A") return false; const attrValue = el.dataset.astroPrefetch; if (attrValue === "false") { return false; } if (strategy === "tap" && (attrValue != null || prefetchAll) && isSlowConnection()) { return true; } if (attrValue == null && prefetchAll || attrValue === "") { return strategy === defaultStrategy; } if (attrValue === strategy) { return true; } return false; } function isSlowConnection() { if ("connection" in navigator) { const conn = navigator.connection; return conn.saveData || /2g/.test(conn.effectiveType); } return false; } function onPageLoad(cb) { cb(); let firstLoad = false; document.addEventListener("astro:page-load", () => { if (!firstLoad) { firstLoad = true; return; } cb(); }); } function appendSpeculationRules(url) { const script = document.createElement("script"); script.type = "speculationrules"; script.textContent = JSON.stringify({ prerender: [ { source: "list", urls: [url] } ] }); document.head.append(script); } export { init, prefetch };