Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prerender EAGERNERSS #436

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
49a212f
add isCrossorigin attribute to prefetch method
giorgiopellegrino May 9, 2024
fdd7d23
added javascript doc
giorgiopellegrino Oct 25, 2024
41c8df5
crossorigin call from fetch and prefetch
giorgiopellegrino Oct 25, 2024
1f2aa43
removed xhr and added Access-Control-Allow-Origin header check
giorgiopellegrino Oct 25, 2024
b0cf0d5
javascript doc fixes
giorgiopellegrino Oct 25, 2024
7cd5371
Add attribute to manage AccessControllAllowCredentials
giorgiopellegrino Oct 25, 2024
0087419
2.3.0
giorgiopellegrino Oct 25, 2024
b53152d
2.4.0
giorgiopellegrino Oct 25, 2024
bcb74c8
removed exception handler
giorgiopellegrino Oct 28, 2024
dfb6161
Merge branch 'GoogleChromeLabs:main' into main
giorgiopellegrino Oct 28, 2024
281255c
2.4.0
giorgiopellegrino Oct 29, 2024
34e0980
Added prerender eagerness property
giorgiopellegrino Nov 26, 2024
5b0b288
Added prefetch fallback for eagerness prerender
giorgiopellegrino Nov 27, 2024
c47627e
added missing variable declaration
giorgiopellegrino Nov 27, 2024
dcd924b
added fallback prefetch module refactoring
giorgiopellegrino Nov 28, 2024
ec067f5
2.4.0
giorgiopellegrino Nov 28, 2024
8afc88c
fix decodeURIComponent
giorgiopellegrino Nov 28, 2024
bc6f2cd
Refactoring call prefetchOnHover function to clean up index.mjs
giorgiopellegrino Nov 29, 2024
5ca43f1
add eagerness property to speculation rules
giorgiopellegrino Dec 4, 2024
037e592
Added control of Access-Control-Allow-Origin and Credentials
giorgiopellegrino Dec 5, 2024
7c61055
remove unused parameters
giorgiopellegrino Dec 6, 2024
ded767c
Related compatible URLs
giorgiopellegrino Feb 11, 2025
f3f0e84
Related compatible URLs
giorgiopellegrino Feb 11, 2025
7f5a54e
Relative URLs compatible
giorgiopellegrino Feb 14, 2025
e0eee44
Revert "Relative URLs compatible"
giorgiopellegrino Feb 14, 2025
653d3d6
Revert "Related compatible URLs"
giorgiopellegrino Feb 14, 2025
6d3f53a
bug fix
giorgiopellegrino Feb 14, 2025
767d89e
Relative URLs compatible
giorgiopellegrino Feb 14, 2025
397d9a5
bug fix naming method "viaFetch"
giorgiopellegrino Mar 18, 2025
5ee2e76
fixing JS DOC on prefetch and prerender methods
giorgiopellegrino Mar 18, 2025
4f9f366
fixing JS DOC on prefetch and prerender methods
giorgiopellegrino Mar 18, 2025
114a2f7
fixing JS DOC on prefetch and prerender methods
giorgiopellegrino Mar 18, 2025
e07b2b4
Merge branch 'main' into crossorigin
addyosmani Mar 22, 2025
09a16c6
Merge branch 'main' into prerender-eagerness
addyosmani Mar 22, 2025
932ea86
Merge branch 'main' of https://github.com/giorgiopellegrino/quicklink…
giorgiopellegrino Mar 22, 2025
5a4ab67
Merge branch 'prerender-eagerness' of https://github.com/giorgiopelle…
giorgiopellegrino Mar 22, 2025
a89d008
Merge branch 'crossorigin' of https://github.com/giorgiopellegrino/qu…
giorgiopellegrino Mar 22, 2025
1ab0a32
Merge branch 'main' of https://github.com/giorgiopellegrino/quicklink…
giorgiopellegrino Mar 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
**/

import throttle from 'throttles';
import {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';

Expand Down Expand Up @@ -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
Expand All @@ -86,6 +87,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}
*/
Expand Down Expand Up @@ -135,7 +137,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 {
Expand All @@ -150,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();
Expand Down Expand Up @@ -208,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(urls, isPriority, checkAccessControlAllowOrigin, checkAccessControlAllowCredentials) {
export function prefetch(urls, isPriority, checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, onlyOnMouseover) {
const chkConn = checkConnection(navigator.connection);
if (chkConn instanceof Error) {
return Promise.reject(new Error(`Cannot prefetch, ${chkConn.message}`));
Expand All @@ -229,7 +232,7 @@ export function prefetch(urls, isPriority, checkAccessControlAllowOrigin, checkA
// ~> so that we don't repeat broken links
toPrefetch.add(str);

return (isPriority ? viaFetch : supported)(new URL(str, location.href).toString(),
return prefetchOnHover((isPriority ? viaFetch : supported), new URL(str, location.href).toString(), onlyOnMouseover,
checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, isPriority);
}),
);
Expand All @@ -238,9 +241,10 @@ export function prefetch(urls, isPriority, checkAccessControlAllowOrigin, checkA
/**
* Prerender a given URL
* @param {String | String[]} urls - the URLs to fetch
* @param {String} eagerness - prerender eagerness mode - default immediate
* @return {Object} a Promise
*/
export function prerender(urls) {
export function prerender(urls, eagerness = 'immediate') {
const chkConn = checkConnection(navigator.connection);
if (chkConn instanceof Error) {
return Promise.reject(new Error(`Cannot prerender, ${chkConn.message}`));
Expand All @@ -250,7 +254,7 @@ export function prerender(urls) {
// 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, true, false, false);
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.'));
}

Expand All @@ -263,6 +267,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);
}
37 changes: 37 additions & 0 deletions src/prefetch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,41 @@ 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 prefetchOnHover(callback, url, onlyOnMouseover, ...args) {
if (!onlyOnMouseover) return callback(url, ...args);

const elements = Array.from(document.querySelectorAll('a')).filter(el => el.href === url);
const timerMap = new Map();

for (const el of elements) {
const mouseenterListener = _ => {
const timer = setTimeout(() => {
el.removeEventListener('mouseenter', mouseenterListener);
el.removeEventListener('mouseleave', mouseleaveListener);
return callback(url, ...args);
}, 200);
timerMap.set(el, timer);
};

const mouseleaveListener = _ => {
const timer = timerMap.get(el);
if (timer) {
clearTimeout(timer);
timerMap.delete(el);
}
};

el.addEventListener('mouseenter', mouseenterListener);
el.addEventListener('mouseleave', mouseleaveListener);
}
}

export const supported = hasPrefetch() ? viaDOM : viaFetch;
6 changes: 4 additions & 2 deletions src/prerender.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
* @param {String} eagerness - prerender eagerness mode
* @return {Boolean|Object} boolean or Error Object
*/
export function addSpeculationRules(urlsToPrerender) {
export function addSpeculationRules(urlsToPrerender, eagerness) {
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) {
Expand Down
Loading