summaryrefslogtreecommitdiff
path: root/packages/integrations/prefetch/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/integrations/prefetch/src')
-rw-r--r--packages/integrations/prefetch/src/client.ts155
-rw-r--r--packages/integrations/prefetch/src/index.ts19
-rw-r--r--packages/integrations/prefetch/src/requestIdleCallback.ts16
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;