hyperinstant 1.0.1 → 1.0.2
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/README.md +15 -30
- package/dist/hyperinstant.js +307 -218
- package/dist/hyperinstant.min.js +1 -1
- package/package.json +2 -9
package/README.md
CHANGED
|
@@ -1,42 +1,27 @@
|
|
|
1
|
-
# HyperInstant v1.0.
|
|
1
|
+
# HyperInstant v1.0.2
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
- **
|
|
7
|
-
- **
|
|
8
|
-
- **Platform adapters** (auto: Shopify / WooCommerce / nopCommerce)
|
|
9
|
-
- **Local stats** (hit-rate) + optional debug overlay
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Quick install (CDN)
|
|
3
|
+
v1.0.2 focuses on **real-world speed**:
|
|
4
|
+
- **Prerender only on high-confidence intent** (mousedown/touchstart), not on hover
|
|
5
|
+
- **Lower overhead scheduler** (capped queue + dedupe; no heavy sorting)
|
|
6
|
+
- **Safer auto-deny** (less over-blocking)
|
|
7
|
+
- **Optional auto-tuning** (OFF by default)
|
|
14
8
|
|
|
9
|
+
## CDN
|
|
15
10
|
```html
|
|
16
11
|
<script>
|
|
17
12
|
window.HyperInstant = {
|
|
18
|
-
mode: "balanced",
|
|
19
|
-
adapter: "auto",
|
|
20
|
-
observe: "intent", // intent | intent+viewport
|
|
21
|
-
hoverDelay: 65,
|
|
13
|
+
mode: "balanced",
|
|
14
|
+
adapter: "auto",
|
|
22
15
|
debug: false
|
|
23
16
|
};
|
|
24
17
|
</script>
|
|
25
|
-
<script src="https://unpkg.com/hyperinstant@1.0.
|
|
18
|
+
<script src="https://unpkg.com/hyperinstant@1.0.2/dist/hyperinstant.min.js" defer></script>
|
|
26
19
|
```
|
|
27
20
|
|
|
28
|
-
##
|
|
29
|
-
You can add project-specific entries (strings are matched by `.includes()`):
|
|
30
|
-
|
|
21
|
+
## “Very fast” preset (nopCommerce)
|
|
31
22
|
```js
|
|
32
|
-
|
|
23
|
+
mode: "aggressive",
|
|
24
|
+
adapter: "nopcommerce",
|
|
25
|
+
hoverDelay: 35,
|
|
26
|
+
enablePrerender: false
|
|
33
27
|
```
|
|
34
|
-
|
|
35
|
-
## Debug
|
|
36
|
-
Set `debug: true` to enable:
|
|
37
|
-
- `window.HyperInstantStats` (stats API)
|
|
38
|
-
- optional mini overlay (`debugOverlay: true`)
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
MIT License.
|
package/dist/hyperinstant.js
CHANGED
|
@@ -1,300 +1,389 @@
|
|
|
1
|
-
/*! HyperInstant v1.0.
|
|
2
|
-
(function(){
|
|
1
|
+
/*! HyperInstant v1.0.2 | MIT | Putia Web */
|
|
2
|
+
(function () {
|
|
3
3
|
if (window.__HYPERINSTANT_LOADED__) return;
|
|
4
4
|
window.__HYPERINSTANT_LOADED__ = true;
|
|
5
5
|
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
6
|
+
var userCfg = window.HyperInstant || {};
|
|
7
|
+
var VERSION = "1.0.2";
|
|
8
|
+
|
|
9
|
+
function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); }
|
|
10
|
+
function isSameOrigin(url) {
|
|
11
|
+
try { return new URL(url, location.href).origin === location.origin; }
|
|
12
|
+
catch (e) { return false; }
|
|
13
|
+
}
|
|
14
|
+
function normalizeUrl(url) {
|
|
15
|
+
try { var u = new URL(url, location.href); u.hash = ""; return u.toString(); }
|
|
16
|
+
catch (e) { return ""; }
|
|
17
|
+
}
|
|
18
|
+
function supportsPrerender() {
|
|
19
|
+
try {
|
|
20
|
+
return "relList" in HTMLLinkElement.prototype &&
|
|
21
|
+
HTMLLinkElement.prototype.relList &&
|
|
22
|
+
HTMLLinkElement.prototype.relList.supports &&
|
|
23
|
+
HTMLLinkElement.prototype.relList.supports("prerender");
|
|
24
|
+
} catch (e) { return false; }
|
|
25
|
+
}
|
|
26
|
+
function getConn() {
|
|
27
|
+
var c = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
28
|
+
return {
|
|
29
|
+
saveData: !!(c && c.saveData),
|
|
30
|
+
effectiveType: (c && c.effectiveType) ? String(c.effectiveType) : "unknown",
|
|
31
|
+
downlink: (c && typeof c.downlink === "number") ? c.downlink : null,
|
|
32
|
+
rtt: (c && typeof c.rtt === "number") ? c.rtt : null
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function inNav(a) {
|
|
36
|
+
try { return !!a.closest("nav, header, [role='navigation'], .header, .site-header"); }
|
|
37
|
+
catch (e) { return false; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function applyModeDefaults(cfg) {
|
|
41
|
+
var mode = String(cfg.mode || "safe").toLowerCase();
|
|
42
|
+
var out = Object.assign({}, cfg);
|
|
43
|
+
|
|
44
|
+
// v1.0.2: prerender is intentionally conservative to avoid slowing pages
|
|
45
|
+
if (mode === "aggressive") {
|
|
46
|
+
out.maxPrefetch = out.maxPrefetch ?? 10;
|
|
47
|
+
out.maxPrerender = out.maxPrerender ?? 1;
|
|
48
|
+
out.hoverDelay = out.hoverDelay ?? 35;
|
|
49
|
+
} else if (mode === "balanced") {
|
|
50
|
+
out.maxPrefetch = out.maxPrefetch ?? 6;
|
|
51
|
+
out.maxPrerender = out.maxPrerender ?? 0;
|
|
52
|
+
out.hoverDelay = out.hoverDelay ?? 65;
|
|
53
|
+
} else {
|
|
54
|
+
out.maxPrefetch = out.maxPrefetch ?? 4;
|
|
55
|
+
out.maxPrerender = out.maxPrerender ?? 0;
|
|
56
|
+
out.hoverDelay = out.hoverDelay ?? 75;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
out.adapter = out.adapter ?? "auto";
|
|
60
|
+
out.observe = out.observe ?? "intent";
|
|
61
|
+
out.events = out.events ?? ["mouseover", "touchstart", "mousedown", "focusin"];
|
|
62
|
+
out.deny = out.deny ?? [];
|
|
22
63
|
out.debug = !!out.debug;
|
|
23
64
|
out.debugOverlay = !!out.debugOverlay;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
out.
|
|
65
|
+
|
|
66
|
+
// new in 1.0.2
|
|
67
|
+
out.enablePrerender = (out.enablePrerender === undefined) ? true : !!out.enablePrerender;
|
|
68
|
+
out.autoTune = !!out.autoTune; // OFF by default
|
|
69
|
+
out.maxQueue = clamp(out.maxQueue ?? 60, 10, 200);
|
|
70
|
+
|
|
27
71
|
return out;
|
|
28
72
|
}
|
|
29
73
|
|
|
30
|
-
function hasMeta(name){ return !!document.querySelector('meta[name="'+name+'"]'); }
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
[
|
|
34
|
-
|
|
74
|
+
function hasMeta(name) { return !!document.querySelector('meta[name="' + name + '"]'); }
|
|
75
|
+
|
|
76
|
+
function shopifyAdapter(cfg) {
|
|
77
|
+
var deny = new Set([].concat(cfg.deny || []));
|
|
78
|
+
["/cart", "/checkout", "/account", "/account/login", "/account/register", "/account/logout", "/search",
|
|
79
|
+
"cart/add", "cart/change", "cart/update", "variant=", "sections="].forEach(function (x) { deny.add(x); });
|
|
80
|
+
return Object.assign({}, cfg, { deny: Array.from(deny) });
|
|
35
81
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
[
|
|
39
|
-
|
|
82
|
+
|
|
83
|
+
function wooAdapter(cfg) {
|
|
84
|
+
var deny = new Set([].concat(cfg.deny || []));
|
|
85
|
+
["/cart", "/checkout", "/my-account", "add-to-cart", "wc-ajax", "remove_item", "update_cart", "apply_coupon"].forEach(function (x) { deny.add(x); });
|
|
86
|
+
return Object.assign({}, cfg, { deny: Array.from(deny) });
|
|
40
87
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
[
|
|
44
|
-
|
|
88
|
+
|
|
89
|
+
function nopAdapter(cfg) {
|
|
90
|
+
var deny = new Set([].concat(cfg.deny || []));
|
|
91
|
+
["/shoppingcart", "/checkout", "/login", "/register", "/logout", "/customer", "/wishlist", "/compareproducts",
|
|
92
|
+
"/search", "?q=", "addproducttocart", "updateshoppingcart", "updatecart", "deleteitem", "returnurl=", "token=", "ajax", "flyout"].forEach(function (x) { deny.add(x); });
|
|
93
|
+
return Object.assign({}, cfg, { deny: Array.from(deny) });
|
|
45
94
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (name==="
|
|
50
|
-
if (name==="
|
|
51
|
-
if (name==="
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
try{ if (window.
|
|
55
|
-
try{ if (window.
|
|
56
|
-
|
|
95
|
+
|
|
96
|
+
function detectAdapter(name) {
|
|
97
|
+
name = String(name || "auto").toLowerCase();
|
|
98
|
+
if (name === "shopify") return shopifyAdapter;
|
|
99
|
+
if (name === "woocommerce") return wooAdapter;
|
|
100
|
+
if (name === "nopcommerce") return nopAdapter;
|
|
101
|
+
if (name === "generic") return function (c) { return c; };
|
|
102
|
+
|
|
103
|
+
try { if (window.Shopify || hasMeta("shopify-digital-wallet") || hasMeta("shopify-checkout-api-token")) return shopifyAdapter; } catch (e) { }
|
|
104
|
+
try { if (window.wc_add_to_cart_params || window.wc_cart_fragments_params || (document.body && String(document.body.className || "").includes("woocommerce"))) return wooAdapter; } catch (e) { }
|
|
105
|
+
try { if (window.nopCommerce || document.querySelector("a[href*='shoppingcart']") || document.querySelector("form[action*='addproducttocart']")) return nopAdapter; } catch (e) { }
|
|
106
|
+
return function (c) { return c; };
|
|
57
107
|
}
|
|
58
108
|
|
|
59
|
-
|
|
60
|
-
var
|
|
61
|
-
var AUTO_PARAMS=["add","remove","update","change","delete","token","variant","sections","returnurl","redirect"];
|
|
109
|
+
// Safer auto-deny (avoid broad keywords like "order" that can hit benign pages)
|
|
110
|
+
var AUTO_PATH_PREFIX = ["/checkout", "/cart", "/shoppingcart", "/account", "/my-account", "/logout", "/login", "/register"];
|
|
111
|
+
var AUTO_PARAMS = ["add", "remove", "update", "change", "delete", "token", "variant", "sections", "returnurl", "redirect"];
|
|
112
|
+
var AUTO_KEYWORDS = ["add-to-cart", "addtocart", "addproducttocart", "wc-ajax", "admin-ajax", "sections=", "variant="];
|
|
62
113
|
|
|
63
|
-
function shouldDeny(url,
|
|
114
|
+
function shouldDeny(url, cfg) {
|
|
64
115
|
if (!url) return true;
|
|
65
116
|
if (!isSameOrigin(url)) return true;
|
|
117
|
+
|
|
66
118
|
var u = String(url).toLowerCase();
|
|
67
119
|
|
|
68
|
-
var denyList =
|
|
69
|
-
for (var i=0;i<denyList.length;i++){
|
|
120
|
+
var denyList = cfg.deny || [];
|
|
121
|
+
for (var i = 0; i < denyList.length; i++) {
|
|
122
|
+
var r = denyList[i];
|
|
123
|
+
if (r && u.includes(String(r).toLowerCase())) return true;
|
|
124
|
+
}
|
|
70
125
|
|
|
71
|
-
try{
|
|
126
|
+
try {
|
|
72
127
|
var p = new URL(url, location.href).pathname.toLowerCase();
|
|
73
|
-
for (var j=0;j<
|
|
74
|
-
var ap=
|
|
75
|
-
if (p===ap || p.startsWith(ap+"/")) return true;
|
|
128
|
+
for (var j = 0; j < AUTO_PATH_PREFIX.length; j++) {
|
|
129
|
+
var ap = AUTO_PATH_PREFIX[j];
|
|
130
|
+
if (p === ap || p.startsWith(ap + "/")) return true;
|
|
76
131
|
}
|
|
77
|
-
}catch(e){}
|
|
132
|
+
} catch (e) { }
|
|
78
133
|
|
|
79
|
-
for (var k=0;k<AUTO_KEYWORDS.length;k++)
|
|
134
|
+
for (var k = 0; k < AUTO_KEYWORDS.length; k++) if (u.includes(AUTO_KEYWORDS[k])) return true;
|
|
80
135
|
|
|
81
|
-
try{
|
|
136
|
+
try {
|
|
82
137
|
var uu = new URL(url, location.href);
|
|
83
138
|
var sp = uu.searchParams;
|
|
84
|
-
for (var q=0;q<AUTO_PARAMS.length;q++)
|
|
85
|
-
}catch(e){}
|
|
139
|
+
for (var q = 0; q < AUTO_PARAMS.length; q++) if (sp.has(AUTO_PARAMS[q])) return true;
|
|
140
|
+
} catch (e) { }
|
|
86
141
|
|
|
87
142
|
return false;
|
|
88
143
|
}
|
|
89
144
|
|
|
90
|
-
function Stats(){
|
|
91
|
-
this.startedAt=Date.now();
|
|
92
|
-
this.seen
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
145
|
+
function Stats() {
|
|
146
|
+
this.startedAt = Date.now();
|
|
147
|
+
this.seen = 0;
|
|
148
|
+
this.denied = 0;
|
|
149
|
+
this.enqueued = 0;
|
|
150
|
+
this.prefetched = 0;
|
|
151
|
+
this.prerendered = 0;
|
|
152
|
+
this.clicked = 0;
|
|
153
|
+
this.hit = 0;
|
|
154
|
+
this._loaded = new Set();
|
|
96
155
|
}
|
|
97
|
-
Stats.prototype.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
156
|
+
Stats.prototype.isLoaded = function (url) { return this._loaded.has(url); };
|
|
157
|
+
Stats.prototype.markLoaded = function (url, type) {
|
|
158
|
+
if (this._loaded.has(url)) return;
|
|
159
|
+
this._loaded.add(url);
|
|
160
|
+
if (type === "prerender") this.prerendered++; else this.prefetched++;
|
|
102
161
|
};
|
|
103
|
-
Stats.prototype.
|
|
104
|
-
Stats.prototype
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
162
|
+
Stats.prototype.markClick = function (url) { this.clicked++; if (this._loaded.has(url)) this.hit++; };
|
|
163
|
+
Object.defineProperty(Stats.prototype, "hitRate", { get: function () { var d = this.prefetched + this.prerendered; return d ? (this.hit / d) : 0; } });
|
|
164
|
+
Stats.prototype.snapshot = function () {
|
|
165
|
+
return {
|
|
166
|
+
version: VERSION,
|
|
167
|
+
uptimeSec: Math.round((Date.now() - this.startedAt) / 1000),
|
|
168
|
+
seen: this.seen,
|
|
169
|
+
denied: this.denied,
|
|
170
|
+
enqueued: this.enqueued,
|
|
171
|
+
prefetched: this.prefetched,
|
|
172
|
+
prerendered: this.prerendered,
|
|
173
|
+
clicked: this.clicked,
|
|
174
|
+
hit: this.hit,
|
|
175
|
+
hitRate: Number(this.hitRate.toFixed(3))
|
|
176
|
+
};
|
|
108
177
|
};
|
|
109
178
|
|
|
110
|
-
function createScheduler(
|
|
111
|
-
var
|
|
112
|
-
var
|
|
113
|
-
var
|
|
114
|
-
var usedPrefetch=
|
|
179
|
+
function createScheduler(cfg, stats) {
|
|
180
|
+
var q = [];
|
|
181
|
+
var qSet = new Set();
|
|
182
|
+
var inFlight = new Set();
|
|
183
|
+
var usedPrefetch = 0;
|
|
184
|
+
var usedPrerender = 0;
|
|
185
|
+
var prerenderSupported = supportsPrerender();
|
|
115
186
|
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
if (String(ci.effectiveType).includes("
|
|
125
|
-
if (
|
|
126
|
-
if (typeof ci.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
187
|
+
function budgets() {
|
|
188
|
+
var mp = cfg.maxPrefetch;
|
|
189
|
+
var mr = cfg.maxPrerender;
|
|
190
|
+
|
|
191
|
+
if (!cfg.enablePrerender || !prerenderSupported) mr = 0;
|
|
192
|
+
|
|
193
|
+
var ci = getConn();
|
|
194
|
+
if (ci.saveData) { mr = 0; mp = Math.min(mp, 2); }
|
|
195
|
+
if (String(ci.effectiveType).includes("2g")) { mr = 0; mp = Math.min(mp, 2); }
|
|
196
|
+
if (String(ci.effectiveType).includes("3g")) { mr = 0; mp = Math.min(mp, 4); }
|
|
197
|
+
if (typeof ci.rtt === "number" && ci.rtt > 350) { mr = 0; mp = Math.min(mp, 3); }
|
|
198
|
+
if (typeof ci.downlink === "number" && ci.downlink < 1.2) { mr = 0; mp = Math.min(mp, 3); }
|
|
199
|
+
|
|
200
|
+
mp = clamp(mp, 0, 12);
|
|
201
|
+
mr = clamp(mr, 0, 3);
|
|
202
|
+
|
|
203
|
+
return { mp: mp, mr: mr };
|
|
130
204
|
}
|
|
131
205
|
|
|
132
|
-
function enqueue(rawUrl, score){
|
|
133
|
-
var url=normalizeUrl(rawUrl);
|
|
134
|
-
if(!url) return;
|
|
206
|
+
function enqueue(rawUrl, score, signal) {
|
|
207
|
+
var url = normalizeUrl(rawUrl);
|
|
208
|
+
if (!url) return;
|
|
209
|
+
if (stats.isLoaded(url) || inFlight.has(url) || qSet.has(url)) return;
|
|
210
|
+
|
|
211
|
+
if (q.length >= cfg.maxQueue) {
|
|
212
|
+
var minIdx = 0;
|
|
213
|
+
for (var i = 1; i < q.length; i++) if (q[i].score < q[minIdx].score) minIdx = i;
|
|
214
|
+
var dropped = q.splice(minIdx, 1)[0];
|
|
215
|
+
if (dropped) qSet.delete(dropped.url);
|
|
216
|
+
}
|
|
217
|
+
|
|
135
218
|
stats.enqueued++;
|
|
136
|
-
|
|
137
|
-
|
|
219
|
+
q.push({ url: url, score: score, signal: signal, t: Date.now() });
|
|
220
|
+
qSet.add(url);
|
|
138
221
|
pump();
|
|
139
222
|
}
|
|
140
223
|
|
|
141
|
-
function pump(){
|
|
142
|
-
var b=
|
|
143
|
-
while(queue.length){
|
|
144
|
-
var n=queue.shift();
|
|
145
|
-
var url=n.url, score=n.score;
|
|
146
|
-
if(loaded.has(url) || stats.isLoaded(url)) continue;
|
|
147
|
-
if(inFlight.has(url)) continue;
|
|
224
|
+
function pump() {
|
|
225
|
+
var b = budgets();
|
|
148
226
|
|
|
149
|
-
|
|
150
|
-
|
|
227
|
+
function takeBest() {
|
|
228
|
+
if (!q.length) return null;
|
|
229
|
+
var bestIdx = 0;
|
|
230
|
+
for (var i = 1; i < q.length; i++) if (q[i].score > q[bestIdx].score) bestIdx = i;
|
|
231
|
+
var item = q.splice(bestIdx, 1)[0];
|
|
232
|
+
if (item) qSet.delete(item.url);
|
|
233
|
+
return item;
|
|
234
|
+
}
|
|
151
235
|
|
|
152
|
-
|
|
236
|
+
while (q.length) {
|
|
237
|
+
var item = takeBest();
|
|
238
|
+
if (!item) break;
|
|
153
239
|
|
|
154
|
-
var
|
|
155
|
-
|
|
240
|
+
var url = item.url;
|
|
241
|
+
var score = item.score;
|
|
242
|
+
var signal = item.signal;
|
|
243
|
+
|
|
244
|
+
if (stats.isLoaded(url) || inFlight.has(url)) continue;
|
|
245
|
+
|
|
246
|
+
var canPrefetch = usedPrefetch < b.mp;
|
|
247
|
+
var canPrerender = usedPrerender < b.mr;
|
|
248
|
+
|
|
249
|
+
// v1.0.2: prerender only on HIGH confidence signals (not hover)
|
|
250
|
+
var highIntent = (signal === "mousedown" || signal === "touchstart");
|
|
251
|
+
var doPrerender = canPrerender && highIntent && score >= 110;
|
|
252
|
+
|
|
253
|
+
if (!doPrerender && !canPrefetch) break;
|
|
254
|
+
|
|
255
|
+
var type = doPrerender ? "prerender" : "prefetch";
|
|
256
|
+
if (type === "prerender") usedPrerender++; else usedPrefetch++;
|
|
156
257
|
|
|
157
258
|
inFlight.add(url);
|
|
158
|
-
var link=document.createElement("link");
|
|
159
|
-
link.rel=type;
|
|
160
|
-
link.href=url;
|
|
161
|
-
link.as="document";
|
|
162
|
-
link.fetchPriority =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
inFlight.delete(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
link.onerror=function(){ inFlight.delete(url); };
|
|
259
|
+
var link = document.createElement("link");
|
|
260
|
+
link.rel = type;
|
|
261
|
+
link.href = url;
|
|
262
|
+
link.as = "document";
|
|
263
|
+
link.fetchPriority = doPrerender ? "high" : (score >= 90 ? "auto" : "low");
|
|
264
|
+
|
|
265
|
+
(function (u, t) {
|
|
266
|
+
link.onload = function () { inFlight.delete(u); stats.markLoaded(u, t); };
|
|
267
|
+
link.onerror = function () { inFlight.delete(u); };
|
|
268
|
+
})(url, type);
|
|
269
|
+
|
|
170
270
|
document.head.appendChild(link);
|
|
171
271
|
}
|
|
172
272
|
}
|
|
173
273
|
|
|
174
|
-
window.addEventListener("pageshow", function(){ usedPrefetch=0; usedPrerender=0; });
|
|
274
|
+
window.addEventListener("pageshow", function () { usedPrefetch = 0; usedPrerender = 0; });
|
|
175
275
|
|
|
176
276
|
return { enqueue: enqueue };
|
|
177
277
|
}
|
|
178
278
|
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
var
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function maybeOverlay(c, stats){
|
|
195
|
-
if(!c.debug || !c.debugOverlay) return;
|
|
196
|
-
var el=document.createElement("div");
|
|
197
|
-
el.style.cssText="position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px";
|
|
198
|
-
function render(){
|
|
199
|
-
var s=stats.snapshot();
|
|
200
|
-
el.innerHTML="<div style='font-weight:700;margin-bottom:6px'>HyperInstant "+s.version+"</div>"+
|
|
201
|
-
"<div>Loaded: <b>"+(s.prefetched+s.prerendered)+"</b> (P:"+s.prefetched+" R:"+s.prerendered+")</div>"+
|
|
202
|
-
"<div>Hit-rate: <b>"+Math.round(s.hitRate*100)+"%</b></div>"+
|
|
203
|
-
"<div>HoverDelay: <b>"+c.hoverDelay+"ms</b></div>"+
|
|
204
|
-
"<div style='opacity:.8;margin-top:6px'>Clicks: "+s.clicked+" • Seen: "+s.seen+"</div>";
|
|
279
|
+
function maybeOverlay(cfg, stats) {
|
|
280
|
+
if (!cfg.debug || !cfg.debugOverlay) return;
|
|
281
|
+
var el = document.createElement("div");
|
|
282
|
+
el.style.cssText = "position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px";
|
|
283
|
+
function render() {
|
|
284
|
+
var s = stats.snapshot();
|
|
285
|
+
el.innerHTML =
|
|
286
|
+
"<div style='font-weight:700;margin-bottom:6px'>HyperInstant " + s.version + "</div>" +
|
|
287
|
+
"<div>Loaded: <b>" + (s.prefetched + s.prerendered) + "</b> (P:" + s.prefetched + " R:" + s.prerendered + ")</div>" +
|
|
288
|
+
"<div>Hit-rate: <b>" + Math.round(s.hitRate * 100) + "%</b></div>" +
|
|
289
|
+
"<div>HoverDelay: <b>" + cfg.hoverDelay + "ms</b></div>" +
|
|
290
|
+
"<div style='opacity:.8;margin-top:6px'>Clicks: " + s.clicked + " • Seen: " + s.seen + "</div>";
|
|
205
291
|
}
|
|
206
292
|
render();
|
|
207
|
-
document.addEventListener("DOMContentLoaded", function(){ document.body.appendChild(el); });
|
|
293
|
+
document.addEventListener("DOMContentLoaded", function () { document.body.appendChild(el); });
|
|
208
294
|
setInterval(render, 1200);
|
|
209
295
|
}
|
|
210
296
|
|
|
211
|
-
function eligibleLink(target){
|
|
297
|
+
function eligibleLink(target) {
|
|
212
298
|
var a = target && target.closest ? target.closest("a[href]") : null;
|
|
213
|
-
if(!a || !a.href) return null;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if(
|
|
217
|
-
if(a.getAttribute("data-
|
|
218
|
-
if(a.
|
|
299
|
+
if (!a || !a.href) return null;
|
|
300
|
+
|
|
301
|
+
var rel = String(a.getAttribute("rel") || "").toLowerCase();
|
|
302
|
+
if (rel.includes("nofollow")) return null;
|
|
303
|
+
if (a.getAttribute("data-no-instant") !== null) return null;
|
|
304
|
+
if (a.getAttribute("data-hyperinstant") === "off") return null;
|
|
305
|
+
if (a.target && a.target !== "_self") return null;
|
|
306
|
+
|
|
219
307
|
return a;
|
|
220
308
|
}
|
|
221
309
|
|
|
222
|
-
function score(signal, a){
|
|
223
|
-
var s=60;
|
|
224
|
-
if(signal==="mousedown") s=
|
|
225
|
-
else if(signal==="touchstart") s=
|
|
226
|
-
else if(signal==="focusin") s=
|
|
227
|
-
else if(signal==="mouseover") s=
|
|
228
|
-
|
|
229
|
-
if(inNav(a)) s+=8;
|
|
230
|
-
if(String(a.getAttribute("data-hyperinstant-priority")||"").toLowerCase()==="high") s+=12;
|
|
231
|
-
return Math.min(
|
|
310
|
+
function score(signal, a) {
|
|
311
|
+
var s = 60;
|
|
312
|
+
if (signal === "mousedown") s = 120;
|
|
313
|
+
else if (signal === "touchstart") s = 118;
|
|
314
|
+
else if (signal === "focusin") s = 98;
|
|
315
|
+
else if (signal === "mouseover") s = 82;
|
|
316
|
+
|
|
317
|
+
if (inNav(a)) s += 8;
|
|
318
|
+
if (String(a.getAttribute("data-hyperinstant-priority") || "").toLowerCase() === "high") s += 12;
|
|
319
|
+
return Math.min(130, s);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function createTuner(cfg, stats) {
|
|
323
|
+
var intentCount = 0;
|
|
324
|
+
function maybeTune() {
|
|
325
|
+
var total = stats.prefetched + stats.prerendered;
|
|
326
|
+
if (total < 16) return;
|
|
327
|
+
var hr = stats.hitRate;
|
|
328
|
+
|
|
329
|
+
if (hr >= 0.40) cfg.hoverDelay = clamp(cfg.hoverDelay - 8, 20, 120);
|
|
330
|
+
else if (hr <= 0.12) cfg.hoverDelay = clamp(cfg.hoverDelay + 10, 25, 180);
|
|
331
|
+
}
|
|
332
|
+
return { onEnqueue: function () { intentCount++; if (cfg.autoTune && intentCount % 25 === 0) maybeTune(); }, onClick: function () { } };
|
|
232
333
|
}
|
|
233
334
|
|
|
234
|
-
function
|
|
235
|
-
var hoverTimer=null;
|
|
335
|
+
function start(cfg, scheduler, stats, tuner) {
|
|
336
|
+
var hoverTimer = null;
|
|
337
|
+
|
|
338
|
+
function onIntent(e) {
|
|
339
|
+
var a = eligibleLink(e.target);
|
|
340
|
+
if (!a) return;
|
|
236
341
|
|
|
237
|
-
|
|
238
|
-
var a=eligibleLink(e.target);
|
|
239
|
-
if(!a) return;
|
|
240
|
-
var url=a.href;
|
|
342
|
+
var url = a.href;
|
|
241
343
|
stats.seen++;
|
|
242
|
-
if(shouldDeny(url,c)){ stats.denied++; return; }
|
|
243
344
|
|
|
244
|
-
|
|
245
|
-
|
|
345
|
+
if (shouldDeny(url, cfg)) { stats.denied++; return; }
|
|
346
|
+
|
|
347
|
+
var sig = e.type;
|
|
348
|
+
var sc = score(sig, a);
|
|
349
|
+
|
|
246
350
|
clearTimeout(hoverTimer);
|
|
247
|
-
var delay = (sig==="mouseover") ?
|
|
248
|
-
|
|
249
|
-
|
|
351
|
+
var delay = (sig === "mouseover") ? cfg.hoverDelay : 0;
|
|
352
|
+
|
|
353
|
+
hoverTimer = setTimeout(function () {
|
|
354
|
+
scheduler.enqueue(url, sc, sig);
|
|
250
355
|
tuner.onEnqueue();
|
|
251
356
|
}, delay);
|
|
252
357
|
}
|
|
253
358
|
|
|
254
|
-
function onClick(e){
|
|
255
|
-
var a=eligibleLink(e.target);
|
|
256
|
-
if(!a) return;
|
|
257
|
-
stats.markClick(a.href);
|
|
359
|
+
function onClick(e) {
|
|
360
|
+
var a = eligibleLink(e.target);
|
|
361
|
+
if (!a) return;
|
|
362
|
+
stats.markClick(normalizeUrl(a.href));
|
|
258
363
|
tuner.onClick();
|
|
259
364
|
}
|
|
260
365
|
|
|
261
|
-
(
|
|
262
|
-
document.addEventListener(evt, onIntent, {passive:true, capture:true});
|
|
366
|
+
(cfg.events || ["mouseover", "touchstart", "mousedown", "focusin"]).forEach(function (evt) {
|
|
367
|
+
document.addEventListener(evt, onIntent, { passive: true, capture: true });
|
|
263
368
|
});
|
|
264
|
-
document.addEventListener("click", onClick, {passive:true, capture:true});
|
|
265
|
-
|
|
266
|
-
if (String(c.observe||"intent").toLowerCase().includes("viewport") && "IntersectionObserver" in window) {
|
|
267
|
-
var io=new IntersectionObserver(function(entries){
|
|
268
|
-
entries.forEach(function(ent){
|
|
269
|
-
if(!ent.isIntersecting) return;
|
|
270
|
-
var a=eligibleLink(ent.target);
|
|
271
|
-
if(!a) return;
|
|
272
|
-
var url=a.href;
|
|
273
|
-
if(shouldDeny(url,c)) return;
|
|
274
|
-
scheduler.enqueue(url, score("viewport", a));
|
|
275
|
-
io.unobserve(ent.target);
|
|
276
|
-
});
|
|
277
|
-
}, {root:null, rootMargin:"200px 0px", threshold:0.01});
|
|
278
|
-
|
|
279
|
-
function scan(){
|
|
280
|
-
document.querySelectorAll("a[data-hyperinstant-viewport='on'][href]").forEach(function(a){ io.observe(a); });
|
|
281
|
-
}
|
|
282
|
-
scan();
|
|
283
|
-
var mo=new MutationObserver(function(){ scan(); });
|
|
284
|
-
mo.observe(document.documentElement, {childList:true, subtree:true});
|
|
285
|
-
}
|
|
369
|
+
document.addEventListener("click", onClick, { passive: true, capture: true });
|
|
286
370
|
}
|
|
287
371
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
var adapter = detectAdapter(c.adapter);
|
|
291
|
-
c = adapter(c);
|
|
372
|
+
var cfg = applyModeDefaults(userCfg);
|
|
373
|
+
cfg = detectAdapter(cfg.adapter)(cfg);
|
|
292
374
|
|
|
293
375
|
var stats = new Stats();
|
|
294
|
-
var tuner = createTuner(
|
|
295
|
-
var scheduler = createScheduler(
|
|
376
|
+
var tuner = createTuner(cfg, stats);
|
|
377
|
+
var scheduler = createScheduler(cfg, stats);
|
|
378
|
+
|
|
379
|
+
window.HyperInstant = window.HyperInstant || {};
|
|
380
|
+
window.HyperInstant.version = VERSION;
|
|
381
|
+
|
|
382
|
+
if (cfg.debug) {
|
|
383
|
+
window.HyperInstantStats = stats;
|
|
384
|
+
console.log("[HyperInstant] v" + VERSION + " loaded", { mode: cfg.mode, adapter: cfg.adapter });
|
|
385
|
+
}
|
|
296
386
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
maybeOverlay(c, stats);
|
|
387
|
+
start(cfg, scheduler, stats, tuner);
|
|
388
|
+
maybeOverlay(cfg, stats);
|
|
300
389
|
})();
|
package/dist/hyperinstant.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
/*! HyperInstant v1.0.1 | MIT | Putia Web */ (function(){if (window.__HYPERINSTANT_LOADED__) return; window.__HYPERINSTANT_LOADED__ = true; var cfg = window.HyperInstant ||{}; function clamp(n,a,b){return Math.max(a, Math.min(b,n));} function isSameOrigin(url){try{return new URL(url, location.href).origin === location.origin;}catch(e){return false;}} function normalizeUrl(url){try{var u=new URL(url, location.href); u.hash=""; return u.toString();}catch(e){return "";}} function getConn(){var c=navigator.connection||navigator.mozConnection||navigator.webkitConnection; return{saveData:!!(c&&c.saveData), effectiveType:(c&&c.effectiveType)?String(c.effectiveType):"unknown", downlink:(c&&typeof c.downlink==="number")?c.downlink:null, rtt:(c&&typeof c.rtt==="number")?c.rtt:null};} function supportsPrerender(){try{return "relList" in HTMLLinkElement.prototype && HTMLLinkElement.prototype.relList.supports && HTMLLinkElement.prototype.relList.supports("prerender");}catch(e){return false;}} function inNav(a){try{return !!a.closest("nav, header, [role='navigation'], .header, .site-header");}catch(e){return false;}} function applyModeDefaults(c){var mode = String(c.mode||"safe").toLowerCase(); var out = Object.assign({}, c); if (mode==="aggressive"){out.maxPrefetch = out.maxPrefetch ?? 10; out.maxPrerender = out.maxPrerender ?? 2; out.observe = out.observe ?? "intent+viewport";} else if (mode==="balanced"){out.maxPrefetch = out.maxPrefetch ?? 6; out.maxPrerender = out.maxPrerender ?? 1; out.observe = out.observe ?? "intent";} else{out.maxPrefetch = out.maxPrefetch ?? 4; out.maxPrerender = out.maxPrerender ?? 0; out.observe = out.observe ?? "intent";} out.hoverDelay = out.hoverDelay ?? 65; out.debug = !!out.debug; out.debugOverlay = !!out.debugOverlay; out.budget = out.budget ?? "auto"; out.events = out.events ?? ["mouseover","touchstart","mousedown","focusin"]; out.deny = out.deny ?? []; return out;} function hasMeta(name){return !!document.querySelector('meta[name="'+name+'"]');} function shopifyAdapter(c){var deny=new Set([].concat(c.deny||[])); ["/cart","/checkout","/account","/account/login","/account/register","/account/logout","/search","cart/add","cart/change","cart/update","?variant=","variant=","sections="].forEach(function(x){deny.add(x);}); return Object.assign({}, c,{deny:Array.from(deny)});} function wooAdapter(c){var deny=new Set([].concat(c.deny||[])); ["/cart","/checkout","/my-account","add-to-cart","wc-ajax","remove_item","update_cart","apply_coupon"].forEach(function(x){deny.add(x);}); return Object.assign({}, c,{deny:Array.from(deny)});} function nopAdapter(c){var deny=new Set([].concat(c.deny||[])); ["/shoppingcart","/checkout","/login","/register","/logout","/customer","addproducttocart","updateshoppingcart","updatecart","deleteitem","/wishlist","/compareproducts","/search","?q="].forEach(function(x){deny.add(x);}); return Object.assign({}, c,{deny:Array.from(deny)});} function detectAdapter(name){name = String(name||"auto").toLowerCase(); if (name==="shopify") return shopifyAdapter; if (name==="woocommerce") return wooAdapter; if (name==="nopcommerce") return nopAdapter; if (name==="generic") return function(x){return x;}; try{if (window.Shopify || hasMeta("shopify-digital-wallet") || hasMeta("shopify-checkout-api-token")) return shopifyAdapter;}catch(e){} try{if (window.wc_add_to_cart_params || window.wc_cart_fragments_params || (document.body&&String(document.body.className||"").includes("woocommerce"))) return wooAdapter;}catch(e){} try{if (window.nopCommerce || document.querySelector("a[href*='shoppingcart']") || document.querySelector("form[action*='addproducttocart']")) return nopAdapter;}catch(e){} return function(x){return x;};} var AUTO_KEYWORDS=["logout","logoff","signin","sign-in","login","register","account","checkout","cart","basket","order","orders","pay","payment","add-to-cart","addtocart","addproducttocart","remove","update","change","delete","wc-ajax","admin-ajax","ajax","sections=","variant=","token","returnurl","redirect"]; var AUTO_PATHS=["/checkout","/cart","/shoppingcart","/account","/my-account","/logout","/login","/register"]; var AUTO_PARAMS=["add","remove","update","change","delete","token","variant","sections","returnurl","redirect"]; function shouldDeny(url, c){if (!url) return true; if (!isSameOrigin(url)) return true; var u = String(url).toLowerCase(); var denyList = c.deny || []; for (var i=0;i<denyList.length;i++){var r=denyList[i]; if (r && u.includes(String(r).toLowerCase())) return true;} try{var p = new URL(url, location.href).pathname.toLowerCase(); for (var j=0;j<AUTO_PATHS.length;j++){var ap=AUTO_PATHS[j]; if (p===ap || p.startsWith(ap+"/")) return true;}}catch(e){} for (var k=0;k<AUTO_KEYWORDS.length;k++){if (u.includes(AUTO_KEYWORDS[k])) return true;} try{var uu = new URL(url, location.href); var sp = uu.searchParams; for (var q=0;q<AUTO_PARAMS.length;q++){if (sp.has(AUTO_PARAMS[q])) return true;}}catch(e){} return false;} function Stats(){this.startedAt=Date.now(); this.seen=0; this.denied=0; this.enqueued=0; this.prefetched=0; this.prerendered=0; this.clicked=0; this.hit=0; this._loaded=new Map();} Stats.prototype.markLoaded=function(url,type){if(!this._loaded.has(url)){this._loaded.set(url,{type:type,t:Date.now()}); if(type==="prerender") this.prerendered++; else this.prefetched++;}}; Stats.prototype.isLoaded=function(url){return this._loaded.has(url);}; Stats.prototype.markClick=function(url){this.clicked++; if(this._loaded.has(url)) this.hit++;}; Object.defineProperty(Stats.prototype,"hitRate",{get:function(){var d=this.prefetched+this.prerendered; return d? (this.hit/d):0;}}); Stats.prototype.snapshot=function(){return{version:"1.0.1", uptimeSec:Math.round((Date.now()-this.startedAt)/1000), seen:this.seen, denied:this.denied, enqueued:this.enqueued, prefetched:this.prefetched, prerendered:this.prerendered, clicked:this.clicked, hit:this.hit, hitRate:Number(this.hitRate.toFixed(3))};}; function createScheduler(c, stats){var queue=[]; var inFlight=new Set(); var loaded=new Set(); var usedPrefetch=0, usedPrerender=0; function computeBudgets(){if (c.budget && typeof c.budget==="object"){return{maxPrefetch: c.budget.maxPrefetch ?? c.maxPrefetch, maxPrerender: c.budget.maxPrerender ?? c.maxPrerender};} var ci=getConn(); var mp=c.maxPrefetch, mr=c.maxPrerender; if (ci.saveData){mr=0; mp=Math.min(mp,2);} if (String(ci.effectiveType).includes("2g")){mr=0; mp=Math.min(mp,2);} if (String(ci.effectiveType).includes("3g")){mr=Math.min(mr,0); mp=Math.min(mp,4);} if (typeof ci.rtt==="number" && ci.rtt>300){mr=0; mp=Math.min(mp,3);} if (typeof ci.downlink==="number" && ci.downlink<1.2){mr=0; mp=Math.min(mp,3);} mp=clamp(mp,0,12); mr=clamp(mr,0,3); if (!supportsPrerender()) mr=0; return{maxPrefetch: mp, maxPrerender: mr};} function enqueue(rawUrl, score){var url=normalizeUrl(rawUrl); if(!url) return; stats.enqueued++; queue.push({url:url,score:score,t:Date.now()}); queue.sort(function(a,b){return b.score-a.score;}); pump();} function pump(){var b=computeBudgets(); while(queue.length){var n=queue.shift(); var url=n.url, score=n.score; if(loaded.has(url) || stats.isLoaded(url)) continue; if(inFlight.has(url)) continue; var wantPrerender = b.maxPrerender>usedPrerender && score>=90; var wantPrefetch = b.maxPrefetch>usedPrefetch; if(!wantPrerender && !wantPrefetch) break; var type = wantPrerender ? "prerender" : "prefetch"; if(type==="prerender") usedPrerender++; else usedPrefetch++; inFlight.add(url); var link=document.createElement("link"); link.rel=type; link.href=url; link.as="document"; link.fetchPriority = (score>=90) ? "high" : (score>=70 ? "auto" : "low"); link.onload=function(){loaded.add(url); inFlight.delete(url); stats.markLoaded(url,type);}; link.onerror=function(){inFlight.delete(url);}; document.head.appendChild(link);}} window.addEventListener("pageshow", function(){usedPrefetch=0; usedPrerender=0;}); return{enqueue: enqueue};} function createTuner(c, stats){var intentCount=0; function maybeTune(){var total = stats.prefetched + stats.prerendered; if(total<12) return; var hr = stats.hitRate; if (hr>=0.35) c.hoverDelay = clamp((c.hoverDelay||65)-10, 25, 140); else if (hr<=0.15) c.hoverDelay = clamp((c.hoverDelay||65)+15, 25, 180);} return{onEnqueue:function(){intentCount++; if(intentCount%20===0) maybeTune();}, onClick:function(){}};} function maybeOverlay(c, stats){if(!c.debug || !c.debugOverlay) return; var el=document.createElement("div"); el.style.cssText="position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px"; function render(){var s=stats.snapshot(); el.innerHTML="<div style='font-weight:700;margin-bottom:6px'>HyperInstant "+s.version+"</div>"+ "<div>Loaded: <b>"+(s.prefetched+s.prerendered)+"</b> (P:"+s.prefetched+" R:"+s.prerendered+")</div>"+ "<div>Hit-rate: <b>"+Math.round(s.hitRate*100)+"%</b></div>"+ "<div>HoverDelay: <b>"+c.hoverDelay+"ms</b></div>"+ "<div style='opacity:.8;margin-top:6px'>Clicks: "+s.clicked+" • Seen: "+s.seen+"</div>";} render(); document.addEventListener("DOMContentLoaded", function(){document.body.appendChild(el);}); setInterval(render, 1200);} function eligibleLink(target){var a = target && target.closest ? target.closest("a[href]") : null; if(!a || !a.href) return null; var rel = String(a.getAttribute("rel")||"").toLowerCase(); if(rel.includes("nofollow")) return null; if(a.getAttribute("data-no-instant")!==null) return null; if(a.getAttribute("data-hyperinstant")==="off") return null; if(a.target && a.target!=="_self") return null; return a;} function score(signal, a){var s=60; if(signal==="mousedown") s=100; else if(signal==="touchstart") s=96; else if(signal==="focusin") s=86; else if(signal==="mouseover") s=72; else if(signal==="viewport") s=40; if(inNav(a)) s+=8; if(String(a.getAttribute("data-hyperinstant-priority")||"").toLowerCase()==="high") s+=12; return Math.min(110,s);} function startObservers(c, scheduler, stats, tuner){var hoverTimer=null; function onIntent(e){var a=eligibleLink(e.target); if(!a) return; var url=a.href; stats.seen++; if(shouldDeny(url,c)){stats.denied++; return;} var sig=e.type; var sc=score(sig,a); clearTimeout(hoverTimer); var delay = (sig==="mouseover") ? c.hoverDelay : 0; hoverTimer=setTimeout(function(){scheduler.enqueue(url, sc); tuner.onEnqueue();}, delay);} function onClick(e){var a=eligibleLink(e.target); if(!a) return; stats.markClick(a.href); tuner.onClick();} (c.events||["mouseover","touchstart","mousedown","focusin"]).forEach(function(evt){document.addEventListener(evt, onIntent,{passive:true, capture:true});}); document.addEventListener("click", onClick,{passive:true, capture:true}); if (String(c.observe||"intent").toLowerCase().includes("viewport") && "IntersectionObserver" in window){var io=new IntersectionObserver(function(entries){entries.forEach(function(ent){if(!ent.isIntersecting) return; var a=eligibleLink(ent.target); if(!a) return; var url=a.href; if(shouldDeny(url,c)) return; scheduler.enqueue(url, score("viewport", a)); io.unobserve(ent.target);});},{root:null, rootMargin:"200px 0px", threshold:0.01}); function scan(){document.querySelectorAll("a[data-hyperinstant-viewport='on'][href]").forEach(function(a){io.observe(a);});} scan(); var mo=new MutationObserver(function(){scan();}); mo.observe(document.documentElement,{childList:true, subtree:true});}} // init var c = applyModeDefaults(cfg); var adapter = detectAdapter(c.adapter); c = adapter(c); var stats = new Stats(); var tuner = createTuner(c, stats); var scheduler = createScheduler(c, stats); if (c.debug){window.HyperInstantStats = stats; console.log("[HyperInstant] v1.0.1 loaded", c);} startObservers(c, scheduler, stats, tuner); maybeOverlay(c, stats);})();
|
|
1
|
+
/*! HyperInstant v1.0.2 | MIT | Putia Web */ (function (){if (window.__HYPERINSTANT_LOADED__) return; window.__HYPERINSTANT_LOADED__ = true; var userCfg = window.HyperInstant ||{}; var VERSION = "1.0.2"; function clamp(n, a, b){return Math.max(a, Math.min(b, n));} function isSameOrigin(url){try{return new URL(url, location.href).origin === location.origin;} catch (e){return false;}} function normalizeUrl(url){try{var u = new URL(url, location.href); u.hash = ""; return u.toString();} catch (e){return "";}} function supportsPrerender(){try{return "relList" in HTMLLinkElement.prototype && HTMLLinkElement.prototype.relList && HTMLLinkElement.prototype.relList.supports && HTMLLinkElement.prototype.relList.supports("prerender");} catch (e){return false;}} function getConn(){var c = navigator.connection || navigator.mozConnection || navigator.webkitConnection; return{saveData: !!(c && c.saveData), effectiveType: (c && c.effectiveType) ? String(c.effectiveType) : "unknown", downlink: (c && typeof c.downlink === "number") ? c.downlink : null, rtt: (c && typeof c.rtt === "number") ? c.rtt : null};} function inNav(a){try{return !!a.closest("nav, header, [role='navigation'], .header, .site-header");} catch (e){return false;}} function applyModeDefaults(cfg){var mode = String(cfg.mode || "safe").toLowerCase(); var out = Object.assign({}, cfg); // v1.0.2: prerender is intentionally conservative to avoid slowing pages if (mode === "aggressive"){out.maxPrefetch = out.maxPrefetch ?? 10; out.maxPrerender = out.maxPrerender ?? 1; out.hoverDelay = out.hoverDelay ?? 35;} else if (mode === "balanced"){out.maxPrefetch = out.maxPrefetch ?? 6; out.maxPrerender = out.maxPrerender ?? 0; out.hoverDelay = out.hoverDelay ?? 65;} else{out.maxPrefetch = out.maxPrefetch ?? 4; out.maxPrerender = out.maxPrerender ?? 0; out.hoverDelay = out.hoverDelay ?? 75;} out.adapter = out.adapter ?? "auto"; out.observe = out.observe ?? "intent"; out.events = out.events ?? ["mouseover", "touchstart", "mousedown", "focusin"]; out.deny = out.deny ?? []; out.debug = !!out.debug; out.debugOverlay = !!out.debugOverlay; // new in 1.0.2 out.enablePrerender = (out.enablePrerender === undefined) ? true : !!out.enablePrerender; out.autoTune = !!out.autoTune; // OFF by default out.maxQueue = clamp(out.maxQueue ?? 60, 10, 200); return out;} function hasMeta(name){return !!document.querySelector('meta[name="' + name + '"]');} function shopifyAdapter(cfg){var deny = new Set([].concat(cfg.deny || [])); ["/cart", "/checkout", "/account", "/account/login", "/account/register", "/account/logout", "/search", "cart/add", "cart/change", "cart/update", "variant=", "sections="].forEach(function (x){deny.add(x);}); return Object.assign({}, cfg,{deny: Array.from(deny)});} function wooAdapter(cfg){var deny = new Set([].concat(cfg.deny || [])); ["/cart", "/checkout", "/my-account", "add-to-cart", "wc-ajax", "remove_item", "update_cart", "apply_coupon"].forEach(function (x){deny.add(x);}); return Object.assign({}, cfg,{deny: Array.from(deny)});} function nopAdapter(cfg){var deny = new Set([].concat(cfg.deny || [])); ["/shoppingcart", "/checkout", "/login", "/register", "/logout", "/customer", "/wishlist", "/compareproducts", "/search", "?q=", "addproducttocart", "updateshoppingcart", "updatecart", "deleteitem", "returnurl=", "token=", "ajax", "flyout"].forEach(function (x){deny.add(x);}); return Object.assign({}, cfg,{deny: Array.from(deny)});} function detectAdapter(name){name = String(name || "auto").toLowerCase(); if (name === "shopify") return shopifyAdapter; if (name === "woocommerce") return wooAdapter; if (name === "nopcommerce") return nopAdapter; if (name === "generic") return function (c){return c;}; try{if (window.Shopify || hasMeta("shopify-digital-wallet") || hasMeta("shopify-checkout-api-token")) return shopifyAdapter;} catch (e){} try{if (window.wc_add_to_cart_params || window.wc_cart_fragments_params || (document.body && String(document.body.className || "").includes("woocommerce"))) return wooAdapter;} catch (e){} try{if (window.nopCommerce || document.querySelector("a[href*='shoppingcart']") || document.querySelector("form[action*='addproducttocart']")) return nopAdapter;} catch (e){} return function (c){return c;};} // Safer auto-deny (avoid broad keywords like "order" that can hit benign pages) var AUTO_PATH_PREFIX = ["/checkout", "/cart", "/shoppingcart", "/account", "/my-account", "/logout", "/login", "/register"]; var AUTO_PARAMS = ["add", "remove", "update", "change", "delete", "token", "variant", "sections", "returnurl", "redirect"]; var AUTO_KEYWORDS = ["add-to-cart", "addtocart", "addproducttocart", "wc-ajax", "admin-ajax", "sections=", "variant="]; function shouldDeny(url, cfg){if (!url) return true; if (!isSameOrigin(url)) return true; var u = String(url).toLowerCase(); var denyList = cfg.deny || []; for (var i = 0; i < denyList.length; i++){var r = denyList[i]; if (r && u.includes(String(r).toLowerCase())) return true;} try{var p = new URL(url, location.href).pathname.toLowerCase(); for (var j = 0; j < AUTO_PATH_PREFIX.length; j++){var ap = AUTO_PATH_PREFIX[j]; if (p === ap || p.startsWith(ap + "/")) return true;}} catch (e){} for (var k = 0; k < AUTO_KEYWORDS.length; k++) if (u.includes(AUTO_KEYWORDS[k])) return true; try{var uu = new URL(url, location.href); var sp = uu.searchParams; for (var q = 0; q < AUTO_PARAMS.length; q++) if (sp.has(AUTO_PARAMS[q])) return true;} catch (e){} return false;} function Stats(){this.startedAt = Date.now(); this.seen = 0; this.denied = 0; this.enqueued = 0; this.prefetched = 0; this.prerendered = 0; this.clicked = 0; this.hit = 0; this._loaded = new Set();} Stats.prototype.isLoaded = function (url){return this._loaded.has(url);}; Stats.prototype.markLoaded = function (url, type){if (this._loaded.has(url)) return; this._loaded.add(url); if (type === "prerender") this.prerendered++; else this.prefetched++;}; Stats.prototype.markClick = function (url){this.clicked++; if (this._loaded.has(url)) this.hit++;}; Object.defineProperty(Stats.prototype, "hitRate",{get: function (){var d = this.prefetched + this.prerendered; return d ? (this.hit / d) : 0;}}); Stats.prototype.snapshot = function (){return{version: VERSION, uptimeSec: Math.round((Date.now() - this.startedAt) / 1000), seen: this.seen, denied: this.denied, enqueued: this.enqueued, prefetched: this.prefetched, prerendered: this.prerendered, clicked: this.clicked, hit: this.hit, hitRate: Number(this.hitRate.toFixed(3))};}; function createScheduler(cfg, stats){var q = []; var qSet = new Set(); var inFlight = new Set(); var usedPrefetch = 0; var usedPrerender = 0; var prerenderSupported = supportsPrerender(); function budgets(){var mp = cfg.maxPrefetch; var mr = cfg.maxPrerender; if (!cfg.enablePrerender || !prerenderSupported) mr = 0; var ci = getConn(); if (ci.saveData){mr = 0; mp = Math.min(mp, 2);} if (String(ci.effectiveType).includes("2g")){mr = 0; mp = Math.min(mp, 2);} if (String(ci.effectiveType).includes("3g")){mr = 0; mp = Math.min(mp, 4);} if (typeof ci.rtt === "number" && ci.rtt > 350){mr = 0; mp = Math.min(mp, 3);} if (typeof ci.downlink === "number" && ci.downlink < 1.2){mr = 0; mp = Math.min(mp, 3);} mp = clamp(mp, 0, 12); mr = clamp(mr, 0, 3); return{mp: mp, mr: mr};} function enqueue(rawUrl, score, signal){var url = normalizeUrl(rawUrl); if (!url) return; if (stats.isLoaded(url) || inFlight.has(url) || qSet.has(url)) return; if (q.length >= cfg.maxQueue){var minIdx = 0; for (var i = 1; i < q.length; i++) if (q[i].score < q[minIdx].score) minIdx = i; var dropped = q.splice(minIdx, 1)[0]; if (dropped) qSet.delete(dropped.url);} stats.enqueued++; q.push({url: url, score: score, signal: signal, t: Date.now()}); qSet.add(url); pump();} function pump(){var b = budgets(); function takeBest(){if (!q.length) return null; var bestIdx = 0; for (var i = 1; i < q.length; i++) if (q[i].score > q[bestIdx].score) bestIdx = i; var item = q.splice(bestIdx, 1)[0]; if (item) qSet.delete(item.url); return item;} while (q.length){var item = takeBest(); if (!item) break; var url = item.url; var score = item.score; var signal = item.signal; if (stats.isLoaded(url) || inFlight.has(url)) continue; var canPrefetch = usedPrefetch < b.mp; var canPrerender = usedPrerender < b.mr; // v1.0.2: prerender only on HIGH confidence signals (not hover) var highIntent = (signal === "mousedown" || signal === "touchstart"); var doPrerender = canPrerender && highIntent && score >= 110; if (!doPrerender && !canPrefetch) break; var type = doPrerender ? "prerender" : "prefetch"; if (type === "prerender") usedPrerender++; else usedPrefetch++; inFlight.add(url); var link = document.createElement("link"); link.rel = type; link.href = url; link.as = "document"; link.fetchPriority = doPrerender ? "high" : (score >= 90 ? "auto" : "low"); (function (u, t){link.onload = function (){inFlight.delete(u); stats.markLoaded(u, t);}; link.onerror = function (){inFlight.delete(u);};})(url, type); document.head.appendChild(link);}} window.addEventListener("pageshow", function (){usedPrefetch = 0; usedPrerender = 0;}); return{enqueue: enqueue};} function maybeOverlay(cfg, stats){if (!cfg.debug || !cfg.debugOverlay) return; var el = document.createElement("div"); el.style.cssText = "position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px"; function render(){var s = stats.snapshot(); el.innerHTML = "<div style='font-weight:700;margin-bottom:6px'>HyperInstant " + s.version + "</div>" + "<div>Loaded: <b>" + (s.prefetched + s.prerendered) + "</b> (P:" + s.prefetched + " R:" + s.prerendered + ")</div>" + "<div>Hit-rate: <b>" + Math.round(s.hitRate * 100) + "%</b></div>" + "<div>HoverDelay: <b>" + cfg.hoverDelay + "ms</b></div>" + "<div style='opacity:.8;margin-top:6px'>Clicks: " + s.clicked + " • Seen: " + s.seen + "</div>";} render(); document.addEventListener("DOMContentLoaded", function (){document.body.appendChild(el);}); setInterval(render, 1200);} function eligibleLink(target){var a = target && target.closest ? target.closest("a[href]") : null; if (!a || !a.href) return null; var rel = String(a.getAttribute("rel") || "").toLowerCase(); if (rel.includes("nofollow")) return null; if (a.getAttribute("data-no-instant") !== null) return null; if (a.getAttribute("data-hyperinstant") === "off") return null; if (a.target && a.target !== "_self") return null; return a;} function score(signal, a){var s = 60; if (signal === "mousedown") s = 120; else if (signal === "touchstart") s = 118; else if (signal === "focusin") s = 98; else if (signal === "mouseover") s = 82; if (inNav(a)) s += 8; if (String(a.getAttribute("data-hyperinstant-priority") || "").toLowerCase() === "high") s += 12; return Math.min(130, s);} function createTuner(cfg, stats){var intentCount = 0; function maybeTune(){var total = stats.prefetched + stats.prerendered; if (total < 16) return; var hr = stats.hitRate; if (hr >= 0.40) cfg.hoverDelay = clamp(cfg.hoverDelay - 8, 20, 120); else if (hr <= 0.12) cfg.hoverDelay = clamp(cfg.hoverDelay + 10, 25, 180);} return{onEnqueue: function (){intentCount++; if (cfg.autoTune && intentCount % 25 === 0) maybeTune();}, onClick: function (){}};} function start(cfg, scheduler, stats, tuner){var hoverTimer = null; function onIntent(e){var a = eligibleLink(e.target); if (!a) return; var url = a.href; stats.seen++; if (shouldDeny(url, cfg)){stats.denied++; return;} var sig = e.type; var sc = score(sig, a); clearTimeout(hoverTimer); var delay = (sig === "mouseover") ? cfg.hoverDelay : 0; hoverTimer = setTimeout(function (){scheduler.enqueue(url, sc, sig); tuner.onEnqueue();}, delay);} function onClick(e){var a = eligibleLink(e.target); if (!a) return; stats.markClick(normalizeUrl(a.href)); tuner.onClick();} (cfg.events || ["mouseover", "touchstart", "mousedown", "focusin"]).forEach(function (evt){document.addEventListener(evt, onIntent,{passive: true, capture: true});}); document.addEventListener("click", onClick,{passive: true, capture: true});} var cfg = applyModeDefaults(userCfg); cfg = detectAdapter(cfg.adapter)(cfg); var stats = new Stats(); var tuner = createTuner(cfg, stats); var scheduler = createScheduler(cfg, stats); window.HyperInstant = window.HyperInstant ||{}; window.HyperInstant.version = VERSION; if (cfg.debug){window.HyperInstantStats = stats; console.log("[HyperInstant] v" + VERSION + " loaded",{mode: cfg.mode, adapter: cfg.adapter});} start(cfg, scheduler, stats, tuner); maybeOverlay(cfg, stats);})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyperinstant",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Adaptive, safe and intelligent instant navigation engine for any website.",
|
|
5
5
|
"main": "dist/hyperinstant.min.js",
|
|
6
6
|
"module": "dist/hyperinstant.js",
|
|
@@ -20,12 +20,5 @@
|
|
|
20
20
|
"navigation"
|
|
21
21
|
],
|
|
22
22
|
"author": "Putia Web",
|
|
23
|
-
"license": "MIT"
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "https://example.com/putiaweb/hyperinstant"
|
|
27
|
-
},
|
|
28
|
-
"bugs": {
|
|
29
|
-
"url": "https://example.com/putiaweb/hyperinstant/issues"
|
|
30
|
-
}
|
|
23
|
+
"license": "MIT"
|
|
31
24
|
}
|