From 49a212f837b80b01ef11dbfdf89e05c47f8102bf Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Thu, 9 May 2024 13:33:41 +0200 Subject: [PATCH 01/17] add isCrossorigin attribute to prefetch method --- src/index.mjs | 44 +++++++++++++++++++++++--------------------- src/prefetch.mjs | 9 ++++++--- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/index.mjs b/src/index.mjs index 0872cc7c..c34ede54 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -15,9 +15,9 @@ **/ import throttle from 'throttles'; -import {priority, supported} from './prefetch.mjs'; +import { priority, supported } from './prefetch.mjs'; import requestIdleCallback from './request-idle-callback.mjs'; -import {addSpeculationRules, hasSpecRulesSupport} from './prerender.mjs'; +import { addSpeculationRules, hasSpecRulesSupport } from './prerender.mjs'; // Cache of URLs we've prefetched // Its `size` is compared against `opts.limit` value. @@ -148,15 +148,15 @@ export function listen(options = {}) { if (toPrefetch.size < limit && !shouldOnlyPrerender) { toAdd(() => { prefetch(hrefFn ? hrefFn(entry) : entry.href, options.priority) - .then(isDone) - .catch(error => { - isDone(); - if (options.onError) options.onError(error); - }); + .then(isDone) + .catch(error => { + isDone(); + if (options.onError) options.onError(error); + }); }); } }, delay); - // On exit + // On exit } else { entry = entry.target; const index = hrefsInViewport.indexOf(entry.href); @@ -172,9 +172,9 @@ export function listen(options = {}) { timeoutFn(() => { // Find all links & Connect them to IO if allowed const elementsToListen = options.el && - options.el.length && - options.el.length > 0 && - options.el[0].nodeName === 'A' ? + options.el.length && + options.el.length > 0 && + options.el[0].nodeName === 'A' ? options.el : (options.el || document).querySelectorAll('a'); @@ -204,7 +204,7 @@ export function listen(options = {}) { * @param {Boolean} [isPriority] - if is "high" priority * @return {Object} a Promise */ -export function prefetch(url, isPriority) { +export function prefetch(url, isPriority, isCrossorigin) { const chkConn = checkConnection(navigator.connection); if (chkConn instanceof Error) { return Promise.reject(new Error(`Cannot prefetch, ${chkConn.message}`)); @@ -214,19 +214,21 @@ export function prefetch(url, isPriority) { console.warn('[Warning] You are using both prefetching and prerendering on the same document'); } + const crossorigin = isCrossorigin ? "anonymous" : "use-credentials"; + // Dev must supply own catch() return Promise.all( - [].concat(url).map(str => { - if (toPrefetch.has(str)) return []; + [].concat(url).map(str => { + if (toPrefetch.has(str)) return []; - // Add it now, regardless of its success - // ~> so that we don't repeat broken links - toPrefetch.add(str); + // Add it now, regardless of its success + // ~> so that we don't repeat broken links + toPrefetch.add(str); - return (isPriority ? priority : supported)( - new URL(str, location.href).toString(), - ); - }), + return (isPriority ? priority : supported)( + new URL(str, location.href).toString(), crossorigin + ); + }), ); } diff --git a/src/prefetch.mjs b/src/prefetch.mjs index 8a4aeb19..57dbcff4 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -33,11 +33,12 @@ function hasPrefetch(link) { * @param {string} url - the URL to fetch * @return {Object} a Promise */ -function viaDOM(url) { +function viaDOM(url, crossorigin) { return new Promise((resolve, reject, link) => { link = document.createElement('link'); link.rel = 'prefetch'; link.href = url; + link.setAttribute("crossorigin", crossorigin); link.onload = resolve; link.onerror = reject; @@ -76,7 +77,7 @@ function viaXHR(url) { * @param {string} url - the URL to fetch * @return {Object} a Promise */ -export function priority(url) { +export function priority(url, crossorigin) { // TODO: Investigate using preload for high-priority // fetches. May have to sniff file-extension to provide // valid 'as' values. In the future, we may be able to @@ -84,7 +85,9 @@ export function priority(url) { // // As of 2018, fetch() is high-priority in Chrome // and medium-priority in Safari. - return window.fetch ? fetch(url, {credentials: 'include'}) : viaXHR(url); + const options = {}; + if (crossorigin === 'use-credentials') options.credentials = 'include'; + return window.fetch ? fetch(url, options) : viaXHR(url); } export const supported = hasPrefetch() ? viaDOM : viaXHR; From fdd7d239b344d50c06e7a40a32df9fc1ba940135 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Fri, 25 Oct 2024 11:12:46 +0200 Subject: [PATCH 02/17] added javascript doc --- src/index.mjs | 1 + src/prefetch.mjs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/index.mjs b/src/index.mjs index c34ede54..31491ecb 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -202,6 +202,7 @@ export function listen(options = {}) { * Prefetch a given URL with an optional preferred fetch priority * @param {String} url - the URL to fetch * @param {Boolean} [isPriority] - if is "high" priority +* @param {Boolean} [isCrossorigin] - true to set crossorigin="anonymous" * @return {Object} a Promise */ export function prefetch(url, isPriority, isCrossorigin) { diff --git a/src/prefetch.mjs b/src/prefetch.mjs index 57dbcff4..dd2a36af 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -31,6 +31,7 @@ function hasPrefetch(link) { /** * Fetches a given URL using `` * @param {string} url - the URL to fetch + * @param {string} url - the value of crossorigin attribute * @return {Object} a Promise */ function viaDOM(url, crossorigin) { @@ -75,6 +76,7 @@ function viaXHR(url) { * Fetches a given URL using the Fetch API. Falls back * to XMLHttpRequest if the API is not supported. * @param {string} url - the URL to fetch + * @param {string} url - the value of crossorigin attribute * @return {Object} a Promise */ export function priority(url, crossorigin) { From 41c8df514f84dc5e62bdb665adc7b5d8a6e4f679 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Fri, 25 Oct 2024 14:50:46 +0200 Subject: [PATCH 03/17] crossorigin call from fetch and prefetch --- src/index.mjs | 9 ++++----- src/prefetch.mjs | 18 +++++++++++------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/index.mjs b/src/index.mjs index 31491ecb..d339241d 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -202,10 +202,11 @@ export function listen(options = {}) { * Prefetch a given URL with an optional preferred fetch priority * @param {String} url - the URL to fetch * @param {Boolean} [isPriority] - if is "high" priority -* @param {Boolean} [isCrossorigin] - true to set crossorigin="anonymous" +* @param {Boolean} [checkAccessControlAllowOrigin] - true to set crossorigin="anonymous" for DOM prefetch +* and mode:'cors' for API fetch * @return {Object} a Promise */ -export function prefetch(url, isPriority, isCrossorigin) { +export function prefetch(url, isPriority, checkAccessControlAllowOrigin) { const chkConn = checkConnection(navigator.connection); if (chkConn instanceof Error) { return Promise.reject(new Error(`Cannot prefetch, ${chkConn.message}`)); @@ -215,8 +216,6 @@ export function prefetch(url, isPriority, isCrossorigin) { console.warn('[Warning] You are using both prefetching and prerendering on the same document'); } - const crossorigin = isCrossorigin ? "anonymous" : "use-credentials"; - // Dev must supply own catch() return Promise.all( [].concat(url).map(str => { @@ -227,7 +226,7 @@ export function prefetch(url, isPriority, isCrossorigin) { toPrefetch.add(str); return (isPriority ? priority : supported)( - new URL(str, location.href).toString(), crossorigin + new URL(str, location.href).toString(), checkAccessControlAllowOrigin ); }), ); diff --git a/src/prefetch.mjs b/src/prefetch.mjs index dd2a36af..395f823a 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -31,15 +31,16 @@ function hasPrefetch(link) { /** * Fetches a given URL using `` * @param {string} url - the URL to fetch - * @param {string} url - the value of crossorigin attribute + * @param {Boolean} [hasCrossorigin] - true to set crossorigin="anonymous" * @return {Object} a Promise */ -function viaDOM(url, crossorigin) { +function viaDOM(url, hasCrossorigin) { return new Promise((resolve, reject, link) => { link = document.createElement('link'); link.rel = 'prefetch'; link.href = url; - link.setAttribute("crossorigin", crossorigin); + if (hasCrossorigin) + link.setAttribute("crossorigin", "anonymous"); link.onload = resolve; link.onerror = reject; @@ -76,10 +77,10 @@ function viaXHR(url) { * Fetches a given URL using the Fetch API. Falls back * to XMLHttpRequest if the API is not supported. * @param {string} url - the URL to fetch - * @param {string} url - the value of crossorigin attribute + * @param {Boolean} [hasModeCors] - true to set mode:'cors' * @return {Object} a Promise */ -export function priority(url, crossorigin) { +export function priority(url, hasModeCors) { // TODO: Investigate using preload for high-priority // fetches. May have to sniff file-extension to provide // valid 'as' values. In the future, we may be able to @@ -87,8 +88,11 @@ export function priority(url, crossorigin) { // // As of 2018, fetch() is high-priority in Chrome // and medium-priority in Safari. - const options = {}; - if (crossorigin === 'use-credentials') options.credentials = 'include'; + options = {}; + // options.credentials = 'include'; + if (!hasModeCors) { + options.mode = 'no-cors'; + } return window.fetch ? fetch(url, options) : viaXHR(url); } From 1f2aa4357a331aa8e21836c54e03e5ff2fb8b17e Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Fri, 25 Oct 2024 17:55:14 +0200 Subject: [PATCH 04/17] removed xhr and added Access-Control-Allow-Origin header check --- src/index.mjs | 9 +++++---- src/prefetch.mjs | 42 +++++++++--------------------------------- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/src/index.mjs b/src/index.mjs index d339241d..7f7fe8bf 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -15,7 +15,7 @@ **/ import throttle from 'throttles'; -import { priority, supported } from './prefetch.mjs'; +import { supported, viaFetch } from './prefetch.mjs'; import requestIdleCallback from './request-idle-callback.mjs'; import { addSpeculationRules, hasSpecRulesSupport } from './prerender.mjs'; @@ -72,6 +72,7 @@ function checkConnection(conn) { * @param {Object} options - Configuration options for quicklink * @param {Object|Array} [options.el] - DOM element(s) to prefetch in-viewport links of * @param {Boolean} [options.priority] - Attempt higher priority fetch (low or high) + * @param {Boolean} [options.checkAccessControlAllowOrigin] - Check if the Access-Control-Allow-Origin response header is correctly setted * @param {Array} [options.origins] - Allowed origins to prefetch (empty allows all) * @param {Array|RegExp|Function} [options.ignores] - Custom filter(s) that run after origin checks * @param {Number} [options.timeout] - Timeout after which prefetching will occur @@ -147,7 +148,7 @@ export function listen(options = {}) { // Do not prefetch if will match/exceed limit and user has not switched to shouldOnlyPrerender mode if (toPrefetch.size < limit && !shouldOnlyPrerender) { toAdd(() => { - prefetch(hrefFn ? hrefFn(entry) : entry.href, options.priority) + prefetch(hrefFn ? hrefFn(entry) : entry.href, options.priority, options.checkAccessControlAllowOrigin) .then(isDone) .catch(error => { isDone(); @@ -225,8 +226,8 @@ export function prefetch(url, isPriority, checkAccessControlAllowOrigin) { // ~> so that we don't repeat broken links toPrefetch.add(str); - return (isPriority ? priority : supported)( - new URL(str, location.href).toString(), checkAccessControlAllowOrigin + return (isPriority ? viaFetch : supported)( + new URL(str, location.href).toString(), checkAccessControlAllowOrigin, isPriority ); }), ); diff --git a/src/prefetch.mjs b/src/prefetch.mjs index 395f823a..d1464cd8 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -20,7 +20,7 @@ /** * Checks if a feature on `link` is natively supported. * Examples of features include `prefetch` and `preload`. - * @param {Object} link Link object. + * @param {Object} link - Link object. * @return {Boolean} whether the feature is supported */ function hasPrefetch(link) { @@ -40,7 +40,7 @@ function viaDOM(url, hasCrossorigin) { link.rel = 'prefetch'; link.href = url; if (hasCrossorigin) - link.setAttribute("crossorigin", "anonymous"); + link.setAttribute('crossorigin', 'anonymous'); link.onload = resolve; link.onerror = reject; @@ -49,38 +49,15 @@ function viaDOM(url, hasCrossorigin) { }); } -/** - * Fetches a given URL using XMLHttpRequest - * @param {string} url - the URL to fetch - * @return {Object} a Promise - */ -function viaXHR(url) { - return new Promise((resolve, reject, request) => { - request = new XMLHttpRequest(); - - request.open('GET', url, request.withCredentials = true); - - request.onload = () => { - if (request.status === 200) { - resolve(); - } else { - // eslint-disable-next-line prefer-promise-reject-errors - reject(); - } - }; - - request.send(); - }); -} - /** * Fetches a given URL using the Fetch API. Falls back * to XMLHttpRequest if the API is not supported. * @param {string} url - the URL to fetch - * @param {Boolean} [hasModeCors] - true to set mode:'cors' + * @param {Boolean} hasModeCors - true to set mode:'cors' + * @param {Boolean} isPriority - true to set priority:'high' * @return {Object} a Promise */ -export function priority(url, hasModeCors) { +export function viaFetch(url, hasModeCors, isPriority) { // TODO: Investigate using preload for high-priority // fetches. May have to sniff file-extension to provide // valid 'as' values. In the future, we may be able to @@ -90,10 +67,9 @@ export function priority(url, hasModeCors) { // and medium-priority in Safari. options = {}; // options.credentials = 'include'; - if (!hasModeCors) { - options.mode = 'no-cors'; - } - return window.fetch ? fetch(url, options) : viaXHR(url); + if (!hasModeCors) options.mode = 'no-cors'; + isPriority ? options.priority = 'high' : options.priority = 'low'; + return fetch(url, options); } -export const supported = hasPrefetch() ? viaDOM : viaXHR; +export const supported = hasPrefetch() ? viaDOM : viaFetch; From b0cf0d5b143b4d08923c1e368a80a6696aa167d4 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Fri, 25 Oct 2024 17:56:12 +0200 Subject: [PATCH 05/17] javascript doc fixes --- src/index.mjs | 4 ++-- src/prefetch.mjs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.mjs b/src/index.mjs index 7f7fe8bf..aba04a29 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -202,8 +202,8 @@ export function listen(options = {}) { /** * Prefetch a given URL with an optional preferred fetch priority * @param {String} url - the URL to fetch -* @param {Boolean} [isPriority] - if is "high" priority -* @param {Boolean} [checkAccessControlAllowOrigin] - true to set crossorigin="anonymous" for DOM prefetch +* @param {Boolean} isPriority - if is "high" priority +* @param {Boolean} checkAccessControlAllowOrigin - true to set crossorigin="anonymous" for DOM prefetch * and mode:'cors' for API fetch * @return {Object} a Promise */ diff --git a/src/prefetch.mjs b/src/prefetch.mjs index d1464cd8..df18b68e 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -31,7 +31,7 @@ function hasPrefetch(link) { /** * Fetches a given URL using `` * @param {string} url - the URL to fetch - * @param {Boolean} [hasCrossorigin] - true to set crossorigin="anonymous" + * @param {Boolean} hasCrossorigin - true to set crossorigin="anonymous" * @return {Object} a Promise */ function viaDOM(url, hasCrossorigin) { From 7cd537176a9225b6cc493169981ea363ae907088 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Fri, 25 Oct 2024 20:04:59 +0200 Subject: [PATCH 06/17] Add attribute to manage AccessControllAllowCredentials --- src/index.mjs | 8 +++++--- src/prefetch.mjs | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/index.mjs b/src/index.mjs index aba04a29..4a9cfd1f 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -73,6 +73,7 @@ function checkConnection(conn) { * @param {Object|Array} [options.el] - DOM element(s) to prefetch in-viewport links of * @param {Boolean} [options.priority] - Attempt higher priority fetch (low or high) * @param {Boolean} [options.checkAccessControlAllowOrigin] - Check if the Access-Control-Allow-Origin response header is correctly setted + * @param {Boolean} [options.checkAccessControlAllowCredentials] - Check if the Access-Control-Allow-Credentials response header is correctly setted * @param {Array} [options.origins] - Allowed origins to prefetch (empty allows all) * @param {Array|RegExp|Function} [options.ignores] - Custom filter(s) that run after origin checks * @param {Number} [options.timeout] - Timeout after which prefetching will occur @@ -148,7 +149,7 @@ export function listen(options = {}) { // Do not prefetch if will match/exceed limit and user has not switched to shouldOnlyPrerender mode if (toPrefetch.size < limit && !shouldOnlyPrerender) { toAdd(() => { - prefetch(hrefFn ? hrefFn(entry) : entry.href, options.priority, options.checkAccessControlAllowOrigin) + prefetch(hrefFn ? hrefFn(entry) : entry.href, options.priority, options.checkAccessControlAllowOrigin, options.checkAccessControlAllowCredentials) .then(isDone) .catch(error => { isDone(); @@ -205,9 +206,10 @@ export function listen(options = {}) { * @param {Boolean} isPriority - if is "high" priority * @param {Boolean} checkAccessControlAllowOrigin - true to set crossorigin="anonymous" for DOM prefetch * and mode:'cors' for API fetch +* @param {Boolean} checkAccessControlAllowCredentials - true to set credentials:'include' for API fetch * @return {Object} a Promise */ -export function prefetch(url, isPriority, checkAccessControlAllowOrigin) { +export function prefetch(url, isPriority, checkAccessControlAllowOrigin, checkAccessControlAllowCredentials) { const chkConn = checkConnection(navigator.connection); if (chkConn instanceof Error) { return Promise.reject(new Error(`Cannot prefetch, ${chkConn.message}`)); @@ -227,7 +229,7 @@ export function prefetch(url, isPriority, checkAccessControlAllowOrigin) { toPrefetch.add(str); return (isPriority ? viaFetch : supported)( - new URL(str, location.href).toString(), checkAccessControlAllowOrigin, isPriority + new URL(str, location.href).toString(), checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, isPriority ); }), ); diff --git a/src/prefetch.mjs b/src/prefetch.mjs index df18b68e..bb281024 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -49,15 +49,41 @@ function viaDOM(url, hasCrossorigin) { }); } +/** + * Fetches a given URL using XMLHttpRequest + * @param {string} url - the URL to fetch + * @param {Boolean} hasCredentials - true to set withCredentials:true + * @return {Object} a Promise + */ +function viaXHR(url, hasCredentials) { + return new Promise((resolve, reject, request) => { + request = new XMLHttpRequest(); + + request.open('GET', url, request.withCredentials = hasCredentials); + + request.onload = () => { + if (request.status === 200) { + resolve(); + } else { + // eslint-disable-next-line prefer-promise-reject-errors + reject(); + } + }; + + request.send(); + }); +} + /** * Fetches a given URL using the Fetch API. Falls back * to XMLHttpRequest if the API is not supported. * @param {string} url - the URL to fetch * @param {Boolean} hasModeCors - true to set mode:'cors' + * @param {Boolean} hasCredentials - true to set credentials:'include' * @param {Boolean} isPriority - true to set priority:'high' * @return {Object} a Promise */ -export function viaFetch(url, hasModeCors, isPriority) { +export function viaFetch(url, hasModeCors, hasCredentials, isPriority) { // TODO: Investigate using preload for high-priority // fetches. May have to sniff file-extension to provide // valid 'as' values. In the future, we may be able to @@ -66,10 +92,14 @@ export function viaFetch(url, hasModeCors, isPriority) { // As of 2018, fetch() is high-priority in Chrome // and medium-priority in Safari. options = {}; - // options.credentials = 'include'; if (!hasModeCors) options.mode = 'no-cors'; + if (hasCredentials) options.credentials = 'include'; isPriority ? options.priority = 'high' : options.priority = 'low'; - return fetch(url, options); + try { + return fetch(url, options); + } catch (e) { + return viaXHR(url, hasCredentials); + } } export const supported = hasPrefetch() ? viaDOM : viaFetch; From 008741998b695367be6681ad4fb304d69918225b Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Fri, 25 Oct 2024 20:27:54 +0200 Subject: [PATCH 07/17] 2.3.0 --- src/chunks.mjs | 34 +++++++++++++++++----------------- src/index.mjs | 41 +++++++++++++++++++++-------------------- src/prefetch.mjs | 3 ++- test/quicklink.spec.js | 4 ++-- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/chunks.mjs b/src/chunks.mjs index aa36c2be..ff3c1baa 100644 --- a/src/chunks.mjs +++ b/src/chunks.mjs @@ -15,7 +15,7 @@ **/ import throttle from 'throttles'; -import {priority, supported} from './prefetch.mjs'; +import { viaFetch, supported } from './prefetch.mjs'; import requestIdleCallback from './request-idle-callback.mjs'; // Cache of URLs we've prefetched @@ -67,15 +67,15 @@ export function listen(options = {}) { const timeoutFn = options.timeoutFn || requestIdleCallback; - const {prefetchChunks} = options; + const { prefetchChunks } = options; const prefetchHandler = urls => { prefetch(urls, options.priority) - .then(isDone) - .catch(error => { - isDone(); - if (options.onError) options.onError(error); - }); + .then(isDone) + .catch(error => { + isDone(); + if (options.onError) options.onError(error); + }); }; const observer = new IntersectionObserver(entries => { @@ -123,7 +123,7 @@ export function listen(options = {}) { * @return {Object} a Promise */ export function prefetch(url, isPriority) { - const {connection} = navigator; + const { connection } = navigator; if (connection) { // Don't prefetch if using 2G or if Save-Data is enabled. @@ -138,16 +138,16 @@ export function prefetch(url, isPriority) { // Dev must supply own catch() return Promise.all( - [].concat(url).map(str => { - if (toPrefetch.has(str)) return []; + [].concat(url).map(str => { + if (toPrefetch.has(str)) return []; - // Add it now, regardless of its success - // ~> so that we don't repeat broken links - toPrefetch.add(str); + // Add it now, regardless of its success + // ~> so that we don't repeat broken links + toPrefetch.add(str); - return (isPriority ? priority : supported)( - new URL(str, location.href).toString(), - ); - }), + return (isPriority ? viaFetch : supported)( + new URL(str, location.href).toString(), + ); + }), ); } diff --git a/src/index.mjs b/src/index.mjs index 4a9cfd1f..81f34b6f 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -15,9 +15,9 @@ **/ import throttle from 'throttles'; -import { supported, viaFetch } from './prefetch.mjs'; +import {supported, viaFetch} from './prefetch.mjs'; import requestIdleCallback from './request-idle-callback.mjs'; -import { addSpeculationRules, hasSpecRulesSupport } from './prerender.mjs'; +import {addSpeculationRules, hasSpecRulesSupport} from './prerender.mjs'; // Cache of URLs we've prefetched // Its `size` is compared against `opts.limit` value. @@ -72,8 +72,8 @@ function checkConnection(conn) { * @param {Object} options - Configuration options for quicklink * @param {Object|Array} [options.el] - DOM element(s) to prefetch in-viewport links of * @param {Boolean} [options.priority] - Attempt higher priority fetch (low or high) - * @param {Boolean} [options.checkAccessControlAllowOrigin] - Check if the Access-Control-Allow-Origin response header is correctly setted - * @param {Boolean} [options.checkAccessControlAllowCredentials] - Check if the Access-Control-Allow-Credentials response header is correctly setted + * @param {Boolean} [options.checkAccessControlAllowOrigin] - Check Access-Control-Allow-Origin response header + * @param {Boolean} [options.checkAccessControlAllowCredentials] - Check the Access-Control-Allow-Credentials response header * @param {Array} [options.origins] - Allowed origins to prefetch (empty allows all) * @param {Array|RegExp|Function} [options.ignores] - Custom filter(s) that run after origin checks * @param {Number} [options.timeout] - Timeout after which prefetching will occur @@ -149,12 +149,13 @@ export function listen(options = {}) { // Do not prefetch if will match/exceed limit and user has not switched to shouldOnlyPrerender mode if (toPrefetch.size < limit && !shouldOnlyPrerender) { toAdd(() => { - prefetch(hrefFn ? hrefFn(entry) : entry.href, options.priority, options.checkAccessControlAllowOrigin, options.checkAccessControlAllowCredentials) - .then(isDone) - .catch(error => { - isDone(); - if (options.onError) options.onError(error); - }); + prefetch(hrefFn ? hrefFn(entry) : entry.href, options.priority, + options.checkAccessControlAllowOrigin, options.checkAccessControlAllowCredentials) + .then(isDone) + .catch(error => { + isDone(); + if (options.onError) options.onError(error); + }); }); } }, delay); @@ -204,7 +205,7 @@ export function listen(options = {}) { * Prefetch a given URL with an optional preferred fetch priority * @param {String} url - the URL to fetch * @param {Boolean} isPriority - if is "high" priority -* @param {Boolean} checkAccessControlAllowOrigin - true to set crossorigin="anonymous" for DOM prefetch +* @param {Boolean} checkAccessControlAllowOrigin - true to set crossorigin="anonymous" for DOM prefetch * and mode:'cors' for API fetch * @param {Boolean} checkAccessControlAllowCredentials - true to set credentials:'include' for API fetch * @return {Object} a Promise @@ -221,17 +222,17 @@ export function prefetch(url, isPriority, checkAccessControlAllowOrigin, checkAc // Dev must supply own catch() return Promise.all( - [].concat(url).map(str => { - if (toPrefetch.has(str)) return []; + [].concat(url).map(str => { + if (toPrefetch.has(str)) return []; - // Add it now, regardless of its success - // ~> so that we don't repeat broken links - toPrefetch.add(str); + // Add it now, regardless of its success + // ~> so that we don't repeat broken links + toPrefetch.add(str); - return (isPriority ? viaFetch : supported)( - new URL(str, location.href).toString(), checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, isPriority - ); - }), + return (isPriority ? viaFetch : supported)( + new URL(str, location.href).toString(), checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, isPriority, + ); + }), ); } diff --git a/src/prefetch.mjs b/src/prefetch.mjs index bb281024..8c8aa01e 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -39,8 +39,9 @@ function viaDOM(url, hasCrossorigin) { link = document.createElement('link'); link.rel = 'prefetch'; link.href = url; - if (hasCrossorigin) + if (hasCrossorigin) { link.setAttribute('crossorigin', 'anonymous'); + } link.onload = resolve; link.onerror = reject; diff --git a/test/quicklink.spec.js b/test/quicklink.spec.js index 74a26d3b..525e9f44 100644 --- a/test/quicklink.spec.js +++ b/test/quicklink.spec.js @@ -1,7 +1,7 @@ 'use strict'; const puppeteer = require('puppeteer'); -const {suite} = require('uvu'); +const { suite } = require('uvu'); const assert = require('uvu/assert'); const host = 'http://127.0.0.1:8080'; @@ -269,7 +269,7 @@ mainSuite('should respect the `throttle` concurrency', async context => { if (/test\/fixtures\/\d+\.html$/i.test(url)) { await sleep(100); URLs.push(url); - return req.respond({status: 200}); + return req.respond({ status: 200 }); } req.continue(); From b53152dec1f9ca8cd7bd4af02e474d3ec473650b Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Fri, 25 Oct 2024 20:33:47 +0200 Subject: [PATCH 08/17] 2.4.0 --- package.json | 4 ++-- src/chunks.mjs | 34 +++++++++++++++++----------------- test/quicklink.spec.js | 4 ++-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 4a92ea07..bb185a2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "quicklink", - "version": "2.3.0", + "version": "2.4.0", "description": "Faster subsequent page-loads by prefetching in-viewport links during idle time", "repository": { "type": "git", @@ -71,4 +71,4 @@ "size-limit": "^11.1.2", "uvu": "^0.5.6" } -} +} \ No newline at end of file diff --git a/src/chunks.mjs b/src/chunks.mjs index ff3c1baa..de78fe30 100644 --- a/src/chunks.mjs +++ b/src/chunks.mjs @@ -15,7 +15,7 @@ **/ import throttle from 'throttles'; -import { viaFetch, supported } from './prefetch.mjs'; +import {viaFetch, supported} from './prefetch.mjs'; import requestIdleCallback from './request-idle-callback.mjs'; // Cache of URLs we've prefetched @@ -67,15 +67,15 @@ export function listen(options = {}) { const timeoutFn = options.timeoutFn || requestIdleCallback; - const { prefetchChunks } = options; + const {prefetchChunks} = options; const prefetchHandler = urls => { prefetch(urls, options.priority) - .then(isDone) - .catch(error => { - isDone(); - if (options.onError) options.onError(error); - }); + .then(isDone) + .catch(error => { + isDone(); + if (options.onError) options.onError(error); + }); }; const observer = new IntersectionObserver(entries => { @@ -123,7 +123,7 @@ export function listen(options = {}) { * @return {Object} a Promise */ export function prefetch(url, isPriority) { - const { connection } = navigator; + const {connection} = navigator; if (connection) { // Don't prefetch if using 2G or if Save-Data is enabled. @@ -138,16 +138,16 @@ export function prefetch(url, isPriority) { // Dev must supply own catch() return Promise.all( - [].concat(url).map(str => { - if (toPrefetch.has(str)) return []; + [].concat(url).map(str => { + if (toPrefetch.has(str)) return []; - // Add it now, regardless of its success - // ~> so that we don't repeat broken links - toPrefetch.add(str); + // Add it now, regardless of its success + // ~> so that we don't repeat broken links + toPrefetch.add(str); - return (isPriority ? viaFetch : supported)( - new URL(str, location.href).toString(), - ); - }), + return (isPriority ? viaFetch : supported)( + new URL(str, location.href).toString(), + ); + }), ); } diff --git a/test/quicklink.spec.js b/test/quicklink.spec.js index 525e9f44..74a26d3b 100644 --- a/test/quicklink.spec.js +++ b/test/quicklink.spec.js @@ -1,7 +1,7 @@ 'use strict'; const puppeteer = require('puppeteer'); -const { suite } = require('uvu'); +const {suite} = require('uvu'); const assert = require('uvu/assert'); const host = 'http://127.0.0.1:8080'; @@ -269,7 +269,7 @@ mainSuite('should respect the `throttle` concurrency', async context => { if (/test\/fixtures\/\d+\.html$/i.test(url)) { await sleep(100); URLs.push(url); - return req.respond({ status: 200 }); + return req.respond({status: 200}); } req.continue(); From bcb74c855d5a528df6066ea5953174d8be977ba4 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Mon, 28 Oct 2024 10:58:09 +0100 Subject: [PATCH 09/17] removed exception handler --- src/prefetch.mjs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/prefetch.mjs b/src/prefetch.mjs index 8c8aa01e..f19d9162 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -96,11 +96,7 @@ export function viaFetch(url, hasModeCors, hasCredentials, isPriority) { if (!hasModeCors) options.mode = 'no-cors'; if (hasCredentials) options.credentials = 'include'; isPriority ? options.priority = 'high' : options.priority = 'low'; - try { - return fetch(url, options); - } catch (e) { - return viaXHR(url, hasCredentials); - } + return window.fetch ? fetch(url, options) : viaXHR(url, hasCredentials); } export const supported = hasPrefetch() ? viaDOM : viaFetch; From 281255ce6ee693c0df894fd254cecf6d85db7115 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Tue, 29 Oct 2024 14:32:09 +0100 Subject: [PATCH 10/17] 2.4.0 --- src/prefetch.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/prefetch.mjs b/src/prefetch.mjs index f19d9162..b78c5015 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -62,6 +62,8 @@ function viaXHR(url, hasCredentials) { request.open('GET', url, request.withCredentials = hasCredentials); + request.setRequestHeader('Accept', '*/*'); + request.onload = () => { if (request.status === 200) { resolve(); @@ -92,7 +94,7 @@ export function viaFetch(url, hasModeCors, hasCredentials, isPriority) { // // As of 2018, fetch() is high-priority in Chrome // and medium-priority in Safari. - options = {}; + options = {headers: {accept: '*/*'}}; if (!hasModeCors) options.mode = 'no-cors'; if (hasCredentials) options.credentials = 'include'; isPriority ? options.priority = 'high' : options.priority = 'low'; From 34e09807aecc02dbea31327a60ef22f3f61fd9fe Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Tue, 26 Nov 2024 16:20:40 +0100 Subject: [PATCH 11/17] Added prerender eagerness property --- src/index.mjs | 8 +++++--- src/prerender.mjs | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/index.mjs b/src/index.mjs index 81f34b6f..ec36ee7f 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -86,6 +86,7 @@ function checkConnection(conn) { * @param {Function} [options.hrefFn] - Function to use to build the URL to prefetch. * If it's not a valid function, then it will use the entry href. * @param {Boolean} [options.prerender] - Option to switch from prefetching and use prerendering only + * @param {String} [options.eagerness] - Prerender eagerness mode - default immediate * @param {Boolean} [options.prerenderAndPrefetch] - Option to use both prerendering and prefetching * @return {Function} */ @@ -135,7 +136,7 @@ export function listen(options = {}) { // either it's the prerender + prefetch mode or it's prerender *only* mode // Prerendering limit is following options.limit. UA may impose arbitraty numeric limit if ((shouldPrerenderAndPrefetch || shouldOnlyPrerender) && toPrerender.size < limit) { - prerender(hrefFn ? hrefFn(entry) : entry.href).catch(error => { + prerender(hrefFn ? hrefFn(entry) : entry.href, options.eagerness).catch(error => { if (options.onError) { options.onError(error); } else { @@ -239,9 +240,10 @@ export function prefetch(url, isPriority, checkAccessControlAllowOrigin, checkAc /** * Prerender a given URL * @param {String} urls - the URL to fetch +* @param {String} eagerness - prerender eagerness mode - default immediate * @return {Object} a Promise */ -export function prerender(urls) { +export function prerender(urls, eagerness) { const chkConn = checkConnection(navigator.connection); if (chkConn instanceof Error) { return Promise.reject(new Error(`Cannot prerender, ${chkConn.message}`)); @@ -264,6 +266,6 @@ export function prerender(urls) { console.warn('[Warning] You are using both prefetching and prerendering on the same document'); } - const addSpecRules = addSpeculationRules(toPrerender); + const addSpecRules = addSpeculationRules(toPrerender, eagerness); return addSpecRules === true ? Promise.resolve() : Promise.reject(addSpecRules); } diff --git a/src/prerender.mjs b/src/prerender.mjs index e41ba8d1..26db0692 100644 --- a/src/prerender.mjs +++ b/src/prerender.mjs @@ -20,12 +20,15 @@ /** * Add a given set of urls to the speculation rules * @param {Set} urlsToPrerender - the URLs to add to speculation rules + * @param {String} eagerness - prerender eagerness mode - default immediate * @return {Boolean|Object} boolean or Error Object */ -export function addSpeculationRules(urlsToPrerender) { +export function addSpeculationRules(urlsToPrerender, eagerness = 'immediate') { const specScript = document.createElement('script'); specScript.type = 'speculationrules'; - specScript.text = `{"prerender":[{"source": "list","urls": ["${Array.from(urlsToPrerender).join('","')}"]}]}`; + specScript.text = `{"prerender":[{"source": "list", + "urls": ["${Array.from(urlsToPrerender).join('","')}"], + "eagerness": "${eagerness}"}]}`; try { document.head.appendChild(specScript); } catch (error) { From 5b0b288e24d5b9f1aa3fa8533cdfc1f4cbe0c178 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Wed, 27 Nov 2024 20:15:02 +0100 Subject: [PATCH 12/17] Added prefetch fallback for eagerness prerender --- src/index.mjs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/index.mjs b/src/index.mjs index ec36ee7f..73e95340 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -253,7 +253,27 @@ export function prerender(urls, eagerness) { // 1) whether UA supports spec rules.. If not, fallback to prefetch // Note: Prerendering supports same-site cross origin with opt-in header if (!hasSpecRulesSupport()) { - prefetch(urls); + if (eagerness === 'moderate' || eagerness === 'conservative') { + console.log('URLS', Array(urls)); + const elements = document.querySelectorAll(Array(urls).map(url => `a[href="${decodeURIComponent(url)}"]`).join(',')); + console.log('elements', elements); + for (const el of elements) { + console.log('el', el); + let timer = null; + el.addEventListener('mouseenter', e => { + timer = setTimeout(() => { + console.log('prefetch', e.target.href); + prefetch(e.target.href); + }, 200); + }); + el.addEventListener('mouseleave', e => { + clearTimeout(timer); + timer = null; + }); + } + } else { + prefetch(urls); + } return Promise.reject(new Error('This browser does not support the speculation rules API. Falling back to prefetch.')); } From c47627ec54b76afed60aad3fc568af77c3fe3608 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Wed, 27 Nov 2024 20:17:34 +0100 Subject: [PATCH 13/17] added missing variable declaration --- src/prefetch.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prefetch.mjs b/src/prefetch.mjs index b78c5015..500817aa 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -94,7 +94,7 @@ export function viaFetch(url, hasModeCors, hasCredentials, isPriority) { // // As of 2018, fetch() is high-priority in Chrome // and medium-priority in Safari. - options = {headers: {accept: '*/*'}}; + const options = {headers: {accept: '*/*'}}; if (!hasModeCors) options.mode = 'no-cors'; if (hasCredentials) options.credentials = 'include'; isPriority ? options.priority = 'high' : options.priority = 'low'; From dcd924bee282349339b443841bf99f303fa7d565 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Thu, 28 Nov 2024 22:21:38 +0100 Subject: [PATCH 14/17] added fallback prefetch module refactoring --- src/index.mjs | 39 +++++++++++---------------------------- src/prefetch.mjs | 40 ++++++++++++++++++++++++++++++++++++++++ src/prerender.mjs | 4 ++-- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/index.mjs b/src/index.mjs index 73e95340..8125fdee 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -15,7 +15,7 @@ **/ import throttle from 'throttles'; -import {supported, viaFetch} from './prefetch.mjs'; +import {addMouseoverListener, supported, viaFetch} from './prefetch.mjs'; import requestIdleCallback from './request-idle-callback.mjs'; import {addSpeculationRules, hasSpecRulesSupport} from './prerender.mjs'; @@ -74,6 +74,7 @@ function checkConnection(conn) { * @param {Boolean} [options.priority] - Attempt higher priority fetch (low or high) * @param {Boolean} [options.checkAccessControlAllowOrigin] - Check Access-Control-Allow-Origin response header * @param {Boolean} [options.checkAccessControlAllowCredentials] - Check the Access-Control-Allow-Credentials response header + * @param {Boolean} [options.onlyOnMouseover] - Enable the prefetch only on mouseover event * @param {Array} [options.origins] - Allowed origins to prefetch (empty allows all) * @param {Array|RegExp|Function} [options.ignores] - Custom filter(s) that run after origin checks * @param {Number} [options.timeout] - Timeout after which prefetching will occur @@ -151,7 +152,7 @@ export function listen(options = {}) { if (toPrefetch.size < limit && !shouldOnlyPrerender) { toAdd(() => { prefetch(hrefFn ? hrefFn(entry) : entry.href, options.priority, - options.checkAccessControlAllowOrigin, options.checkAccessControlAllowCredentials) + options.checkAccessControlAllowOrigin, options.checkAccessControlAllowCredentials, options.onlyOnMouseover) .then(isDone) .catch(error => { isDone(); @@ -209,9 +210,10 @@ export function listen(options = {}) { * @param {Boolean} checkAccessControlAllowOrigin - true to set crossorigin="anonymous" for DOM prefetch * and mode:'cors' for API fetch * @param {Boolean} checkAccessControlAllowCredentials - true to set credentials:'include' for API fetch +* @param {Boolean} onlyOnMouseover - true to enable prefetch only on mouseover event * @return {Object} a Promise */ -export function prefetch(url, isPriority, checkAccessControlAllowOrigin, checkAccessControlAllowCredentials) { +export function prefetch(url, isPriority, checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, onlyOnMouseover) { const chkConn = checkConnection(navigator.connection); if (chkConn instanceof Error) { return Promise.reject(new Error(`Cannot prefetch, ${chkConn.message}`)); @@ -230,9 +232,10 @@ export function prefetch(url, isPriority, checkAccessControlAllowOrigin, checkAc // ~> so that we don't repeat broken links toPrefetch.add(str); - return (isPriority ? viaFetch : supported)( - new URL(str, location.href).toString(), checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, isPriority, - ); + const urlToPrefetch = new URL(str, location.href).toString(); + return addMouseoverListener(() => (isPriority ? viaFetch : supported)( + urlToPrefetch, checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, isPriority, + ), urlToPrefetch, onlyOnMouseover); }), ); } @@ -243,7 +246,7 @@ export function prefetch(url, isPriority, checkAccessControlAllowOrigin, checkAc * @param {String} eagerness - prerender eagerness mode - default immediate * @return {Object} a Promise */ -export function prerender(urls, eagerness) { +export function prerender(urls, eagerness = 'immediate') { const chkConn = checkConnection(navigator.connection); if (chkConn instanceof Error) { return Promise.reject(new Error(`Cannot prerender, ${chkConn.message}`)); @@ -253,27 +256,7 @@ export function prerender(urls, eagerness) { // 1) whether UA supports spec rules.. If not, fallback to prefetch // Note: Prerendering supports same-site cross origin with opt-in header if (!hasSpecRulesSupport()) { - if (eagerness === 'moderate' || eagerness === 'conservative') { - console.log('URLS', Array(urls)); - const elements = document.querySelectorAll(Array(urls).map(url => `a[href="${decodeURIComponent(url)}"]`).join(',')); - console.log('elements', elements); - for (const el of elements) { - console.log('el', el); - let timer = null; - el.addEventListener('mouseenter', e => { - timer = setTimeout(() => { - console.log('prefetch', e.target.href); - prefetch(e.target.href); - }, 200); - }); - el.addEventListener('mouseleave', e => { - clearTimeout(timer); - timer = null; - }); - } - } else { - prefetch(urls); - } + prefetch(urls, true, false, false, eagerness === 'moderate' || eagerness === 'conservative'); return Promise.reject(new Error('This browser does not support the speculation rules API. Falling back to prefetch.')); } diff --git a/src/prefetch.mjs b/src/prefetch.mjs index 500817aa..1bad2bb3 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -101,4 +101,44 @@ export function viaFetch(url, hasModeCors, hasCredentials, isPriority) { return window.fetch ? fetch(url, options) : viaXHR(url, hasCredentials); } +/** + * Calls the prefetch function immediately + * or only on the mouseover event. + * @param {Function} callback - original prefetch function + * @param {String} url - url to prefetch + * @param {Boolean} onlyOnMouseover - true to add the mouseover listener + * @return {Object} a Promise + */ +export function addMouseoverListener(callback, url, onlyOnMouseover) { + if (onlyOnMouseover) { + const elements = document.querySelectorAll(`a[href="${url}"]`); + + for (const el of elements) { + const timerMap = new Map(); + + const mouseenterListener = e => { + const timer = setTimeout(() => { + el.removeEventListener('mouseenter', mouseenterListener); + el.removeEventListener('mouseleave', mouseleaveListener); + return callback(); + }, 200); + timerMap.set(el, timer); + }; + + const mouseleaveListener = e => { + const timer = timerMap.get(el); + if (timer) { + clearTimeout(timer); + timerMap.delete(el); + } + }; + + el.addEventListener('mouseenter', mouseenterListener); + el.addEventListener('mouseleave', mouseleaveListener); + } + } else { + return callback(); + } +} + export const supported = hasPrefetch() ? viaDOM : viaFetch; diff --git a/src/prerender.mjs b/src/prerender.mjs index 26db0692..f91e1e7e 100644 --- a/src/prerender.mjs +++ b/src/prerender.mjs @@ -20,10 +20,10 @@ /** * Add a given set of urls to the speculation rules * @param {Set} urlsToPrerender - the URLs to add to speculation rules - * @param {String} eagerness - prerender eagerness mode - default immediate + * @param {String} eagerness - prerender eagerness mode * @return {Boolean|Object} boolean or Error Object */ -export function addSpeculationRules(urlsToPrerender, eagerness = 'immediate') { +export function addSpeculationRules(urlsToPrerender, eagerness) { const specScript = document.createElement('script'); specScript.type = 'speculationrules'; specScript.text = `{"prerender":[{"source": "list", From ec067f5302f377ad6e6dac52e61f0e2ddbb29353 Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Thu, 28 Nov 2024 22:24:55 +0100 Subject: [PATCH 15/17] 2.4.0 --- .size-limit.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.size-limit.json b/.size-limit.json index 7a684a02..47eb6d71 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -1,22 +1,22 @@ [ { "path": "dist/quicklink.js", - "limit": "2 kB", + "limit": "2.35 kB", "gzip": true }, { "path": "dist/quicklink.mjs", - "limit": "2 kB", + "limit": "2.35 kB", "gzip": true }, { "path": "dist/quicklink.modern.mjs", - "limit": "1.6 kB", + "limit": "1.9 kB", "gzip": true }, { "path": "dist/quicklink.umd.js", - "limit": "2 kB", + "limit": "2.4 kB", "gzip": true } -] +] \ No newline at end of file From 8afc88c7f5c62cb34d4dd7711ebd0d1169d5507c Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Thu, 28 Nov 2024 22:36:59 +0100 Subject: [PATCH 16/17] fix decodeURIComponent --- src/prefetch.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prefetch.mjs b/src/prefetch.mjs index 1bad2bb3..f781b9ac 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -111,7 +111,7 @@ export function viaFetch(url, hasModeCors, hasCredentials, isPriority) { */ export function addMouseoverListener(callback, url, onlyOnMouseover) { if (onlyOnMouseover) { - const elements = document.querySelectorAll(`a[href="${url}"]`); + const elements = document.querySelectorAll(`a[href="${decodeURIComponent(url)}"]`); for (const el of elements) { const timerMap = new Map(); From bc6f2cd2ce4a644cf594ae82ca08fb3da7d92f5c Mon Sep 17 00:00:00 2001 From: Giorgio Pellegrino Date: Fri, 29 Nov 2024 11:44:27 +0100 Subject: [PATCH 17/17] Refactoring call prefetchOnHover function to clean up index.mjs --- .size-limit.json | 8 +++---- src/index.mjs | 8 +++---- src/prefetch.mjs | 55 +++++++++++++++++++++++------------------------- 3 files changed, 33 insertions(+), 38 deletions(-) diff --git a/.size-limit.json b/.size-limit.json index 47eb6d71..ba778919 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -1,22 +1,22 @@ [ { "path": "dist/quicklink.js", - "limit": "2.35 kB", + "limit": "2.36 kB", "gzip": true }, { "path": "dist/quicklink.mjs", - "limit": "2.35 kB", + "limit": "2.36 kB", "gzip": true }, { "path": "dist/quicklink.modern.mjs", - "limit": "1.9 kB", + "limit": "1.91 kB", "gzip": true }, { "path": "dist/quicklink.umd.js", - "limit": "2.4 kB", + "limit": "2.43 kB", "gzip": true } ] \ No newline at end of file diff --git a/src/index.mjs b/src/index.mjs index 8125fdee..f6b9f967 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -15,7 +15,7 @@ **/ import throttle from 'throttles'; -import {addMouseoverListener, supported, viaFetch} from './prefetch.mjs'; +import {prefetchOnHover, supported, viaFetch} from './prefetch.mjs'; import requestIdleCallback from './request-idle-callback.mjs'; import {addSpeculationRules, hasSpecRulesSupport} from './prerender.mjs'; @@ -232,10 +232,8 @@ export function prefetch(url, isPriority, checkAccessControlAllowOrigin, checkAc // ~> so that we don't repeat broken links toPrefetch.add(str); - const urlToPrefetch = new URL(str, location.href).toString(); - return addMouseoverListener(() => (isPriority ? viaFetch : supported)( - urlToPrefetch, checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, isPriority, - ), urlToPrefetch, onlyOnMouseover); + return prefetchOnHover((isPriority ? viaFetch : supported), new URL(str, location.href).toString(), onlyOnMouseover, + checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, isPriority); }), ); } diff --git a/src/prefetch.mjs b/src/prefetch.mjs index f781b9ac..d9259820 100644 --- a/src/prefetch.mjs +++ b/src/prefetch.mjs @@ -109,35 +109,32 @@ export function viaFetch(url, hasModeCors, hasCredentials, isPriority) { * @param {Boolean} onlyOnMouseover - true to add the mouseover listener * @return {Object} a Promise */ -export function addMouseoverListener(callback, url, onlyOnMouseover) { - if (onlyOnMouseover) { - const elements = document.querySelectorAll(`a[href="${decodeURIComponent(url)}"]`); - - for (const el of elements) { - const timerMap = new Map(); - - const mouseenterListener = e => { - const timer = setTimeout(() => { - el.removeEventListener('mouseenter', mouseenterListener); - el.removeEventListener('mouseleave', mouseleaveListener); - return callback(); - }, 200); - timerMap.set(el, timer); - }; - - const mouseleaveListener = e => { - const timer = timerMap.get(el); - if (timer) { - clearTimeout(timer); - timerMap.delete(el); - } - }; - - el.addEventListener('mouseenter', mouseenterListener); - el.addEventListener('mouseleave', mouseleaveListener); - } - } else { - return callback(); +export function prefetchOnHover(callback, url, onlyOnMouseover, ...args) { + if (!onlyOnMouseover) return callback(url, ...args); + + const elements = document.querySelectorAll(`a[href="${decodeURIComponent(url)}"]`); + const timerMap = new Map(); + + for (const el of elements) { + const mouseenterListener = e => { + const timer = setTimeout(() => { + el.removeEventListener('mouseenter', mouseenterListener); + el.removeEventListener('mouseleave', mouseleaveListener); + return callback(url, ...args); + }, 200); + timerMap.set(el, timer); + }; + + const mouseleaveListener = e => { + const timer = timerMap.get(el); + if (timer) { + clearTimeout(timer); + timerMap.delete(el); + } + }; + + el.addEventListener('mouseenter', mouseenterListener); + el.addEventListener('mouseleave', mouseleaveListener); } }