Skip to content

Modernize code and improve readability #444

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
{
"path": "dist/quicklink.umd.js",
"limit": "2.51 kB",
"limit": "2.55 kB",
"gzip": true
}
]
80 changes: 39 additions & 41 deletions src/chunks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ const toPrefetch = new Set();
* @return {Boolean} If true, then it should be ignored
*/
function isIgnored(node, filter) {
return Array.isArray(filter) ?
filter.some(x => isIgnored(node, x)) :
(filter.test || filter).call(filter, node.href, node);
if (Array.isArray(filter)) {
return filter.some(x => isIgnored(node, x));
}

return (filter.test || filter).call(filter, node.href, node);
}

/**
Expand All @@ -59,8 +61,8 @@ function isIgnored(node, filter) {
export function listen(options = {}) {
if (!window.IntersectionObserver) return;

const [toAdd, isDone] = throttle(options.throttle || 1 / 0);
const limit = options.limit || 1 / 0;
const [toAdd, isDone] = throttle(options.throttle || Number.Infinity);
const limit = options.limit || Number.Infinity;

const allowed = options.origins || [location.hostname];
const ignores = options.ignores || [];
Expand All @@ -79,36 +81,37 @@ export function listen(options = {}) {
};

const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
observer.unobserve(entry = entry.target);
// Do not prefetch if will match/exceed limit
if (toPrefetch.size < limit) {
toAdd(() => {
prefetchChunks ?
prefetchChunks(entry, prefetchHandler) :
prefetchHandler(entry.href);
});
}
for (const {isIntersecting, target} of entries) {
if (!isIntersecting) continue;

observer.unobserve(target);
// Do not prefetch if will match/exceed limit
if (toPrefetch.size < limit) {
toAdd(() => {
prefetchChunks ?
prefetchChunks(target, prefetchHandler) :
prefetchHandler(target.href);
});
}
});
}
});

timeoutFn(() => {
// Find all links & Connect them to IO if allowed
(options.el || document).querySelectorAll('a').forEach(link => {
const links = (options.el || document).querySelectorAll('a[href]');
for (const link of links) {
// If the anchor matches a permitted origin
// ~> A `[]` or `true` means everything is allowed
if (!allowed.length || allowed.includes(link.hostname)) {
// If there are any filters, the link must not match any of them
if (!isIgnored(link, ignores)) observer.observe(link);
}
});
}
}, {
timeout: options.timeout || 2000,
});

return function () {
return () => {
// wipe url list
toPrefetch.clear();
// detach IO entries
Expand All @@ -124,30 +127,25 @@ export function listen(options = {}) {
*/
export function prefetch(url, isPriority) {
const {connection} = navigator;
if (!connection) return Promise.resolve();

if (connection) {
// Don't prefetch if using 2G or if Save-Data is enabled.
if (connection.saveData) {
return Promise.reject(new Error('Cannot prefetch, Save-Data is enabled'));
}
// Don't prefetch if using 2G or if Save-Data is enabled.
if (connection.saveData) {
return Promise.reject(new Error('Cannot prefetch, Save-Data is enabled'));
}

if (/2g/.test(connection.effectiveType)) {
return Promise.reject(new Error('Cannot prefetch, network conditions are poor'));
}
if (/2g/.test(connection.effectiveType)) {
return Promise.reject(new Error('Cannot prefetch, network conditions are poor'));
}

// Dev must supply own catch()
return Promise.all(
[].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);

return (isPriority ? viaFetch : supported)(
new URL(str, location.href).toString(),
);
}),
);
return Promise.all([url].flat().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);

return (isPriority ? viaFetch : supported)(new URL(str, location.href).toString());
}));
}
86 changes: 49 additions & 37 deletions src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,17 @@ function isIgnored(node, filter) {
* @return {Boolean|Object} Error Object if the constrainsts are met or boolean otherwise
*/
function checkConnection(conn) {
if (conn) {
// Don't pre* if using 2G or if Save-Data is enabled.
if (conn.saveData) {
return new Error('Save-Data is enabled');
}
// If no connection object, assume it's okay to prefetch
if (!conn) return true;

if (/2g/.test(conn.effectiveType)) {
return new Error('network conditions are poor');
}
// Don't prefetch if Save-Data is enabled.
if (conn.saveData) {
return new Error('Save-Data is enabled');
}

// Don't prefetch if using 2G connection.
if (/2g/.test(conn.effectiveType)) {
return new Error('network conditions are poor');
}

return true;
Expand Down Expand Up @@ -120,7 +122,7 @@ export function listen(options = {}) {
};

const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
for (let entry of entries) {
// On enter
if (entry.isIntersecting) {
entry = entry.target;
Expand All @@ -140,7 +142,11 @@ 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
// The same URL is not already present as a speculation rule
if ((shouldPrerenderAndPrefetch || shouldOnlyPrerender) && toPrerender.size < limit && !specRulesInViewport.has(entry.href)) {
if (
(shouldPrerenderAndPrefetch || shouldOnlyPrerender) &&
toPrerender.size < limit &&
!specRulesInViewport.has(entry.href)
) {
prerender(hrefFn ? hrefFn(entry) : entry.href, options.eagerness)
.then(specMap => {
for (const [key, value] of specMap) {
Expand All @@ -161,8 +167,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, options.onlyOnMouseover)
prefetch(
hrefFn ? hrefFn(entry) : entry.href,
options.priority,
options.checkAccessControlAllowOrigin,
options.checkAccessControlAllowCredentials,
options.onlyOnMouseover,
)
.then(isDone)
.catch(error => {
isDone();
Expand All @@ -175,40 +186,37 @@ export function listen(options = {}) {
} else {
entry = entry.target;
const index = hrefsInViewport.indexOf(entry.href);
if (index > -1) {
if (index !== -1) {
hrefsInViewport.splice(index);
}

if (specRulesInViewport.has(entry.href)) {
specRulesInViewport = removeSpeculationRule(specRulesInViewport, entry.href);
}
}
});
}
}, {
threshold,
});

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 :
(options.el || document).querySelectorAll('a');

elementsToListen.forEach(link => {
const isAnchorElement = options.el && options.el.length > 0 && options.el[0].nodeName === 'A';
const elementsToListen = isAnchorElement ? options.el : (options.el || document).querySelectorAll('a');

for (const link of elementsToListen) {
// If the anchor matches a permitted origin
// ~> A `[]` or `true` means everything is allowed
if (!allowed.length || allowed.includes(link.hostname)) {
// If there are any filters, the link must not match any of them
if (!isIgnored(link, ignores)) observer.observe(link);
}
});
}
}, {
timeout: options.timeout || 2000,
});

return function () {
return () => {
// wipe url list
toPrefetch.clear();
// detach IO entries
Expand Down Expand Up @@ -237,18 +245,22 @@ export function prefetch(urls, isPriority, checkAccessControlAllowOrigin, checkA
}

// Dev must supply own catch()
return Promise.all(
[].concat(urls).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);

return prefetchOnHover((isPriority ? viaFetch : supported), new URL(str, location.href).toString(), onlyOnMouseover,
checkAccessControlAllowOrigin, checkAccessControlAllowCredentials, isPriority);
}),
);
return Promise.all([urls].flat().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);

return prefetchOnHover(
isPriority ? viaFetch : supported,
new URL(str, location.href).toString(),
onlyOnMouseover,
checkAccessControlAllowOrigin,
checkAccessControlAllowCredentials,
isPriority,
);
}));
}

