hyperinstant 1.0.2 → 1.1.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/README.md +12 -16
- package/dist/hyperinstant.js +79 -344
- package/dist/hyperinstant.min.js +1 -1
- package/package.json +3 -8
package/README.md
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
|
-
# HyperInstant v1.0
|
|
1
|
+
# HyperInstant v1.1.0 (Legacy-fast)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
-
|
|
5
|
-
- **Lower overhead scheduler** (capped queue + dedupe; no heavy sorting)
|
|
6
|
-
- **Safer auto-deny** (less over-blocking)
|
|
7
|
-
- **Optional auto-tuning** (OFF by default)
|
|
3
|
+
This package is intentionally a **legacy-fast** build: behavior matches the original **0.1.0** approach
|
|
4
|
+
(simple, ultra-light, perceived-speed first).
|
|
8
5
|
|
|
9
6
|
## CDN
|
|
10
7
|
```html
|
|
11
8
|
<script>
|
|
12
9
|
window.HyperInstant = {
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
strategy: "auto",
|
|
11
|
+
hoverDelay: 65,
|
|
12
|
+
maxPrefetch: 6,
|
|
13
|
+
maxPrerender: 2,
|
|
14
|
+
deny: ["/cart", "/checkout", "/my-account", "/logout", "add-to-cart"],
|
|
15
15
|
debug: false
|
|
16
16
|
};
|
|
17
17
|
</script>
|
|
18
|
-
<script src="https://unpkg.com/hyperinstant@1.0
|
|
18
|
+
<script src="https://unpkg.com/hyperinstant@1.1.0/dist/hyperinstant.min.js" defer></script>
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
##
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
adapter: "nopcommerce",
|
|
25
|
-
hoverDelay: 35,
|
|
26
|
-
enablePrerender: false
|
|
27
|
-
```
|
|
21
|
+
## Notes
|
|
22
|
+
- Uses `<link rel="prefetch|prerender">` for documents
|
|
23
|
+
- No adapters / no auto-tuning / no overlays (by design)
|
package/dist/hyperinstant.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
/*! HyperInstant v1.0.
|
|
1
|
+
/*! HyperInstant v1.1.0 (legacy-fast, 0.1.0 behavior) | MIT | Putia Web */
|
|
2
2
|
(function () {
|
|
3
3
|
if (window.__HYPERINSTANT_LOADED__) return;
|
|
4
4
|
window.__HYPERINSTANT_LOADED__ = true;
|
|
5
5
|
|
|
6
|
-
var
|
|
7
|
-
var
|
|
6
|
+
var cfg = window.HyperInstant || {};
|
|
7
|
+
var strategy = (cfg.strategy || "auto").toLowerCase();
|
|
8
|
+
var hoverDelay = (typeof cfg.hoverDelay === "number") ? cfg.hoverDelay : 65;
|
|
9
|
+
var maxPrefetch = (typeof cfg.maxPrefetch === "number") ? cfg.maxPrefetch : 6;
|
|
10
|
+
var maxPrerender = (typeof cfg.maxPrerender === "number") ? cfg.maxPrerender : 2;
|
|
11
|
+
var deny = Array.isArray(cfg.deny) ? cfg.deny : [];
|
|
12
|
+
var debug = !!cfg.debug;
|
|
13
|
+
|
|
14
|
+
var prefetched = new Set();
|
|
15
|
+
var prerendered = new Set();
|
|
16
|
+
var inflight = new Set();
|
|
17
|
+
var usedPrefetch = 0;
|
|
18
|
+
var usedPrerender = 0;
|
|
8
19
|
|
|
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
20
|
function supportsPrerender() {
|
|
19
21
|
try {
|
|
20
22
|
return "relList" in HTMLLinkElement.prototype &&
|
|
@@ -23,367 +25,100 @@
|
|
|
23
25
|
HTMLLinkElement.prototype.relList.supports("prerender");
|
|
24
26
|
} catch (e) { return false; }
|
|
25
27
|
}
|
|
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 ?? [];
|
|
63
|
-
out.debug = !!out.debug;
|
|
64
|
-
out.debugOverlay = !!out.debugOverlay;
|
|
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
|
-
|
|
71
|
-
return out;
|
|
72
|
-
}
|
|
73
|
-
|
|
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) });
|
|
81
|
-
}
|
|
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) });
|
|
87
|
-
}
|
|
88
28
|
|
|
89
|
-
function
|
|
90
|
-
var
|
|
91
|
-
|
|
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) });
|
|
29
|
+
function normalizeUrl(url) {
|
|
30
|
+
try { var u = new URL(url, location.href); u.hash = ""; return u.toString(); }
|
|
31
|
+
catch (e) { return ""; }
|
|
94
32
|
}
|
|
95
33
|
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
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; };
|
|
34
|
+
function isSameOrigin(url) {
|
|
35
|
+
try { return new URL(url, location.href).origin === location.origin; }
|
|
36
|
+
catch (e) { return false; }
|
|
107
37
|
}
|
|
108
38
|
|
|
109
|
-
|
|
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="];
|
|
113
|
-
|
|
114
|
-
function shouldDeny(url, cfg) {
|
|
39
|
+
function isDenied(url) {
|
|
115
40
|
if (!url) return true;
|
|
116
41
|
if (!isSameOrigin(url)) return true;
|
|
117
42
|
|
|
118
|
-
var u = String(url)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
var r = denyList[i];
|
|
123
|
-
if (r && u.includes(String(r).toLowerCase())) return true;
|
|
43
|
+
var u = String(url);
|
|
44
|
+
for (var i = 0; i < deny.length; i++) {
|
|
45
|
+
var d = deny[i];
|
|
46
|
+
if (d && u.indexOf(String(d)) !== -1) return true;
|
|
124
47
|
}
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
var p = new URL(url, location.href).pathname.toLowerCase();
|
|
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;
|
|
131
|
-
}
|
|
132
|
-
} catch (e) { }
|
|
133
|
-
|
|
134
|
-
for (var k = 0; k < AUTO_KEYWORDS.length; k++) if (u.includes(AUTO_KEYWORDS[k])) return true;
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
var uu = new URL(url, location.href);
|
|
138
|
-
var sp = uu.searchParams;
|
|
139
|
-
for (var q = 0; q < AUTO_PARAMS.length; q++) if (sp.has(AUTO_PARAMS[q])) return true;
|
|
140
|
-
} catch (e) { }
|
|
141
|
-
|
|
142
48
|
return false;
|
|
143
49
|
}
|
|
144
50
|
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this.prefetched = 0;
|
|
151
|
-
this.prerendered = 0;
|
|
152
|
-
this.clicked = 0;
|
|
153
|
-
this.hit = 0;
|
|
154
|
-
this._loaded = new Set();
|
|
155
|
-
}
|
|
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++;
|
|
161
|
-
};
|
|
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
|
-
};
|
|
177
|
-
};
|
|
178
|
-
|
|
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();
|
|
186
|
-
|
|
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 };
|
|
204
|
-
}
|
|
205
|
-
|
|
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
|
-
|
|
218
|
-
stats.enqueued++;
|
|
219
|
-
q.push({ url: url, score: score, signal: signal, t: Date.now() });
|
|
220
|
-
qSet.add(url);
|
|
221
|
-
pump();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function pump() {
|
|
225
|
-
var b = budgets();
|
|
226
|
-
|
|
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
|
-
}
|
|
235
|
-
|
|
236
|
-
while (q.length) {
|
|
237
|
-
var item = takeBest();
|
|
238
|
-
if (!item) break;
|
|
239
|
-
|
|
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++;
|
|
257
|
-
|
|
258
|
-
inFlight.add(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");
|
|
51
|
+
function enqueue(url) {
|
|
52
|
+
url = normalizeUrl(url);
|
|
53
|
+
if (!url) return;
|
|
54
|
+
if (isDenied(url)) return;
|
|
55
|
+
if (prefetched.has(url) || prerendered.has(url) || inflight.has(url)) return;
|
|
264
56
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
link.onerror = function () { inFlight.delete(u); };
|
|
268
|
-
})(url, type);
|
|
57
|
+
var canPrerender = supportsPrerender() && (usedPrerender < maxPrerender);
|
|
58
|
+
var canPrefetch = (usedPrefetch < maxPrefetch);
|
|
269
59
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
60
|
+
var rel = "prefetch";
|
|
61
|
+
if (strategy === "prefetch") rel = "prefetch";
|
|
62
|
+
else if (strategy === "prerender") rel = canPrerender ? "prerender" : "prefetch";
|
|
63
|
+
else rel = canPrerender ? "prerender" : "prefetch";
|
|
273
64
|
|
|
274
|
-
|
|
65
|
+
if (rel === "prefetch" && !canPrefetch) return;
|
|
66
|
+
if (rel === "prerender" && !canPrerender) rel = canPrefetch ? "prefetch" : "";
|
|
67
|
+
if (!rel) return;
|
|
275
68
|
|
|
276
|
-
|
|
277
|
-
}
|
|
69
|
+
inflight.add(url);
|
|
278
70
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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>";
|
|
291
|
-
}
|
|
292
|
-
render();
|
|
293
|
-
document.addEventListener("DOMContentLoaded", function () { document.body.appendChild(el); });
|
|
294
|
-
setInterval(render, 1200);
|
|
295
|
-
}
|
|
71
|
+
var link = document.createElement("link");
|
|
72
|
+
link.rel = rel;
|
|
73
|
+
link.href = url;
|
|
74
|
+
link.as = "document";
|
|
296
75
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (!a || !a.href) return null;
|
|
76
|
+
if (rel === "prerender") usedPrerender++;
|
|
77
|
+
else usedPrefetch++;
|
|
300
78
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
79
|
+
link.onload = function () {
|
|
80
|
+
inflight.delete(url);
|
|
81
|
+
if (rel === "prerender") prerendered.add(url);
|
|
82
|
+
else prefetched.add(url);
|
|
83
|
+
if (debug) console.log("[HyperInstant]", rel, url);
|
|
84
|
+
};
|
|
85
|
+
link.onerror = function () { inflight.delete(url); };
|
|
306
86
|
|
|
307
|
-
|
|
87
|
+
document.head.appendChild(link);
|
|
308
88
|
}
|
|
309
89
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
90
|
+
var t = null;
|
|
91
|
+
function onHover(e) {
|
|
92
|
+
var a = e.target && e.target.closest ? e.target.closest("a[href]") : null;
|
|
93
|
+
if (!a || !a.href) return;
|
|
94
|
+
if (a.target && a.target !== "_self") return;
|
|
95
|
+
if (a.getAttribute("data-no-instant") !== null) return;
|
|
316
96
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return Math.min(130, s);
|
|
97
|
+
clearTimeout(t);
|
|
98
|
+
t = setTimeout(function () { enqueue(a.href); }, hoverDelay);
|
|
320
99
|
}
|
|
321
100
|
|
|
322
|
-
function
|
|
323
|
-
var
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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 () { } };
|
|
101
|
+
function onImmediate(e) {
|
|
102
|
+
var a = e.target && e.target.closest ? e.target.closest("a[href]") : null;
|
|
103
|
+
if (!a || !a.href) return;
|
|
104
|
+
if (a.target && a.target !== "_self") return;
|
|
105
|
+
if (a.getAttribute("data-no-instant") !== null) return;
|
|
106
|
+
enqueue(a.href);
|
|
333
107
|
}
|
|
334
108
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
var a = eligibleLink(e.target);
|
|
340
|
-
if (!a) return;
|
|
341
|
-
|
|
342
|
-
var url = a.href;
|
|
343
|
-
stats.seen++;
|
|
344
|
-
|
|
345
|
-
if (shouldDeny(url, cfg)) { stats.denied++; return; }
|
|
346
|
-
|
|
347
|
-
var sig = e.type;
|
|
348
|
-
var sc = score(sig, a);
|
|
109
|
+
document.addEventListener("mouseover", onHover, { passive: true });
|
|
110
|
+
document.addEventListener("touchstart", onImmediate, { passive: true });
|
|
111
|
+
document.addEventListener("mousedown", onImmediate, { passive: true });
|
|
112
|
+
document.addEventListener("focusin", onImmediate, { passive: true });
|
|
349
113
|
|
|
350
|
-
|
|
351
|
-
|
|
114
|
+
window.addEventListener("pageshow", function () {
|
|
115
|
+
usedPrefetch = 0;
|
|
116
|
+
usedPrerender = 0;
|
|
117
|
+
});
|
|
352
118
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}, delay);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function onClick(e) {
|
|
360
|
-
var a = eligibleLink(e.target);
|
|
361
|
-
if (!a) return;
|
|
362
|
-
stats.markClick(normalizeUrl(a.href));
|
|
363
|
-
tuner.onClick();
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
(cfg.events || ["mouseover", "touchstart", "mousedown", "focusin"]).forEach(function (evt) {
|
|
367
|
-
document.addEventListener(evt, onIntent, { passive: true, capture: true });
|
|
119
|
+
if (debug) {
|
|
120
|
+
console.log("[HyperInstant] v1.1.0 legacy-fast loaded", {
|
|
121
|
+
strategy: strategy, hoverDelay: hoverDelay, maxPrefetch: maxPrefetch, maxPrerender: maxPrerender
|
|
368
122
|
});
|
|
369
|
-
document.addEventListener("click", onClick, { passive: true, capture: true });
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
var cfg = applyModeDefaults(userCfg);
|
|
373
|
-
cfg = detectAdapter(cfg.adapter)(cfg);
|
|
374
|
-
|
|
375
|
-
var stats = new Stats();
|
|
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
123
|
}
|
|
386
|
-
|
|
387
|
-
start(cfg, scheduler, stats, tuner);
|
|
388
|
-
maybeOverlay(cfg, stats);
|
|
389
124
|
})();
|
package/dist/hyperinstant.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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);})();
|
|
1
|
+
/*! HyperInstant v1.1.0 (legacy-fast, 0.1.0 behavior) | MIT | Putia Web */ (function (){if (window.__HYPERINSTANT_LOADED__) return; window.__HYPERINSTANT_LOADED__ = true; var cfg = window.HyperInstant ||{}; var strategy = (cfg.strategy || "auto").toLowerCase(); var hoverDelay = (typeof cfg.hoverDelay === "number") ? cfg.hoverDelay : 65; var maxPrefetch = (typeof cfg.maxPrefetch === "number") ? cfg.maxPrefetch : 6; var maxPrerender = (typeof cfg.maxPrerender === "number") ? cfg.maxPrerender : 2; var deny = Array.isArray(cfg.deny) ? cfg.deny : []; var debug = !!cfg.debug; var prefetched = new Set(); var prerendered = new Set(); var inflight = new Set(); var usedPrefetch = 0; var usedPrerender = 0; 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 normalizeUrl(url){try{var u = new URL(url, location.href); u.hash = ""; return u.toString();} catch (e){return "";}} function isSameOrigin(url){try{return new URL(url, location.href).origin === location.origin;} catch (e){return false;}} function isDenied(url){if (!url) return true; if (!isSameOrigin(url)) return true; var u = String(url); for (var i = 0; i < deny.length; i++){var d = deny[i]; if (d && u.indexOf(String(d)) !== -1) return true;} return false;} function enqueue(url){url = normalizeUrl(url); if (!url) return; if (isDenied(url)) return; if (prefetched.has(url) || prerendered.has(url) || inflight.has(url)) return; var canPrerender = supportsPrerender() && (usedPrerender < maxPrerender); var canPrefetch = (usedPrefetch < maxPrefetch); var rel = "prefetch"; if (strategy === "prefetch") rel = "prefetch"; else if (strategy === "prerender") rel = canPrerender ? "prerender" : "prefetch"; else rel = canPrerender ? "prerender" : "prefetch"; if (rel === "prefetch" && !canPrefetch) return; if (rel === "prerender" && !canPrerender) rel = canPrefetch ? "prefetch" : ""; if (!rel) return; inflight.add(url); var link = document.createElement("link"); link.rel = rel; link.href = url; link.as = "document"; if (rel === "prerender") usedPrerender++; else usedPrefetch++; link.onload = function (){inflight.delete(url); if (rel === "prerender") prerendered.add(url); else prefetched.add(url); if (debug) console.log("[HyperInstant]", rel, url);}; link.onerror = function (){inflight.delete(url);}; document.head.appendChild(link);} var t = null; function onHover(e){var a = e.target && e.target.closest ? e.target.closest("a[href]") : null; if (!a || !a.href) return; if (a.target && a.target !== "_self") return; if (a.getAttribute("data-no-instant") !== null) return; clearTimeout(t); t = setTimeout(function (){enqueue(a.href);}, hoverDelay);} function onImmediate(e){var a = e.target && e.target.closest ? e.target.closest("a[href]") : null; if (!a || !a.href) return; if (a.target && a.target !== "_self") return; if (a.getAttribute("data-no-instant") !== null) return; enqueue(a.href);} document.addEventListener("mouseover", onHover,{passive: true}); document.addEventListener("touchstart", onImmediate,{passive: true}); document.addEventListener("mousedown", onImmediate,{passive: true}); document.addEventListener("focusin", onImmediate,{passive: true}); window.addEventListener("pageshow", function (){usedPrefetch = 0; usedPrerender = 0;}); if (debug){console.log("[HyperInstant] v1.1.0 legacy-fast loaded",{strategy: strategy, hoverDelay: hoverDelay, maxPrefetch: maxPrefetch, maxPrerender: maxPrerender});}})();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hyperinstant",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "HyperInstant legacy-fast build (same behavior as 0.1.0), packaged as 1.1.0.",
|
|
5
5
|
"main": "dist/hyperinstant.min.js",
|
|
6
6
|
"module": "dist/hyperinstant.js",
|
|
7
7
|
"files": [
|
|
@@ -12,12 +12,7 @@
|
|
|
12
12
|
"prefetch",
|
|
13
13
|
"prerender",
|
|
14
14
|
"instant-page",
|
|
15
|
-
"web-performance"
|
|
16
|
-
"shopify",
|
|
17
|
-
"woocommerce",
|
|
18
|
-
"nopcommerce",
|
|
19
|
-
"adaptive",
|
|
20
|
-
"navigation"
|
|
15
|
+
"web-performance"
|
|
21
16
|
],
|
|
22
17
|
"author": "Putia Web",
|
|
23
18
|
"license": "MIT"
|