premonition 0.6.2 → 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 +1 -2
- package/package.json +28 -30
- package/readme.md +98 -82
- package/.babelrc +0 -11
- package/demo/_post.hbs +0 -30
- package/demo/_readnext.hbs +0 -3
- package/demo/entry-a/index.html +0 -35
- package/demo/entry-a/readme.md +0 -12
- package/demo/entry-b/index.html +0 -35
- package/demo/entry-b/readme.md +0 -12
- package/demo/entry-c/index.html +0 -35
- package/demo/entry-c/readme.md +0 -12
- package/demo/entry-d/index.html +0 -35
- package/demo/entry-d/readme.md +0 -12
- package/demo/entry-e/index.html +0 -35
- package/demo/entry-e/readme.md +0 -12
- package/demo/entry-f/index.html +0 -35
- package/demo/entry-f/readme.md +0 -12
- package/demo/index.hbs +0 -54
- package/demo/index.html +0 -96
- package/index.html +0 -116
- package/rollup.config.js +0 -35
- package/src/fquery.js +0 -1
- package/src/head.js +0 -38
- package/src/index.js +0 -53
- package/src/init.js +0 -30
- package/src/inject.js +0 -29
- package/src/links.js +0 -15
- package/src/load.js +0 -26
- package/src/open.js +0 -26
- package/src/replace.js +0 -35
- package/src/validate.js +0 -17
- package/web/_home.hbs +0 -29
- package/web/_nav.hbs +0 -1
- package/web/style.min.css +0 -1
- package/web/style.scss +0 -64
package/index.min.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
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
|
-
"
|
|
20
|
-
"
|
|
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
|
-
"
|
|
25
|
+
"main": "index.min.js",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"files": [
|
|
28
|
+
"index.min.js"
|
|
29
|
+
],
|
|
23
30
|
"devDependencies": {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
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,140 +1,156 @@
|
|
|
1
|
-
|
|
2
|
-
title: premonition.js
|
|
3
|
-
layout: home.hbs
|
|
4
|
-
---
|
|
1
|
+
# premonition [](https://www.npmjs.com/package/premonition) [](https://github.com/franciscop/premonition/actions) [](https://github.com/franciscop/premonition/blob/master/index.min.js) [](https://github.com/franciscop/premonition/blob/master/package.json)
|
|
5
2
|
|
|
6
|
-
|
|
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.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<script src="https://cdn.jsdelivr.net/npm/premonition"></script>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
That is the whole setup. Every same-site link on the page is now preloaded on hover and opened without a page reload.
|
|
7
10
|
|
|
8
11
|
> **Early experiment**, use at your own [high] risk.
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
## Getting started
|
|
14
|
+
|
|
15
|
+
Load it from a CDN, which needs no build step and no configuration:
|
|
11
16
|
|
|
12
17
|
```html
|
|
13
|
-
<script src="https://cdn.jsdelivr.net/npm/premonition"
|
|
18
|
+
<script src="https://cdn.jsdelivr.net/npm/premonition"></script>
|
|
14
19
|
```
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
Or install it and import it, which does the same setup and defines the same `pre` global:
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
```bash
|
|
24
|
+
npm install premonition
|
|
25
|
+
```
|
|
19
26
|
|
|
27
|
+
```js
|
|
28
|
+
import 'premonition';
|
|
29
|
+
```
|
|
20
30
|
|
|
31
|
+
Either way there is nothing to call: premonition hooks up the page as soon as it loads.
|
|
21
32
|
|
|
22
|
-
##
|
|
33
|
+
## Usage
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
Premonition attaches itself to every `<a>` on the page that points to the same site, and then:
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
pre.init(MODE); // Note: it is called onload
|
|
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.
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
pre.open(URL);
|
|
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`.
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
pre.load(URL);
|
|
42
|
+
The cache holds the 100 most recent pages for 10 seconds each, in memory only: a real page reload clears it.
|
|
35
43
|
|
|
36
|
-
|
|
37
|
-
pre.replace('/hello', '<html>...</html>');
|
|
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.
|
|
38
45
|
|
|
39
|
-
|
|
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
|
+
### `once`
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
48
|
+
Evaluate a `<script>` only once, even when it is also in the new page:
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<script src="..." data-pre-once></script>
|
|
60
52
|
```
|
|
61
53
|
|
|
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.
|
|
62
55
|
|
|
56
|
+
**Add this option** to libraries that only define a global API, since they should only run once:
|
|
63
57
|
|
|
64
|
-
|
|
58
|
+
- jQuery.js
|
|
59
|
+
- moment.js
|
|
60
|
+
- etc.
|
|
65
61
|
|
|
66
|
-
|
|
62
|
+
**Do not add it** to scripts that run against the current HTML, since those need to run again with the new HTML:
|
|
67
63
|
|
|
68
|
-
-
|
|
69
|
-
-
|
|
64
|
+
- prism.js: it highlights the code snippets on the page, so it needs to run on every page load.
|
|
65
|
+
- etc.
|
|
70
66
|
|
|
71
|
-
|
|
67
|
+
### `ignore`
|
|
72
68
|
|
|
73
|
-
|
|
69
|
+
Premonition will completely ignore the element:
|
|
74
70
|
|
|
75
71
|
```html
|
|
76
|
-
<
|
|
77
|
-
<
|
|
78
|
-
<body data-pre="pwa" data-pre-expire="100s" data-pre-elements="100">
|
|
79
|
-
<body data-pre="preload">
|
|
72
|
+
<a href="/logout" data-pre-ignore>Log out</a>
|
|
73
|
+
<script src="..." data-pre-ignore></script>
|
|
80
74
|
```
|
|
81
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.
|
|
82
77
|
|
|
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>`.
|
|
83
79
|
|
|
84
|
-
|
|
80
|
+
Comparing the three ways a script from the new page can be treated:
|
|
85
81
|
|
|
86
|
-
|
|
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.
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
<body data-pre="pwa">...</body>
|
|
90
|
-
<body data-pre="preload">...</body>
|
|
91
|
-
<body data-pre="spa">...</body>
|
|
92
|
-
<body data-pre="off">...</body>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
These are useful depending on your type of website and desired performance boost:
|
|
86
|
+
### `cached`
|
|
96
87
|
|
|
97
|
-
|
|
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.
|
|
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:
|
|
101
89
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
90
|
+
```css
|
|
91
|
+
a[data-pre-cached] { border-bottom: 1px solid green; }
|
|
92
|
+
```
|
|
105
93
|
|
|
106
|
-
|
|
94
|
+
## Javascript API
|
|
107
95
|
|
|
108
|
-
|
|
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.
|
|
109
100
|
|
|
110
|
-
|
|
101
|
+
Loading it as a `<script>` exposes the instance as the `pre` global:
|
|
111
102
|
|
|
112
|
-
|
|
103
|
+
```js
|
|
104
|
+
// Parse the current page and hook up its links. Called automatically onload
|
|
105
|
+
pre.init(SELECTOR); // default: 'a'
|
|
113
106
|
|
|
114
|
-
|
|
107
|
+
// Open a url or <a> element, loading it first if it is not cached yet
|
|
108
|
+
pre.open(URL);
|
|
115
109
|
|
|
116
|
-
|
|
110
|
+
// Preload a url or <a> element into memory for a later .open()
|
|
111
|
+
pre.load(URL);
|
|
112
|
+
pre.preload(URL); // Alias for .load()
|
|
117
113
|
|
|
114
|
+
// Replace the current page with a new url + html
|
|
115
|
+
pre.replace('/hello', '<html>...</html>');
|
|
118
116
|
|
|
117
|
+
// Update the data-pre-cached attributes. Called automatically on cache changes
|
|
118
|
+
pre.links(SELECTOR); // default: 'a'
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
// Called with any error that happens along the way; overwrite it to report them
|
|
121
|
+
pre.report = error => console.log('Error:', error);
|
|
122
|
+
```
|
|
121
123
|
|
|
122
|
-
|
|
124
|
+
The cache is a small LRU store that you can read and clear directly:
|
|
123
125
|
|
|
124
|
-
```
|
|
125
|
-
|
|
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
|
|
126
133
|
```
|
|
127
134
|
|
|
128
|
-
|
|
135
|
+
## Demo
|
|
129
136
|
|
|
130
|
-
|
|
131
|
-
|
|
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:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm run build && npm run demo # http://localhost:3200/demo/
|
|
132
142
|
```
|
|
133
143
|
|
|
144
|
+
## FAQ
|
|
134
145
|
|
|
146
|
+
### What about [InstantClick](http://instantclick.io/)?
|
|
135
147
|
|
|
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:
|
|
136
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.
|
|
137
153
|
|
|
138
|
-
|
|
154
|
+
### What about [Turbolinks](https://github.com/turbolinks/turbolinks)?
|
|
139
155
|
|
|
140
|
-
|
|
156
|
+
Another very nice project, with a smaller scope: it swaps pages on click, while Premonition also preloads them on hover.
|
package/.babelrc
DELETED
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>
|
package/demo/_readnext.hbs
DELETED
package/demo/entry-a/index.html
DELETED
|
@@ -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>
|
package/demo/entry-a/readme.md
DELETED
|
@@ -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.
|
package/demo/entry-b/index.html
DELETED
|
@@ -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>
|
package/demo/entry-b/readme.md
DELETED
|
@@ -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.
|
package/demo/entry-c/index.html
DELETED
|
@@ -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>
|
package/demo/entry-c/readme.md
DELETED
|
@@ -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.
|
package/demo/entry-d/index.html
DELETED
|
@@ -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>
|
package/demo/entry-d/readme.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
layout: post.hbs
|
|
3
|
-
title: Entry D
|
|
4
|
-
date: 2018-06-19 20:00
|
|
5
|
-
categories: jekyll mini clone
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Entry D
|
|
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.
|
package/demo/entry-e/index.html
DELETED
|
@@ -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 E</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-e">Entry E</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>
|