/**
Expand All @@ -258,7 +270,7 @@ export function prefetch(urls, isPriority, checkAccessControlAllowOrigin, checkA
* @return {Object} a Promise
*/
export function prerender(urls, eagerness = 'immediate') {
urls = [].concat(urls);
urls = [urls].flat();

const chkConn = checkConnection(navigator.connection);
if (chkConn instanceof Error) {
Expand Down
8 changes: 4 additions & 4 deletions src/prefetch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function viaDOM(url, hasCrossorigin) {
link.onload = resolve;
link.onerror = reject;

document.head.appendChild(link);
document.head.append(link);
});
}

Expand Down Expand Up @@ -97,7 +97,7 @@ export function viaFetch(url, hasModeCors, hasCredentials, isPriority) {
const options = {headers: {accept: '*/*'}};
if (!hasModeCors) options.mode = 'no-cors';
if (hasCredentials) options.credentials = 'include';
isPriority ? options.priority = 'high' : options.priority = 'low';
options.priority = isPriority ? 'high' : 'low';
return window.fetch ? fetch(url, options) : viaXHR(url, hasCredentials);
}

Expand All @@ -116,7 +116,7 @@ export function prefetchOnHover(callback, url, onlyOnMouseover, ...args) {
const timerMap = new Map();

for (const el of elements) {
const mouseenterListener = _ => {
const mouseenterListener = () => {
const timer = setTimeout(() => {
el.removeEventListener('mouseenter', mouseenterListener);
el.removeEventListener('mouseleave', mouseleaveListener);
Expand All @@ -125,7 +125,7 @@ export function prefetchOnHover(callback, url, onlyOnMouseover, ...args) {
timerMap.set(el, timer);
};

const mouseleaveListener = _ => {
const mouseleaveListener = () => {
const timer = timerMap.get(el);
if (timer) {
clearTimeout(timer);
Expand Down
2 changes: 1 addition & 1 deletion src/prerender.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function addSpeculationRules(urlsToPrerender, eagerness) {
}],
});

document.head.appendChild(specScript);
document.head.append(specScript);
specMap.set(url, specScript);
}
} catch (error) {
Expand Down
7 changes: 3 additions & 4 deletions src/react-chunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const useIntersect = ({root = null, rootMargin, threshold = 0} = {}) => {

useEffect(() => {
if (observer.current) observer.current.disconnect();

observer.current = new window.IntersectionObserver(
([entry]) => updateEntry(entry),
{
Expand All @@ -43,14 +44,12 @@ const useIntersect = ({root = null, rootMargin, threshold = 0} = {}) => {
return [setNode, entry];
};

const __defaultAccessor = mix => {
return (mix && mix.href) || mix || '';
};
const __defaultAccessor = mix => (mix && mix.href) || mix || '';

const prefetchChunks = (entry, prefetchHandler, accessor = __defaultAccessor) => {
const {files} = rmanifest(window.__rmanifest, entry.pathname);
const chunkURLs = files.map(accessor).filter(Boolean);
if (chunkURLs.length) {
if (chunkURLs.length > 0) {
prefetchHandler(chunkURLs);
} else {
// also prefetch regular links in-viewport
Expand Down
8 changes: 3 additions & 5 deletions src/request-idle-callback.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@

// RIC and shim for browsers setTimeout() without it
const requestIdleCallback = window.requestIdleCallback ||
function (cb) {
(cb => {
const start = Date.now();
return setTimeout(() => {
cb({
didTimeout: false,
timeRemaining() {
return Math.max(0, 50 - (Date.now() - start));
},
timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
});
}, 1);
};
});

export default requestIdleCallback;
Loading