UNPKG

@lucidclient/speculate

Version:

A lightweight library to handle speculate rules for prefetching and prerendering documents, with added support for data fetching on intent.

2 lines 7.87 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;var S=()=>{if(!navigator.onLine)return console.warn("The device is offline, speculate library not initialised."),!1;if("connection"in navigator){let i=navigator.connection;if(_optionalChain([i, 'optionalAccess', _ => _.saveData]))return console.warn("Save-Data is enabled, speculate library not initialised."),!1;if(/(2|3)g/.test(_optionalChain([i, 'optionalAccess', _2 => _2.effectiveType])))return console.warn("2G or 3G connection is detected, speculate library not initialised."),!1}return!0},c=S;var s={moderateEvents:["mouseenter","touchstart","focus"],fallbackTriggerSupport:["visible","moderate"],fallbackTrigger:"moderate",fallbackAction:"prefetch",validActions:["prefetch","prerender"],validTriggers:["visible","immediate","eager","moderate","conservative"],genSpeculate:"gen-speculate"},u=!1,g=new Set,m="supports"in HTMLScriptElement&&HTMLScriptElement.supports&&HTMLScriptElement.supports("speculationrules"),E=_optionalChain([document, 'access', _3 => _3.createElement, 'call', _4 => _4("link"), 'access', _5 => _5.relList, 'optionalAccess', _6 => _6.supports, 'optionalCall', _7 => _7("prefetch")]),o,a=new AbortController,f=i=>{o.unobserve(i),k(i)},A=i=>{for(let e of s.moderateEvents)i.addEventListener(e,T,{passive:!0,signal:a.signal})},k=i=>{for(let e of s.moderateEvents)i.removeEventListener(e,T)},T=i=>{let e=i.target;e&&h(e,p(e.rel))},h=(i,e)=>{if(!w({href:i.href,target:i.target}))return f(i);m?C(i.href,e):E?L(i.href):fetch(i.href,{priority:"low",signal:a.signal}),g.add(i.href),f(i)},C=(i,e)=>{try{let t=document.createElement("script");t.type="speculationrules",t.setAttribute(s.genSpeculate,"");let r=[{source:"list",urls:[i],eagerness:e[1]==="visible"?void 0:e[1]}];t.textContent=e[0]==="prefetch"?JSON.stringify({prefetch:r}):JSON.stringify({prerender:r,prefetch:r}),document.head.appendChild(t)}catch(t){console.error(t)}},L=i=>{let e=document.createElement("link");e.rel="prefetch",e.setAttribute(s.genSpeculate,""),e.href=i,e.as="document",document.head.appendChild(e)},p=i=>{let e,t;for(let r of i.split(" "))if(r.startsWith("prefetch:")||r.startsWith("prerender:")){[e,t]=r.split(":");break}return e||(e=s.fallbackAction),t||(t=s.fallbackTrigger),m||(e=s.fallbackAction,s.fallbackTriggerSupport.includes(t)||(t=s.fallbackTrigger)),s.validActions.includes(e)||(e=s.fallbackAction),s.validTriggers.includes(t)||(t=s.fallbackTrigger),[e,t]},w=i=>{try{if(i.href=i.href.replace(/#.*/,""),i.target==="_blank"||i.href.includes("mailto:")||i.href.includes("tel:"))return!1;let e=new URL(i.href);return!(e.origin!==window.location.origin||e.pathname===window.location.pathname||g.has(i.href))}catch (e2){return!1}},b=()=>{if(u||(u=!0,!c()))return;o=o||new IntersectionObserver(t=>{for(let r of t)if(r.isIntersecting&&r.target instanceof HTMLAnchorElement){let n=r.target,[y,v]=p(n.rel);v==="visible"?h(n,[y,v]):f(n)}});let e=document.querySelectorAll('a[rel*="prefetch:"], a[rel*="prerender:"]');for(let t of e){let[r,n]=p(t.rel);n==="visible"?o.observe(t):n==="moderate"&&!m?A(t):h(t,[r,n])}},I=()=>{a.abort(),a=new AbortController,_optionalChain([o, 'optionalAccess', _8 => _8.disconnect, 'call', _9 => _9()]),g.clear(),u=!1;let i=document.querySelectorAll(`link[${s.genSpeculate}], script[${s.genSpeculate}]`);for(let e of i)e.remove()},D=()=>(typeof requestIdleCallback<"u"?requestIdleCallback(b):setTimeout(b,0),I),O= exports.speculateLinks =D;var d= (_class =class{constructor(e){;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);this.config=e;this.registerEvents(),this.handleOptimisticPrefetch()}__init() {this.cache=new Map}__init2() {this.abortController=new AbortController}__init3() {this.intentDebounce=null}registerEvents(){for(let e of this.normaliseTargets(this.config.elements))e.addEventListener("mouseover",this.mouseOverEventHandler,{signal:this.abortController.signal}),e.addEventListener("click",this.clickEventHandler,{signal:this.abortController.signal})}__init4() {this.mouseOverEventHandler=e=>{if(!c())return;let t=e.target;this.getCacheKey(t)&&(this.intentDebounce&&this.intentDebounce.target!==t&&(clearTimeout(this.intentDebounce.timeout),this.intentDebounce=null),this.intentDebounce||(this.intentDebounce={target:t,timeout:setTimeout(()=>{this.prefetch(t),this.intentDebounce=null},this.hoverDelay)}))}}__init5() {this.clickEventHandler=async e=>{let t=await this.prefetch(e.target);this.config.onClick&&this.config.onClick(e,t)}}getCacheKey(e){return this.config.getCacheKey?_nullishCoalesce(this.config.getCacheKey(e), () => (null)):e.id||null}isStale(e){return Date.now()-e>this.cacheConfig.staleTime}manageCacheSize(){for(;this.cache.size>=this.cacheConfig.maxSize;){let e=null,t=Number.POSITIVE_INFINITY;for(let[r,n]of this.cache.entries())n.timestamp<t&&(t=n.timestamp,e=r);e&&this.cache.delete(e)}}handleOptimisticPrefetch(){let e=[...this.optimisticStrategies].sort((t,r)=>(_nullishCoalesce(t.priority, () => (Number.POSITIVE_INFINITY)))-(_nullishCoalesce(r.priority, () => (Number.POSITIVE_INFINITY))));for(let t of e){if(t.condition&&!t.condition())continue;let r=this.normaliseTargets(t.elements);for(let n of r)this.schedulePrefetch(n)}}async executeFetchCallback(e){try{return await this.config.fetch(e)}catch(t){return{error:{message:t instanceof Error?t.message:"An unknown error was caught while executing the fetch callback.",exception:t},data:void 0}}}schedulePrefetch(e){let t=()=>this.prefetch(e);typeof requestIdleCallback<"u"?requestIdleCallback(t):setTimeout(t,0)}normaliseTargets(e){return e?e instanceof NodeList?Array.from(e):Array.isArray(e)?e:[e]:[]}get cacheConfig(){return{maxSize:_nullishCoalesce(_optionalChain([this, 'access', _10 => _10.config, 'access', _11 => _11.cache, 'optionalAccess', _12 => _12.maxSize]), () => (5)),staleTime:_nullishCoalesce(_optionalChain([this, 'access', _13 => _13.config, 'access', _14 => _14.cache, 'optionalAccess', _15 => _15.staleTime]), () => (12e4))}}get optimisticStrategies(){return this.config.optimistic?Array.isArray(this.config.optimistic)?this.config.optimistic:[this.config.optimistic]:[]}get hoverDelay(){return _nullishCoalesce(this.config.hoverDelay, () => (100))}destroy(){this.abortController.abort(),this.clearCache(),this.intentDebounce&&(clearTimeout(this.intentDebounce.timeout),this.intentDebounce=null)}refresh(e){e&&(this.config={...this.config,...e.elements&&{elements:e.elements},...e.optimistic&&{optimistic:e.optimistic}}),this.destroy(),this.abortController=new AbortController,this.registerEvents(),this.handleOptimisticPrefetch()}async prefetch(e){let t=this.getCacheKey(e);if(t){let n=this.cache.get(t);if(n&&!this.isStale(n.timestamp))return n.promise}let r=this.executeFetchCallback(e);return t&&(this.manageCacheSize(),this.cache.set(t,{timestamp:Date.now(),promise:r})),r}async prefetchAll(e){let t=Array.from(e);return Promise.all(t.map(r=>this.prefetch(r)))}clearCache(e){if(!e){this.cache.clear();return}let t=this.getCacheKey(e);t&&this.cache.delete(t)}}, _class),H= exports.Speculator =d;exports.Speculator = H; exports.speculateLinks = O; //# sourceMappingURL=index.cjs.map