premonition 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.min.js CHANGED
@@ -1,2 +1 @@
1
- /* premonition@0.7.0 · MIT · Francisco Presencia https://francisco.io/*/
2
- (function(a,b){'object'==typeof exports&&'undefined'!=typeof module?module.exports=b():'function'==typeof define&&define.amd?define(b):a.pre=b()})(this,function(){'use strict';async function a(a='a'){await new Promise(a=>'complete'===document.readyState?a():void document.addEventListener('DOMContentLoaded',a,!1)),this.cache.set(window.location.href,{href:window.location.href,html:i('html')[0].outerHTML}),i(`script[${this.attrs.once}]`).forEach(a=>this.loaded[a.src]=!0),i(a).forEach(a=>{a.host!==location.host||(a.addEventListener('mouseover',a=>this.load(a.currentTarget)),a.addEventListener('touchstart',a=>this.load(a.currentTarget)),a.addEventListener('click',a=>(a.preventDefault(),this.open(a.currentTarget))))})}async function b(a=''){let b=j(a.href||a,'pre.load():');return(b=b.split('#')[0],this.loader[b])?this.loader[b]:this.cache.get(b)?Promise.resolve(this.cache.get(b)):(this.loader[b]=fetch(b).then(a=>(a.ok||this.report(new Error(a.statusText)),a.text())).then(a=>(this.cache.set(b,{href:b,html:a}),delete this.loader[b],this.cache.get(b))),this.loader[b])}async function c(a,{history:b}={}){try{const c=j(a.href||a),d=c.split('#').slice(1).join('#'),e=await this.load(c);return await this.replace(e.href,e.html,{history:b}),this.init()}catch(a){this.report(a),location.href=href}}function d(a='a'){const b=Object.keys(this.cache.cache);i(a).forEach(a=>{if(b.includes(a.href))return a.getAttribute(this.attrs.cache)?void 0:a.setAttribute(this.attrs.cache,!0);a.getAttribute(this.attrs.cache)&&a.removeAttribute(this.attrs.cache)})}function e(a,b){let c=0;[...b.children].forEach(b=>{const d=b.cloneNode(!0),e=[...a.children],f=e.slice(c);if(l(f,d))return c+=f.findIndex(a=>k(d,a)),void e[c].setAttribute(this.attrs.keep,!0);const g=e[c];d.setAttribute(this.attrs.keep,!0),g.parentNode.insertBefore(d,g.nextSibling)}),[...a.children].forEach(b=>{b.getAttribute(this.attrs.keep)?b.removeAttribute(this.attrs.keep):a.removeChild(b)})}function f(a,b,{history:c=!0}={}){c&&window.history.pushState({href:a},'',a),window.scrollTo(0,0);const d=document.createElement('html');d.innerHTML=b;const e=document.body;e.innerHTML='',[...i('body',d)[0].childNodes].forEach(a=>e.appendChild(a)),this.head(document.head,d.querySelector('head'));const f=([a,...b])=>a?m(a,e,this.loaded).then(()=>f(b)):Promise.resolve();return f(i('script'))}var g='undefined'==typeof window?'undefined'==typeof global?'undefined'==typeof self?{}:self:global:window,h=function(a,b){return b={exports:{}},a(b,b.exports),b.exports}(function(a){function b(a,b){if(!(a instanceof b))throw new TypeError('Cannot call a class as a function')}var c=function(){function a(a,b){for(var c,d=0;d<b.length;d++)c=b[d],c.enumerable=c.enumerable||!1,c.configurable=!0,'value'in c&&(c.writable=!0),Object.defineProperty(a,c.key,c)}return function(b,c,d){return c&&a(b.prototype,c),d&&a(b,d),b}}();(function(){var d='undefined'==typeof process?function(a){return setTimeout(a,1)}:process.nextTick,e=function(){function a(c,d,e,f){return b(this,a),this.expire=f,this.max=c,this.notify=d,this.ttl=e,this.reset()}return c(a,[{key:'clear',value:function(){var a=!!(0<arguments.length&&void 0!==arguments[0])&&arguments[0];return this.reset(),!a&&this.notify&&d(this.onchange('clear',this.dump())),this}},{key:'clearTimer',value:function(a){var b=1<arguments.length&&void 0!==arguments[1]?arguments[1]:'timers';return this.has(a,b)&&(clearTimeout(this[b][a]),delete this[b][a]),this}},{key:'delete',value:function(a){var b=!!(1<arguments.length&&void 0!==arguments[1])&&arguments[1];return this.remove(a,b)}},{key:'dump',value:function(){var a=JSON.parse(JSON.stringify(this));return delete a.timers,JSON.stringify(a)}},{key:'evict',value:function(){return this.has(this.last)&&this.remove(this.last,!0),this.notify&&d(this.onchange('evict',this.dump())),this}},{key:'get',value:function(a){var b;return this.has(a)&&(b=this.cache[a].value,this.set(a,b),this.notify&&d(this.onchange('get',this.dump())),0<this.ttl&&this.clearTimer(a).setTimer(a)),b}},{key:'has',value:function(a){var b=1<arguments.length&&void 0!==arguments[1]?arguments[1]:'cache';return a in this[b]}},{key:'onchange',value:function(){}},{key:'remove',value:function(a){var b=!!(1<arguments.length&&void 0!==arguments[1])&&arguments[1],c='string'==typeof a?a:a.toString(),e=void 0;if(this.has(c)){var f=this.cache[c];delete this.cache[c],this.length--,0<this.ttl&&this.clearTimer(c),!1===b&&0<this.expire&&this.clearTimer(c,'expires'),this.has(f.previous)?(this.cache[f.previous].next=f.next,this.first===c&&(this.first=f.previous)):this.first===c&&(this.first=''),this.has(f.next)?(this.cache[f.next].previous=f.previous,this.last===c&&(this.last=f.next)):this.last===c&&(this.last=''),e=f}else this.first===c&&(this.first=''),this.last===c&&(this.last='');return!b&&this.notify&&d(this.onchange('remove',this.dump())),e}},{key:'reset',value:function(){var a=this;return void 0!==this.expires&&Object.keys(this.expires).forEach(function(b){return a.clearTimer(b,'expires')}),void 0!==this.timers&&Object.keys(this.timers).forEach(function(b){return a.clearTimer(b)}),this.cache={},this.expires={},this.first='',this.last='',this.length=0,this.timers={},this}},{key:'set',value:function(a,b){var c,e;return this.has(a)?(e=this.cache[a],e.value=b,e.next='',this.first!==a&&(e.previous=this.first),this.last===a&&''!==e.previous&&(this.last=e.previous)):(++this.length>this.max&&this.remove(this.last,!0),1===this.length&&(this.last=a),this.cache[a]={next:'',previous:this.first,value:b}),this.first!==a&&this.has(this.first)&&(c=this.cache[this.first],c.next=a,c.previous===a&&(c.previous='')),this.first=a,this.notify&&d(this.onchange('set',this.dump())),0<this.ttl&&this.clearTimer(a).setTimer(a),0<this.expire&&!1===this.has(a,'expires')&&this.setExpire(a),this}},{key:'setExpire',value:function(a){var b=this;this.expires[a]=setTimeout(function(){return b.clearTimer(a,'expires').clearTimer(a).remove(a)},this.expire)}},{key:'setTimer',value:function(a){var b=this;this.timers[a]=setTimeout(function(){return b.remove(a)},this.ttl)}},{key:'update',value:function(a){var b=this,c=JSON.parse(a);Object.keys(c).forEach(function(a){b[a]=c[a]})}}]),a}();a.exports=function(){var a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:1e3,b=!!(1<arguments.length&&arguments[1]!==void 0)&&arguments[1],c=2<arguments.length&&arguments[2]!==void 0?arguments[2]:0,d=3<arguments.length&&arguments[3]!==void 0?arguments[3]:0;return new e(a,b,c,d)}})('undefined'==typeof window?g:window)}),i=(a,b=document)=>[...b.querySelectorAll(a)],j=(a,b='')=>{if(!a)throw new Error(`${b} the argument has to be an url or a <a> element`);if('A'===a.nodeName)throw new Error(`${b} argument element is missing the href="" or it is empty`);if(a.nodeName)throw new Error(`${b} expected an anchor <a> element, received "${a.nodeName}"`);if('string'!=typeof a)throw new Error(`${b} expected a string, received ${typeof a}`);return a};const k=(c,a)=>c.outerHTML===a.outerHTML,l=(a,b)=>!!a.find(a=>k(a,b));var m=(a,b,c)=>new Promise((d,e)=>{if(c[a.src])return d();const f=document.createElement('script');if(a.src)return f.addEventListener('load',d),f.addEventListener('error',e),f.src=a.src,void b.appendChild(f);const g=`(() => {${a.innerText}})();`;f.appendChild(document.createTextNode(g)),b.appendChild(f),d()});const n=function({attrs:g}={}){return this instanceof n?void(this.attrs=Object.assign({once:'data-pre-once',keep:'data-pre-keep',cache:'data-pre-cached'},g),this.loader={},this.loaded={},this.cache=h(100,!0,0,1e4),this.cache.onchange=()=>this.links(),this.init=a,this.load=b,this.preload=b,this.open=c,this.links=d,this.head=e,this.replace=f,this.report=a=>console.log('Error:',a),window.onpopstate=a=>{this.open(a.currentTarget.location.href,{history:!1})},this.init()):new n({attrs:g})};var o=new n;return o});
1
+ (()=>{class l{max;ttl;onchange=()=>{};items=new Map;constructor(e=100,t=1e4){this.max=e,this.ttl=t}keys(){return[...this.items.keys()]}get(e){let t=this.items.get(e);if(!t)return;if(this.ttl&&t.expires<performance.now()){this.items.delete(e),this.onchange();return}return this.items.delete(e),this.items.set(e,t),t.value}set(e,t){if(this.items.delete(e),this.items.set(e,{value:t,expires:performance.now()+this.ttl}),this.items.size>this.max)this.items.delete(this.items.keys().next().value);this.onchange()}clear(){this.items.clear(),this.onchange()}}function f(e){if(e.hasAttribute(this.attrs.ignore))return!1;let t=e.getAttribute("href");if(!t)return!1;if(t[0]==="#")return!1;if(e.host!==location.host)return!1;let r=e.getAttribute("target");if(r&&r!=="_self")return!1;if(e.hasAttribute("download"))return!1;if(e.protocol&&!/^https?:$/.test(e.protocol))return!1;return!0}var y=(e,t)=>e.outerHTML===t.outerHTML,L=(e,t)=>e.some((r)=>y(r,t));function u(e,t){let r=0;[...t.children].forEach((o)=>{let i=o.cloneNode(!0),n=[...e.children],s=n.slice(r);if(L(s,i)){r+=s.findIndex((p)=>y(i,p)),n[r].setAttribute(this.attrs.keep,"true");return}i.setAttribute(this.attrs.keep,"true");let c=n[r];if(c)c.parentNode.insertBefore(i,c.nextSibling);else e.appendChild(i)}),[...e.children].forEach((o)=>{if(o.hasAttribute(this.attrs.keep))o.removeAttribute(this.attrs.keep);else e.removeChild(o)})}var a=(e,t=document)=>[...t.querySelectorAll(e)];async function g(e="a"){if(await new Promise((t)=>{if(document.readyState!=="loading")return t();document.addEventListener("DOMContentLoaded",()=>t(),!1)}),!this.started){this.started=!0;let t=location.href.split("#")[0];this.cache.set(t,{href:t,html:document.documentElement.outerHTML})}a(`script[${this.attrs.once}]`).forEach((t)=>{this.loaded[t.src]=!0}),a(e).forEach((t)=>{if(!this.follows(t))return;let r=(o)=>this.load(o.currentTarget).catch((i)=>this.report(i));t.addEventListener("mouseover",r),t.addEventListener("touchstart",r),t.addEventListener("click",(o)=>(o.preventDefault(),this.open(o.currentTarget)))})}function E(e="a"){let t=this.cache.keys();a(e).forEach((r)=>{let o=r.href.split("#")[0];if(t.includes(o)){if(!r.hasAttribute(this.attrs.cache))r.setAttribute(this.attrs.cache,"true")}else if(r.hasAttribute(this.attrs.cache))r.removeAttribute(this.attrs.cache)})}var d=(e,t="")=>{let r=e?.href||e;if(!r)throw Error(`${t} the argument has to be an url or a <a> element`);let o=r;if(o.nodeName==="A")throw Error(`${t} argument element is missing the href="" or it is empty`);if(o.nodeName)throw Error(`${t} expected an anchor <a> element, received "${o.nodeName}"`);if(typeof r!=="string")throw Error(`${t} expected a string, received ${typeof r}`);try{return new URL(r,location.href).href}catch{throw Error(`${t} expected a valid url, received "${r}"`)}};async function m(e=""){let t=d(e,"pre.load():").split("#")[0],r=this.loader[t];if(r)return r;let o=this.cache.get(t);if(o)return o;return this.loader[t]=fetch(t).then((i)=>{if(!i.ok)this.report(Error(i.statusText));return i.text()}).then((i)=>{let n={href:t,html:i};return this.cache.set(t,n),n}).finally(()=>delete this.loader[t]),this.loader[t]}async function x(e,{history:t}={}){let r;try{r=d(e,"pre.open():");let[o,...i]=r.split("#"),n=i.join("#");if(n&&o===location.href.split("#")[0]){location.hash=n;return}let s=await this.load(r);return await this.replace(r,s.html,{history:t}),this.init()}catch(o){if(this.report(o),r)location.href=r}}var P=(e,t,r)=>new Promise((o,i)=>{if(r[e.src])return o();let n=document.createElement("script");if(e.src){n.addEventListener("load",()=>o()),n.addEventListener("error",i),n.src=e.src,t.appendChild(n);return}n.appendChild(document.createTextNode(`(() => {${e.textContent}})();`)),t.appendChild(n),o()});function w(e,t,{history:r=!0}={}){if(r)window.history.pushState({href:e},"",e);let o=new DOMParser().parseFromString(t,"text/html"),i=document.body;i.replaceChildren(...o.body.childNodes),this.head(document.head,o.head);let n=e.split("#").slice(1).join("#"),s=n?document.getElementById(n):null;if(s)s.scrollIntoView();else window.scrollTo(0,0);let c=([h,...A])=>{if(!h)return Promise.resolve();return P(h,i,this.loaded).then(()=>c(A))},p=a("script").filter((h)=>!h.hasAttribute(this.attrs.ignore));return c(p)}class v{attrs;loader={};loaded={};started=!1;cache=new l(100,1e4);follows=f;init=g;load=m;preload=m;open=x;links=E;head=u;replace=w;report=(e)=>console.log("Error:",e);constructor({attrs:e}={}){this.attrs={once:"data-pre-once",ignore:"data-pre-ignore",keep:"data-pre-keep",cache:"data-pre-cached",...e},this.cache.onchange=()=>this.links(),window.addEventListener("popstate",()=>{this.open(location.href,{history:!1})}),this.init()}}var b=v;if(!window.pre)window.pre=new b;})();
package/package.json CHANGED
@@ -1,37 +1,35 @@
1
1
  {
2
2
  "name": "premonition",
3
- "version": "0.7.0",
4
- "description": "Convert your website to a Progressive Web App with a single link. Or just make it faster, it's up to you. Include the script in your website to see it work:",
5
- "main": "index.min.js",
6
- "scripts": {
7
- "build": "rollup -c",
8
- "prepublishOnly": "npm run build && git add index.min.js && git commit -m \"Built package\"",
9
- "test": "echo \"Error: no test so far\"",
10
- "watch": "rollup -c --watch"
11
- },
12
- "repository": {
13
- "type": "git",
14
- "url": "git+https://github.com/franciscop/premonition.git"
15
- },
16
- "keywords": [],
17
- "author": "",
3
+ "version": "0.8.0",
4
+ "description": "Make your website load instantly by preloading links and swapping pages in place",
5
+ "repository": "github:franciscop/premonition",
6
+ "author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
7
+ "funding": "https://www.paypal.me/franciscopresencia/19",
18
8
  "license": "MIT",
19
- "bugs": {
20
- "url": "https://github.com/franciscop/premonition/issues"
9
+ "keywords": [
10
+ "spa",
11
+ "preload",
12
+ "prefetch",
13
+ "performance",
14
+ "navigation",
15
+ "browser"
16
+ ],
17
+ "scripts": {
18
+ "build": "bun build src/browser.ts --outfile=index.min.js --format=iife --minify",
19
+ "demo": "bun demo/server.ts",
20
+ "lint": "tsc",
21
+ "size": "echo $(gzip -c index.min.js | wc -c) bytes",
22
+ "start": "bun test --watch",
23
+ "test": "bun test"
21
24
  },
22
- "homepage": "https://github.com/franciscop/premonition#readme",
25
+ "main": "index.min.js",
26
+ "type": "module",
27
+ "files": [
28
+ "index.min.js"
29
+ ],
23
30
  "devDependencies": {
24
- "babel-core": "^6.26.3",
25
- "babel-plugin-external-helpers": "^6.22.0",
26
- "babel-preset-env": "^1.7.0",
27
- "builtin-modules": "^3.0.0",
28
- "picnic": "^6.5.0",
29
- "rollup": "^0.62.0",
30
- "rollup-plugin-babel": "^3.0.5",
31
- "rollup-plugin-babel-minify": "^5.0.0",
32
- "rollup-plugin-commonjs": "^9.1.3",
33
- "rollup-plugin-node-resolve": "^3.3.0",
34
- "rollup-plugin-uglify": "^4.0.0",
35
- "tiny-lru": "^1.6.1"
31
+ "@happy-dom/global-registrator": "^20.9.0",
32
+ "@types/bun": "^1.3.0",
33
+ "typescript": "5.9"
36
34
  }
37
35
  }
package/readme.md CHANGED
@@ -1,194 +1,156 @@
1
- ---
2
- title: premonition.js
3
- layout: home.hbs
4
- ---
1
+ # premonition [![npm install premonition](https://img.shields.io/badge/npm%20install-premonition-blue.svg)](https://www.npmjs.com/package/premonition) [![test badge](https://github.com/franciscop/premonition/workflows/tests/badge.svg "test badge")](https://github.com/franciscop/premonition/actions) [![gzip size](https://img.badgesize.io/franciscop/premonition/master/index.min.js.svg?label=gzip&logo=&compression=gzip)](https://github.com/franciscop/premonition/blob/master/index.min.js) [![dependencies](https://img.shields.io/badge/dependencies-0-limegreen.svg)](https://github.com/franciscop/premonition/blob/master/package.json)
5
2
 
6
- # Preload App
7
-
8
- > **Early experiment**, use at your own [high] risk.
9
-
10
- Make your website load *very* fast. Converts your website to a Single Page Application. Or just make it faster, it's up to you. Include the script in your website to see it work:
3
+ Make your website load instantly: links are fetched when the user hovers them, and opening one swaps the page in place instead of doing a full reload.
11
4
 
12
5
  ```html
13
- <script src="https://cdn.jsdelivr.net/npm/premonition" data-pre-once></script>
6
+ <script src="https://cdn.jsdelivr.net/npm/premonition"></script>
14
7
  ```
15
8
 
16
- That's it, your website should be noticeably faster now. You can use the [Javascript API](#api) or configure it [with the options](#options). See [the troubleshooting guide](#troubleshooting) if you have any issue.
17
-
18
- > **Experiment tip**: open [the CDN link](https://cdn.jsdelivr.net/npm/premonition), copy the JS and paste it into your website console for testing this project in a real env in ~10s. If there's any error you'll have to do this again.
19
-
20
-
21
-
22
- ## API
23
-
24
- You now have this API available:
9
+ That is the whole setup. Every same-site link on the page is now preloaded on hover and opened without a page reload.
25
10
 
26
- ```js
27
- // Parse the full current website from scratch
28
- pre.init(OPTS); // Note: it is called onload
29
-
30
- // Open the passed urls or link element
31
- pre.open(URL);
32
-
33
- // [pre]load a link into memory for later opening
34
- pre.load(URL);
35
-
36
- // Replace the current page by a new url+html
37
- pre.replace('/hello', '<html>...</html>');
38
-
39
- // Cache instance. Using tiny-lru: https://www.npmjs.com/package/tiny-lru
40
- pre.cache = lru(100, false, 0, 100000);
41
- pre.cache.get(HREF);
42
- // cache.set is mostly internal; highly prefer pre.load(HREF)
43
- pre.cache.set(HREF, { href: HREF, html: HTML });
44
- pre.cache.expire = 1000; // Maximum time of the page
45
- pre.cache.clear(); // Remove all items from cache (login/logout)
46
-
47
- // All events happen just BEFORE the actual name, so they can be prevented
48
- // NOTE: not available yet
49
- pre.on('ready', e => {}); // The DOM is ready to be used
50
- pre.on('init', e => {}); // The script initialization is called
51
- pre.on('load', e => {}); // A single URL is attempted to be preloaded
52
- pre.on('preload', e => {}); // Alias for 'load'
53
- pre.on('open', e => {}); // An HREF is going to be open (load+replace)
54
- pre.on('replace', e => {}); // The website content is going to be updated
55
- pre.on('fetch', e => {}); // A HREF is going to be fetched
56
- pre.on('error', e => {}); // There is an error somewhere
57
- pre.on('cache.get', e => {}); // When a link is read from cache
58
- pre.on('cache.set', e => {}); // Update the cache with more HREF+HTML
59
- pre.on('cache.remove', e => {}); // Some HTML is removed from the cache
60
- ```
61
-
62
-
63
-
64
- ## Options
65
-
66
- There are global options and per-element options:
67
-
68
- - Global options can be specified on the `<body>` element or when calling `pre.init({ OPTIONS })`.
69
- - Element options should be specified in the element (e.g., `<script>`) or when calling the specific method.
11
+ > **Early experiment**, use at your own [high] risk.
70
12
 
71
- When using HTML elements, they are always prefixed by `data-pre-NAME=""` (except `data-pre=""`, which is the mode).
13
+ ## Getting started
72
14
 
73
- Examples:
15
+ Load it from a CDN, which needs no build step and no configuration:
74
16
 
75
17
  ```html
76
- <body data-pre="pwa">
77
- <body data-pre="pwa" data-pre-expire="100s">
78
- <body data-pre="pwa" data-pre-expire="100s" data-pre-elements="100">
79
- <body data-pre="preload">
18
+ <script src="https://cdn.jsdelivr.net/npm/premonition"></script>
80
19
  ```
81
20
 
21
+ Or install it and import it, which does the same setup and defines the same `pre` global:
82
22
 
83
-
84
- ### `mode`
85
-
86
- There are several modes that you can tweak within the `<body>` tag:
87
-
88
- ```html
89
- <body data-pre="pwa">...</body>
90
- <body data-pre="preload">...</body>
91
- <body data-pre="spa">...</body>
92
- <body data-pre="off">...</body>
23
+ ```bash
24
+ npm install premonition
93
25
  ```
94
26
 
95
- These are useful depending on your type of website and desired performance boost:
96
-
97
- - `pwa` (Progressive Web App): the most aggressive one, will load and cache all the internal pages linked from the current page for 24h. Note: still needs a `manifest.json` to actually become a PWA.
98
- - `preload`: will fetch the link when hovering a link for significantly faster loading. Cache the pages for 100 seconds.
99
- - `spa` (Single Page Application): will fetch the page onmousedown and load the new page in-body. Make the page seem slightly faster. Cache the pages for 10 seconds.
100
- - `off`: will not try to load or cache anything. You can now call the different methods to hook up specific link in specific situations.
101
-
102
- **Default mode**: In normal web usage it will use `preload`, but if it detects it's installed in a device as a PWA it will trigger the `pwa` mode. If you want tight control specify the mode manually as shown above.
103
-
104
- > The cache is only in-memory; any page reload or the PWA getting closed will clear it.
105
-
106
- #### Picking a mode
27
+ ```js
28
+ import 'premonition';
29
+ ```
107
30
 
108
- The mode will determine how aggressively the preload and cache is used and your users data consumption. Until you get used to the details, just don't pick a mode and let premonition.js choose for you.
31
+ Either way there is nothing to call: premonition hooks up the page as soon as it loads.
109
32
 
110
- Premonition *only* loads the html and scripts (see [skipping scripts](#load-once)). These normally are quite light and ~10-20 average HTML pages are the equivalent of a single image. So, if your site is fairly small, `pwa` is the recommended way to go.
33
+ ## Usage
111
34
 
112
- If you are in the middle-ground, then `preload` is nice in that it will boost greatly page load speed while only preloading selectively those links that are hovered.
35
+ Premonition attaches itself to every `<a>` on the page that points to the same site, and then:
113
36
 
114
- If you want a very light experience without any wasted requests for the user but still slightly faster loads, then use the `spa` mode. It has slightly better performance in mobile (300ms).
37
+ 1. On `mouseover` or `touchstart`, it fetches the target page and keeps the HTML in memory.
38
+ 2. On `click`, it swaps the current `<body>` and `<head>` for the new ones and pushes the URL to the history, so the back button keeps working.
115
39
 
116
- Finally, turn it off if you want to manually and selectively load links in some situations. Use this to load links only when they become visible on scrolling for example.
40
+ It leaves these link types alone, because swapping the body would break things: other hosts, `target="_blank"`, `download`, schemes like `mailto:` or `tel:`, bare `#fragment` links, and anything marked with `data-pre-ignore`.
117
41
 
42
+ The cache holds the 100 most recent pages for 10 seconds each, in memory only: a real page reload clears it.
118
43
 
44
+ Three attributes change what premonition does with a given element, and they are how you fix a script or a link that misbehaves across page swaps.
119
45
 
120
46
  ### `once`
121
47
 
122
- Evaluate a `<script>` only once ever even if it's in the new page:
48
+ Evaluate a `<script>` only once, even when it is also in the new page:
123
49
 
124
50
  ```html
125
51
  <script src="..." data-pre-once></script>
126
52
  ```
127
53
 
128
- If the script src target does not exist in the old page but exists in the new page, it will be run. If it already exists in the old page, it will be skipped.
54
+ If the script `src` is not in the old page but is in the new one, it runs. If it already ran, it is skipped.
55
+
56
+ **Add this option** to libraries that only define a global API, since they should only run once:
129
57
 
130
- **Add this option** for these kind of libraries since they *only define a global API* so we only want them to run once:
131
58
  - jQuery.js
132
59
  - moment.js
133
60
  - etc.
134
61
 
135
- **Do not add** to scripts that run some code against the current HTML since those will very likely need to run it with the new HTML:
136
- - Inline scripts (will be ignored). To avoid re-rendering inline scripts, see [the option ignore](#ignore).
137
- - prism.js: this will automatically highlight the page code snippets, so it needs to be run on every pageload.
62
+ **Do not add it** to scripts that run against the current HTML, since those need to run again with the new HTML:
63
+
64
+ - prism.js: it highlights the code snippets on the page, so it needs to run on every page load.
138
65
  - etc.
139
66
 
140
- Compare the options for loading a script from the new page:
67
+ ### `ignore`
141
68
 
142
- - `<script src="..."></script>`: run the full script after the HTML has loaded.
143
- - `<script src="..." data-pre-once></script>`: run the script only if the `src` is not in the previous page(s).
144
- - `<script src="..." data-pre-ignore></script>`: completely ignore the script. Will not be run at all in any case.
69
+ Premonition will completely ignore the element:
145
70
 
71
+ ```html
72
+ <a href="/logout" data-pre-ignore>Log out</a>
73
+ <script src="..." data-pre-ignore></script>
74
+ ```
146
75
 
76
+ Useful for links you do not want preloaded, and for scripts you do not want re-evaluated. Inline scripts that must only run on a real page load are the common case: we cannot stop them on a full refresh, but we can avoid re-running them here.
147
77
 
148
- ### `ignore`
78
+ For links that change state, we would recommend following the HTTP specification instead, so that links only make GET requests with no side effects, and side effects live in a `<form>`.
149
79
 
150
- Premonition will completely ignore a script, link, etc. with this option:
80
+ Comparing the three ways a script from the new page can be treated:
151
81
 
152
- ```html
153
- <a href="/logout" data-pre-ignore></script>
154
- <script src="..." data-pre-ignore></script>
82
+ - `<script src="..."></script>`: run it after the new HTML is in place.
83
+ - `<script src="..." data-pre-once></script>`: run it only if the `src` did not run before.
84
+ - `<script src="..." data-pre-ignore></script>`: never run it.
85
+
86
+ ### `cached`
87
+
88
+ Premonition sets `data-pre-cached` on the links whose page is currently in the cache, and removes it when it is not. It is written by the library, not by you, and it is there so you can style preloaded links:
89
+
90
+ ```css
91
+ a[data-pre-cached] { border-bottom: 1px solid green; }
155
92
  ```
156
93
 
157
- This is useful for some links that you do not want preloaded or scripts that you do not want re-evaluated. Examples:
94
+ ## Javascript API
158
95
 
159
- - Inline scripts that must be run only onload. We cannot prevent those from running on a full page refresh, but we can avoid re-running those.
160
- - Links that are making a request that changes some state. We would recommend in this situation to follow the `HTTP` specification, so links only do GET requests which have no side effects. Leave side effects for `<form>`.
96
+ > **You do not need any of this.** The script tag sets everything up on its own.
97
+ > The calls below are for the few things it cannot do for you: clearing the cache when
98
+ > someone logs in or out, hooking up links that your own Javascript added after the
99
+ > page loaded, and sending errors somewhere other than the console.
161
100
 
162
- Compare the options for loading a script from the new page:
101
+ Loading it as a `<script>` exposes the instance as the `pre` global:
163
102
 
164
- - `<script src="..."></script>`: run the full script after the HTML has loaded.
165
- - `<script src="..." data-pre-once></script>`: run the script only if the `src` is not in the previous page(s).
166
- - `<script src="..." data-pre-ignore></script>`: completely ignore the script. Will not be run at all in any case.
103
+ ```js
104
+ // Parse the current page and hook up its links. Called automatically onload
105
+ pre.init(SELECTOR); // default: 'a'
167
106
 
107
+ // Open a url or <a> element, loading it first if it is not cached yet
108
+ pre.open(URL);
168
109
 
110
+ // Preload a url or <a> element into memory for a later .open()
111
+ pre.load(URL);
112
+ pre.preload(URL); // Alias for .load()
169
113
 
170
- ## Troubleshooting
114
+ // Replace the current page with a new url + html
115
+ pre.replace('/hello', '<html>...</html>');
171
116
 
172
- **Very early experiment**, [please open an issue](https://github.com/franciscop/premonition) if you need any help.
117
+ // Update the data-pre-cached attributes. Called automatically on cache changes
118
+ pre.links(SELECTOR); // default: 'a'
173
119
 
120
+ // Called with any error that happens along the way; overwrite it to report them
121
+ pre.report = error => console.log('Error:', error);
122
+ ```
174
123
 
124
+ The cache is a small LRU store that you can read and clear directly:
175
125
 
176
- ## FAQ, credit and alternatives
126
+ ```js
127
+ pre.cache.get(HREF); // Returns { href, html } or undefined
128
+ pre.cache.set(HREF, { href: HREF, html: HTML }); // Prefer pre.load(HREF)
129
+ pre.cache.keys(); // The hrefs currently cached
130
+ pre.cache.clear(); // Remove all items from cache (login/logout)
131
+ pre.cache.max = 100; // Maximum number of pages kept
132
+ pre.cache.ttl = 10000; // Milliseconds before a page is considered stale
133
+ ```
177
134
 
178
- ### What about [Instant Click](http://instantclick.io/)?
135
+ ## Demo
179
136
 
180
- I love it! It was the main inspiration for Premonition.js. However, it is ridden with bugs and [development seems to be stopped](https://github.com/dieulot/instantclick/) with [pull requests languishing](https://github.com/dieulot/instantclick/pulls). These bugs/lack of features were show-stoppers for me:
137
+ There is a small site under `demo/` to try it by hand. Its pages are served with an
138
+ artificial delay, so the difference between a cold click and a preloaded one is easy to see:
181
139
 
182
- - No cache. This means that if you move your mouse around, the links will be fetched again and again.
183
- - Important [race conditions](https://github.com/dieulot/instantclick/pull/85): you never know which script will finish first, which will definitely lead to bugs. I fixed this by loading the scripts sequentially.
184
- - Plug-n-play: I would like to just drop the script into some of my projects, so I worked very hard to make this work by default. Instantclick seems to be more oriented to using the API.
140
+ ```bash
141
+ npm run build && npm run demo # http://localhost:3200/demo/
142
+ ```
185
143
 
144
+ ## FAQ
186
145
 
187
- ### What about [Turbolinks](https://github.com/turbolinks/turbolinks)?
146
+ ### What about [InstantClick](http://instantclick.io/)?
188
147
 
189
- Another very awesome project. However, its scope seems to be a lot smaller. The similar feature with Premonition is `mode: spa`. Use Turbolinks if you want a very well tested and documented library, use Premonition if you want faster pages.
148
+ I love it, it was the main inspiration for Premonition. However it is ridden with bugs and [development seems to be stopped](https://github.com/dieulot/instantclick/), with [pull requests languishing](https://github.com/dieulot/instantclick/pulls). These were show-stoppers for me:
190
149
 
150
+ - No cache: moving the mouse around fetches the same links again and again.
151
+ - Important [race conditions](https://github.com/dieulot/instantclick/pull/85): you never know which script finishes first, which leads to bugs. Premonition loads the scripts sequentially.
152
+ - Plug and play: I wanted to drop the script into a project and have it work by default, while InstantClick is more oriented towards using its API.
191
153
 
192
- ### Why do you use [Tiny-LRU](https://github.com/avoidwork/tiny-lru)?
154
+ ### What about [Turbolinks](https://github.com/turbolinks/turbolinks)?
193
155
 
194
- Because it is awesome! The LRU algorithm ensures that only the most recent URLs are cached, improving the memory usage and reducing the cache misses.
156
+ Another very nice project, with a smaller scope: it swaps pages on click, while Premonition also preloads them on hover.
package/.babelrc DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "presets": [
3
- ["env", {
4
- "modules": false,
5
- "targets": { "uglify": true }
6
- }]
7
- ],
8
- "plugins": [
9
- "external-helpers"
10
- ]
11
- }
package/demo/_post.hbs DELETED
@@ -1,30 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
- <title>{{title}}</title>
8
- <link rel="stylesheet" href="/web/style.min.css">
9
- </head>
10
- <body>
11
- <nav>
12
- <a class="brand" href="/">premonition.js</a>
13
- <div class="menu">
14
- <a href="/demo">Demo</a>
15
- <a href="https://github.com/franciscop/premonition">Github</a>
16
- </div>
17
- </nav>
18
-
19
- <article>
20
- <a href="/demo/">All Articles</a>
21
- {{{body}}}
22
- {{> readnext}}
23
- </article>
24
- Hello
25
- <script src="/index.min.js" data-pre-once></script>
26
- <script type="text/javascript">
27
- console.log('NO PARSING NEEDED');
28
- </script>
29
- </body>
30
- </html>
@@ -1,3 +0,0 @@
1
- <h2>Read Next</h2>
2
-
3
- Example of a partial to see how they work
@@ -1,35 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
- <title>Entry A</title>
8
- <link rel="stylesheet" href="/web/style.min.css">
9
- </head>
10
- <body>
11
- <nav>
12
- <a class="brand" href="/">premonition.js</a>
13
- <div class="menu">
14
- <a href="/demo">Demo</a>
15
- <a href="https://github.com/franciscop/premonition">Github</a>
16
- </div>
17
- </nav>
18
-
19
- <article>
20
- <a href="/demo/">All Articles</a>
21
- <h1 id="entry-a">Entry A</h1>
22
- <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
23
- <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
24
-
25
- <h2>Read Next</h2>
26
-
27
- Example of a partial to see how they work
28
- </article>
29
- Hello
30
- <script src="/index.min.js" data-pre-once></script>
31
- <script type="text/javascript">
32
- console.log('NO PARSING NEEDED');
33
- </script>
34
- </body>
35
- </html>
@@ -1,12 +0,0 @@
1
- ---
2
- layout: post.hbs
3
- title: Entry A
4
- date: 2018-06-19 20:00
5
- categories: jekyll mini clone
6
- ---
7
-
8
- # Entry A
9
-
10
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
11
-
12
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
@@ -1,35 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
- <title>Entry B</title>
8
- <link rel="stylesheet" href="/web/style.min.css">
9
- </head>
10
- <body>
11
- <nav>
12
- <a class="brand" href="/">premonition.js</a>
13
- <div class="menu">
14
- <a href="/demo">Demo</a>
15
- <a href="https://github.com/franciscop/premonition">Github</a>
16
- </div>
17
- </nav>
18
-
19
- <article>
20
- <a href="/demo/">All Articles</a>
21
- <h1 id="entry-b">Entry B</h1>
22
- <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
23
- <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
24
-
25
- <h2>Read Next</h2>
26
-
27
- Example of a partial to see how they work
28
- </article>
29
- Hello
30
- <script src="/index.min.js" data-pre-once></script>
31
- <script type="text/javascript">
32
- console.log('NO PARSING NEEDED');
33
- </script>
34
- </body>
35
- </html>
@@ -1,12 +0,0 @@
1
- ---
2
- layout: post.hbs
3
- title: Entry B
4
- date: 2018-06-19 20:00
5
- categories: jekyll mini clone
6
- ---
7
-
8
- # Entry B
9
-
10
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
11
-
12
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
@@ -1,35 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
- <title>Entry C</title>
8
- <link rel="stylesheet" href="/web/style.min.css">
9
- </head>
10
- <body>
11
- <nav>
12
- <a class="brand" href="/">premonition.js</a>
13
- <div class="menu">
14
- <a href="/demo">Demo</a>
15
- <a href="https://github.com/franciscop/premonition">Github</a>
16
- </div>
17
- </nav>
18
-
19
- <article>
20
- <a href="/demo/">All Articles</a>
21
- <h1 id="entry-c">Entry C</h1>
22
- <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
23
- <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
24
-
25
- <h2>Read Next</h2>
26
-
27
- Example of a partial to see how they work
28
- </article>
29
- Hello
30
- <script src="/index.min.js" data-pre-once></script>
31
- <script type="text/javascript">
32
- console.log('NO PARSING NEEDED');
33
- </script>
34
- </body>
35
- </html>
@@ -1,12 +0,0 @@
1
- ---
2
- layout: post.hbs
3
- title: Entry C
4
- date: 2018-06-19 20:00
5
- categories: jekyll mini clone
6
- ---
7
-
8
- # Entry C
9
-
10
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
11
-
12
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
@@ -1,35 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
- <title>Entry D</title>
8
- <link rel="stylesheet" href="/web/style.min.css">
9
- </head>
10
- <body>
11
- <nav>
12
- <a class="brand" href="/">premonition.js</a>
13
- <div class="menu">
14
- <a href="/demo">Demo</a>
15
- <a href="https://github.com/franciscop/premonition">Github</a>
16
- </div>
17
- </nav>
18
-
19
- <article>
20
- <a href="/demo/">All Articles</a>
21
- <h1 id="entry-d">Entry D</h1>
22
- <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
23
- <p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
24
-
25
- <h2>Read Next</h2>
26
-
27
- Example of a partial to see how they work
28
- </article>
29
- Hello
30
- <script src="/index.min.js" data-pre-once></script>
31
- <script type="text/javascript">
32
- console.log('NO PARSING NEEDED');
33
- </script>
34
- </body>
35
- </html>