diff options
Diffstat (limited to 'packages/integrations/prefetch/src')
-rw-r--r-- | packages/integrations/prefetch/src/client.ts | 155 | ||||
-rw-r--r-- | packages/integrations/prefetch/src/index.ts | 19 | ||||
-rw-r--r-- | packages/integrations/prefetch/src/requestIdleCallback.ts | 16 |
3 files changed, 0 insertions, 190 deletions
diff --git a/packages/integrations/prefetch/src/client.ts b/packages/integrations/prefetch/src/client.ts deleted file mode 100644 index dc05cb84b..000000000 --- a/packages/integrations/prefetch/src/client.ts +++ /dev/null @@ -1,155 +0,0 @@ -/// <reference types="../@types/network-information.d.ts" /> -import throttles from 'throttles'; -import requestIdleCallback from './requestIdleCallback.js'; - -const events = ['mouseenter', 'touchstart', 'focus']; - -const preloaded = new Set<string>(); -const loadedStyles = new Set<string>(); - -function shouldPreload({ href }: { href: string }) { - try { - const url = new URL(href); - return ( - window.location.origin === url.origin && - window.location.pathname !== url.pathname && - !preloaded.has(href) - ); - } catch {} - - return false; -} - -let parser: DOMParser; -let observer: IntersectionObserver; - -function observe(link: HTMLAnchorElement) { - preloaded.add(link.href); - observer.observe(link); - events.map((event) => link.addEventListener(event, onLinkEvent, { passive: true, once: true })); -} - -function unobserve(link: HTMLAnchorElement) { - observer.unobserve(link); - events.map((event) => link.removeEventListener(event, onLinkEvent)); -} - -function onLinkEvent({ target }: Event) { - if (!(target instanceof HTMLAnchorElement)) { - return; - } - - preloadHref(target); -} - -async function preloadHref(link: HTMLAnchorElement) { - unobserve(link); - const { href } = link; - - try { - const contents = await fetch(href).then((res) => res.text()); - parser ||= new DOMParser(); - - const html = parser.parseFromString(contents, 'text/html'); - const styles = Array.from(html.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]')); - - await Promise.all( - styles - .filter((el) => !loadedStyles.has(el.href)) - .map((el) => { - loadedStyles.add(el.href); - return fetch(el.href); - }) - ); - } catch {} -} - -export interface PrefetchOptions { - /** - * Element selector used to find all links on the page that should be prefetched. - * - * @default 'a[href][rel~="prefetch"]' - */ - selector?: string; - /** - * The number of pages that can be prefetched concurrently. - * - * @default 1 - */ - throttle?: number; - /** - * Element selector used to find all links on the page that should be prefetched on user interaction. - * - * @default 'a[href][rel~="prefetch-intent"]' - */ - intentSelector?: string | string[]; -} - -export default function prefetch({ - selector = 'a[href][rel~="prefetch"]', - throttle = 1, - intentSelector = 'a[href][rel~="prefetch-intent"]', -}: PrefetchOptions) { - // If the navigator is offline, it is very unlikely that a request can be made successfully - if (!navigator.onLine) { - return Promise.reject(new Error('Cannot prefetch, no network connection')); - } - - // `Navigator.connection` is an experimental API and is not supported in every browser. - if ('connection' in navigator) { - const connection = (navigator as any).connection; - // Don't prefetch if Save-Data is enabled. - if (connection.saveData) { - return Promise.reject(new Error('Cannot prefetch, Save-Data is enabled')); - } - - // Do not prefetch if using 2G or 3G - if (/(2|3)g/.test(connection.effectiveType)) { - return Promise.reject(new Error('Cannot prefetch, network conditions are poor')); - } - } - - const [toAdd, isDone] = throttles(throttle); - - observer = - observer || - new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting && entry.target instanceof HTMLAnchorElement) { - const relAttributeValue = entry.target.getAttribute('rel') || ''; - let matchesIntentSelector = false; - // Check if intentSelector is an array - if (Array.isArray(intentSelector)) { - // If intentSelector is an array, use .some() to check for matches - matchesIntentSelector = intentSelector.some((intent) => - relAttributeValue.includes(intent) - ); - } else { - // If intentSelector is a string, use .includes() to check for a match - matchesIntentSelector = relAttributeValue.includes(intentSelector); - } - if (!matchesIntentSelector) { - toAdd(() => preloadHref(entry.target as HTMLAnchorElement).finally(isDone)); - } - } - }); - }); - - requestIdleCallback(() => { - const links = [...document.querySelectorAll<HTMLAnchorElement>(selector)].filter(shouldPreload); - links.forEach(observe); - - const intentSelectorFinal = Array.isArray(intentSelector) - ? intentSelector.join(',') - : intentSelector; - // Observe links with prefetch-intent - const intentLinks = [ - ...document.querySelectorAll<HTMLAnchorElement>(intentSelectorFinal), - ].filter(shouldPreload); - intentLinks.forEach((link) => { - events.map((event) => - link.addEventListener(event, onLinkEvent, { passive: true, once: true }) - ); - }); - }); -} diff --git a/packages/integrations/prefetch/src/index.ts b/packages/integrations/prefetch/src/index.ts deleted file mode 100644 index 74ec5186e..000000000 --- a/packages/integrations/prefetch/src/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { AstroIntegration } from 'astro'; -import type { PrefetchOptions } from './client.js'; - -export default function (options: PrefetchOptions = {}): AstroIntegration { - return { - name: '@astrojs/prefetch', - hooks: { - 'astro:config:setup': ({ injectScript }) => { - // Inject the necessary polyfills on every page (inlined for speed). - injectScript( - 'page', - `import prefetch from "@astrojs/prefetch/client.js"; prefetch(${JSON.stringify( - options - )});` - ); - }, - }, - }; -} diff --git a/packages/integrations/prefetch/src/requestIdleCallback.ts b/packages/integrations/prefetch/src/requestIdleCallback.ts deleted file mode 100644 index 9435bd41d..000000000 --- a/packages/integrations/prefetch/src/requestIdleCallback.ts +++ /dev/null @@ -1,16 +0,0 @@ -function shim(callback: IdleRequestCallback, options?: IdleRequestOptions) { - const timeout = options?.timeout ?? 50; - const start = Date.now(); - - return setTimeout(function () { - callback({ - didTimeout: false, - timeRemaining: function () { - return Math.max(0, timeout - (Date.now() - start)); - }, - }); - }, 1); -} - -const requestIdleCallback = window.requestIdleCallback || shim; -export default requestIdleCallback; |