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 CHANGED
@@ -1,27 +1,23 @@
1
- # HyperInstant v1.0.2
1
+ # HyperInstant v1.1.0 (Legacy-fast)
2
2
 
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)
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
- mode: "balanced",
14
- adapter: "auto",
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.2/dist/hyperinstant.min.js" defer></script>
18
+ <script src="https://unpkg.com/hyperinstant@1.1.0/dist/hyperinstant.min.js" defer></script>
19
19
  ```
20
20
 
21
- ## “Very fast” preset (nopCommerce)
22
- ```js
23
- mode: "aggressive",
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)
@@ -1,20 +1,22 @@
1
- /*! HyperInstant v1.0.2 | MIT | Putia Web */
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 userCfg = window.HyperInstant || {};
7
- var VERSION = "1.0.2";
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 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) });
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 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; };
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
- // 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="];
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).toLowerCase();
119
-
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;
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 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();
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
- (function (u, t) {
266
- link.onload = function () { inFlight.delete(u); stats.markLoaded(u, t); };
267
- link.onerror = function () { inFlight.delete(u); };
268
- })(url, type);
57
+ var canPrerender = supportsPrerender() && (usedPrerender < maxPrerender);
58
+ var canPrefetch = (usedPrefetch < maxPrefetch);
269
59
 
270
- document.head.appendChild(link);
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
- window.addEventListener("pageshow", function () { usedPrefetch = 0; usedPrerender = 0; });
65
+ if (rel === "prefetch" && !canPrefetch) return;
66
+ if (rel === "prerender" && !canPrerender) rel = canPrefetch ? "prefetch" : "";
67
+ if (!rel) return;
275
68
 
276
- return { enqueue: enqueue };
277
- }
69
+ inflight.add(url);
278
70
 
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>";
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
- function eligibleLink(target) {
298
- var a = target && target.closest ? target.closest("a[href]") : null;
299
- if (!a || !a.href) return null;
76
+ if (rel === "prerender") usedPrerender++;
77
+ else usedPrefetch++;
300
78
 
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;
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
- return a;
87
+ document.head.appendChild(link);
308
88
  }
309
89
 
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;
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
- if (inNav(a)) s += 8;
318
- if (String(a.getAttribute("data-hyperinstant-priority") || "").toLowerCase() === "high") s += 12;
319
- return Math.min(130, s);
97
+ clearTimeout(t);
98
+ t = setTimeout(function () { enqueue(a.href); }, hoverDelay);
320
99
  }
321
100
 
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 () { } };
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
- 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;
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
- clearTimeout(hoverTimer);
351
- var delay = (sig === "mouseover") ? cfg.hoverDelay : 0;
114
+ window.addEventListener("pageshow", function () {
115
+ usedPrefetch = 0;
116
+ usedPrerender = 0;
117
+ });
352
118
 
353
- hoverTimer = setTimeout(function () {
354
- scheduler.enqueue(url, sc, sig);
355
- tuner.onEnqueue();
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
  })();
@@ -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.2",
4
- "description": "Adaptive, safe and intelligent instant navigation engine for any website.",
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"