Last active
November 29, 2023 19:56
-
-
Save DaniilBabanin/93a20d0759ba979e4bd6cc0912b5d6a2 to your computer and use it in GitHub Desktop.
Quicklink Userscript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Quicklink Userscript | |
// @namespace Violentmonkey Scripts | |
// @version 1.0 | |
// @description Implement prefetching and prerendering for links | |
// @author Daniil Babanin | |
// @match *://*/* | |
// @grant none | |
// @run-at document-start | |
// @allFrames true | |
// @inject-into page | |
// @license Apache License 2.0 | |
// @run-at document-start | |
// ==/UserScript== | |
;(function () { | |
"use strict" | |
/** | |
* InspiredBy Quicklink 2.3.0:https://github.com/GoogleChromeLabs/quicklink | |
*/ | |
// Utility functions | |
const isSupported = (rel) => | |
document.createElement("link").relList.supports(rel) | |
// Fetch with XHR as a fallback | |
const fetchWithXhr = (url) => | |
new Promise((resolve, reject) => { | |
const xhr = new XMLHttpRequest() | |
xhr.open("GET", url, true) | |
xhr.withCredentials = true | |
xhr.onload = () => (xhr.status === 200 ? resolve() : reject()) | |
xhr.send() | |
}) | |
// Prefetch utility | |
const prefetch = (url) => { | |
if (isSupported("prefetch")) { | |
const link = document.createElement("link") | |
link.rel = "prefetch" | |
link.href = url | |
document.head.appendChild(link) | |
} else { | |
// Fallback to XHR if <link rel="prefetch"> is not supported | |
fetchWithXhr(url) | |
} | |
} | |
// Prerender utility | |
const prerender = (url) => { | |
if (isSupported("prerender")) { | |
const link = document.createElement("link") | |
link.rel = "prerender" | |
link.href = url | |
document.head.appendChild(link) | |
} else { | |
// Fallback to prefetch if <link rel="prerender"> is not supported | |
prefetch(url) | |
} | |
} | |
// Intersection Observer setup | |
const setupObserver = (options) => { | |
const observer = new IntersectionObserver( | |
(entries) => { | |
entries.forEach((entry) => { | |
if (entry.isIntersecting) { | |
const url = entry.target.href | |
options.prerender ? prerender(url) : prefetch(url) | |
observer.unobserve(entry.target) | |
} | |
}) | |
}, | |
{ threshold: options.threshold || 0 }, | |
) | |
// Observe links | |
document.querySelectorAll("a").forEach((link) => { | |
if (isValidLink(link, options.origins, options.ignores)) { | |
observer.observe(link) | |
} | |
}) | |
return observer | |
} | |
// Validate link against provided criteria | |
const isValidLink = (link, origins, ignores) => { | |
return ( | |
origins.includes(link.hostname) && !ignores.some((ignore) => ignore(link)) | |
) | |
} | |
// Main function to initialize prefetching/prerendering | |
const initialize = (options = {}) => { | |
const defaults = { | |
origins: [location.hostname], | |
ignores: [], | |
prerender: false, | |
threshold: 0.01, // Default threshold for IntersectionObserver | |
} | |
const finalOptions = { ...defaults, ...options } | |
const observer = setupObserver(finalOptions) | |
return () => observer.disconnect() // Return a function to disconnect the observer | |
} | |
// Ignore rules and custom ignore function | |
const ignoresRules = { | |
urlPaths: | |
"api, logout, signout, exit, quit, login, logoff, subscribe, subscription, doubleclick, bit.ly, signin, signup, apk, release, amazon, shopping, checkout, shop, cart, ads, ticket, captcha", | |
fileExtensions: | |
".zip, .pdf, .mp4, .webm, .mp3, .mov, .rar, .apk, .tar, .doc, .docx, .xls, .xlsx, .ppt, .pptx", | |
urlProtocols: "http:, tel:, mailto:, javascript:, market:", | |
} | |
// Convert ignore rules to arrays | |
Object.keys(ignoresRules).forEach((key) => { | |
ignoresRules[key] = ignoresRules[key].split(",").map((str) => str.trim()) | |
}) | |
function ignoreFunc(uri) { | |
const result = | |
ignoresRules.urlPaths.some((item) => uri.href.includes(`/${item}/`)) || | |
ignoresRules.fileExtensions.some((item) => uri.href.includes(item)) || | |
ignoresRules.urlProtocols.some((item) => uri.href.startsWith(item)) | |
if (result) { | |
console.log("[Quicklink][Ignore]", uri) | |
} | |
return result | |
} | |
// Expose quicklink functionalities | |
const quicklink = { | |
listen: initialize, | |
prefetch: prefetch, | |
prerender: prerender, | |
} | |
// Usage | |
window.addEventListener("load", () => { | |
quicklink.listen({ | |
prerender: true, | |
ignores: [ignoreFunc], | |
}) | |
}) | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment