vite-plugin-smart-prefetch 0.3.4 → 0.3.5
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/dist/react 2/index.cjs +700 -0
- package/dist/react 2/index.cjs.map +1 -0
- package/dist/react 2/index.d.cts +157 -0
- package/dist/react 2/index.d.ts +157 -0
- package/dist/react 2/index.js +660 -0
- package/dist/react 2/index.js.map +1 -0
- package/dist/runtime 2/index.cjs +582 -0
- package/dist/runtime 2/index.cjs.map +1 -0
- package/dist/runtime 2/index.d.cts +192 -0
- package/dist/runtime 2/index.d.ts +192 -0
- package/dist/runtime 2/index.js +549 -0
- package/dist/runtime 2/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
// src/frameworks/react/usePrefetch.ts
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { useLocation } from "react-router-dom";
|
|
4
|
+
|
|
5
|
+
// src/runtime/prefetch-manager.ts
|
|
6
|
+
var PrefetchManager = class {
|
|
7
|
+
constructor(strategy = "hybrid", debug = false) {
|
|
8
|
+
this.config = null;
|
|
9
|
+
this.prefetched = /* @__PURE__ */ new Set();
|
|
10
|
+
this.observer = null;
|
|
11
|
+
this.strategy = strategy;
|
|
12
|
+
this.debug = debug;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initialize the prefetch manager
|
|
16
|
+
* Loads configuration and sets up observers
|
|
17
|
+
*/
|
|
18
|
+
async init() {
|
|
19
|
+
if (this.debug) {
|
|
20
|
+
console.log("\u{1F680} Smart Prefetch Manager - Initializing...");
|
|
21
|
+
console.log(" Strategy:", this.strategy);
|
|
22
|
+
console.log(" Debug mode: enabled");
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
if (this.debug) {
|
|
26
|
+
console.log("\u{1F4E5} Fetching prefetch-config.json...");
|
|
27
|
+
}
|
|
28
|
+
const response = await fetch("/prefetch-config.json");
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
if (response.status === 404) {
|
|
31
|
+
if (this.debug) {
|
|
32
|
+
console.warn("\u26A0\uFE0F Prefetch config not found (404)");
|
|
33
|
+
console.log(" This is expected if:");
|
|
34
|
+
console.log(" 1. You haven't built the app yet (run `pnpm build`)");
|
|
35
|
+
console.log(" 2. The plugin failed to generate the config during build");
|
|
36
|
+
console.log(" Prefetch manager will be inactive until config is available");
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Failed to load prefetch config: ${response.statusText}`);
|
|
41
|
+
}
|
|
42
|
+
this.config = await response.json();
|
|
43
|
+
if (this.debug && this.config) {
|
|
44
|
+
console.log("\u2705 Config loaded successfully");
|
|
45
|
+
console.log(` Routes with prefetch rules: ${Object.keys(this.config.routes).length} ${typeof this.config.routes}`);
|
|
46
|
+
console.log(` Environment: ${this.config.environment}`);
|
|
47
|
+
console.log(` Config version: ${this.config.version || "N/A"}`);
|
|
48
|
+
console.groupCollapsed("\u{1F4CB} Available prefetch rules:");
|
|
49
|
+
Object.entries(this.config.routes).forEach(([source, data]) => {
|
|
50
|
+
console.log(
|
|
51
|
+
`${source} \u2192 ${data.prefetch.length} target(s)`,
|
|
52
|
+
data.prefetch.map((t) => t.route)
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
console.groupEnd();
|
|
56
|
+
}
|
|
57
|
+
if (this.strategy === "visible" || this.strategy === "hybrid") {
|
|
58
|
+
this.initIntersectionObserver();
|
|
59
|
+
if (this.debug) {
|
|
60
|
+
console.log("\u{1F441}\uFE0F IntersectionObserver initialized for visibility-based prefetching");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (this.debug) {
|
|
64
|
+
console.log("\u2705 Prefetch Manager fully initialized");
|
|
65
|
+
console.log("\u{1F4A1} Run window.__PREFETCH_DEBUG__() to see current state");
|
|
66
|
+
window.__PREFETCH_DEBUG__ = () => this.logDebugInfo();
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (this.debug) {
|
|
70
|
+
console.error("\u274C Failed to initialize Prefetch Manager:", error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Prefetch routes based on current route
|
|
76
|
+
*/
|
|
77
|
+
prefetch(currentRoute) {
|
|
78
|
+
const cleanRoute = currentRoute.split("?")[0].split("#")[0];
|
|
79
|
+
if (this.debug) {
|
|
80
|
+
console.log(`\u{1F4CD} Checking prefetch for route: ${currentRoute}`);
|
|
81
|
+
if (currentRoute !== cleanRoute) {
|
|
82
|
+
console.log(` Clean route: ${cleanRoute}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!this.config) {
|
|
86
|
+
if (this.debug) {
|
|
87
|
+
console.warn("\u26A0\uFE0F Config not loaded yet - cannot prefetch");
|
|
88
|
+
console.log(" Make sure the app was built with the plugin enabled");
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!this.shouldPrefetch()) {
|
|
93
|
+
if (this.debug) {
|
|
94
|
+
console.log("\u23ED\uFE0F Skipping prefetch due to network conditions");
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
let routeConfig = this.config.routes[cleanRoute] || this.config.routes[currentRoute];
|
|
99
|
+
const matchedRoute = routeConfig ? this.config.routes[cleanRoute] ? cleanRoute : currentRoute : null;
|
|
100
|
+
if (!routeConfig) {
|
|
101
|
+
if (this.debug) {
|
|
102
|
+
console.warn(`\u26A0\uFE0F No prefetch rules configured for route: ${cleanRoute}`);
|
|
103
|
+
console.groupCollapsed("Available routes in config:");
|
|
104
|
+
Object.keys(this.config.routes).forEach((route) => {
|
|
105
|
+
const targets = this.config.routes[route].prefetch;
|
|
106
|
+
console.log(` ${route} \u2192 ${targets.length} target(s)`, targets.map((t) => t.route));
|
|
107
|
+
});
|
|
108
|
+
console.groupEnd();
|
|
109
|
+
console.log(`\u{1F4A1} Tip: Make sure your manualRules key matches exactly: "${cleanRoute}"`);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
console.log(`\u2705 Found prefetch rule for: ${matchedRoute}`);
|
|
114
|
+
console.log(` Total targets to prefetch: ${routeConfig.prefetch.length}`);
|
|
115
|
+
console.log(` Strategy: ${this.strategy}`);
|
|
116
|
+
if (this.debug) {
|
|
117
|
+
console.groupCollapsed(`\u{1F4CA} Prefetch Rules for: ${matchedRoute}`);
|
|
118
|
+
routeConfig.prefetch.forEach((target, index) => {
|
|
119
|
+
console.log(` ${index + 1}. ${target.route} (${target.priority} priority, ${(target.probability * 100).toFixed(1)}%)`);
|
|
120
|
+
});
|
|
121
|
+
console.groupEnd();
|
|
122
|
+
}
|
|
123
|
+
routeConfig.prefetch.forEach((target) => {
|
|
124
|
+
this.prefetchTarget(target);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Prefetch a specific target
|
|
129
|
+
*/
|
|
130
|
+
prefetchTarget(target) {
|
|
131
|
+
if (this.prefetched.has(target.route)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
switch (this.strategy) {
|
|
135
|
+
case "auto":
|
|
136
|
+
this.injectPrefetchLink(target);
|
|
137
|
+
break;
|
|
138
|
+
case "hover":
|
|
139
|
+
break;
|
|
140
|
+
case "visible":
|
|
141
|
+
break;
|
|
142
|
+
case "idle":
|
|
143
|
+
this.prefetchOnIdle(target);
|
|
144
|
+
break;
|
|
145
|
+
case "hybrid":
|
|
146
|
+
this.prefetchHybrid(target);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Hybrid strategy: prioritize based on probability
|
|
152
|
+
* FOR TESTING: Fetch all priorities to verify chunks are correct
|
|
153
|
+
*/
|
|
154
|
+
prefetchHybrid(target) {
|
|
155
|
+
if (target.priority === "high" || target.probability >= 0.7) {
|
|
156
|
+
console.log(` \u26A1 High priority (${(target.probability * 100).toFixed(1)}%) - Prefetching immediately`);
|
|
157
|
+
this.injectPrefetchLink(target);
|
|
158
|
+
} else if (target.priority === "medium" || target.probability >= 0.4) {
|
|
159
|
+
console.log(` \u23F1\uFE0F Medium priority (${(target.probability * 100).toFixed(1)}%) - Fetching immediately (TESTING MODE)`);
|
|
160
|
+
this.injectPrefetchLink(target);
|
|
161
|
+
} else {
|
|
162
|
+
console.log(` \u{1F514} Low priority (${(target.probability * 100).toFixed(1)}%) - Fetching immediately (TESTING MODE)`);
|
|
163
|
+
this.injectPrefetchLink(target);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Prefetch during idle time
|
|
168
|
+
*/
|
|
169
|
+
prefetchOnIdle(target) {
|
|
170
|
+
if ("requestIdleCallback" in window) {
|
|
171
|
+
requestIdleCallback(() => {
|
|
172
|
+
this.injectPrefetchLink(target);
|
|
173
|
+
});
|
|
174
|
+
} else {
|
|
175
|
+
setTimeout(() => {
|
|
176
|
+
this.injectPrefetchLink(target);
|
|
177
|
+
}, 1e3);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Inject prefetch link into DOM
|
|
182
|
+
*/
|
|
183
|
+
injectPrefetchLink(target) {
|
|
184
|
+
if (this.prefetched.has(target.route)) {
|
|
185
|
+
if (this.debug) {
|
|
186
|
+
console.log(` \u23ED\uFE0F Already prefetched: ${target.route}`);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const link = document.createElement("link");
|
|
191
|
+
link.rel = "modulepreload";
|
|
192
|
+
link.href = `/${target.chunk}`;
|
|
193
|
+
link.setAttribute("data-prefetch-route", target.route);
|
|
194
|
+
link.setAttribute("data-prefetch-priority", target.priority);
|
|
195
|
+
link.setAttribute("data-prefetch-probability", target.probability.toString());
|
|
196
|
+
console.groupCollapsed(`\u{1F517} PREFETCH: ${target.route}`);
|
|
197
|
+
console.log(` \u{1F4C4} Page/Route: ${target.route}`);
|
|
198
|
+
console.log(` \u{1F4E6} Main Chunk: ${target.chunk}`);
|
|
199
|
+
console.log(` \u{1F310} Full URL: ${window.location.origin}/${target.chunk}`);
|
|
200
|
+
console.log(` \u2B50 Priority: ${target.priority}`);
|
|
201
|
+
console.log(` \u{1F4CA} Probability: ${(target.probability * 100).toFixed(1)}%`);
|
|
202
|
+
if (this.debug) {
|
|
203
|
+
console.log(` Link element:`, link);
|
|
204
|
+
}
|
|
205
|
+
document.head.appendChild(link);
|
|
206
|
+
let totalChunksForThisRoute = 1;
|
|
207
|
+
if (target.imports && target.imports.length > 0) {
|
|
208
|
+
console.log(` \u{1F4DA} Dependencies (${target.imports.length}):`);
|
|
209
|
+
target.imports.forEach((importChunk, index) => {
|
|
210
|
+
const importId = `import:${importChunk}`;
|
|
211
|
+
if (this.prefetched.has(importId)) {
|
|
212
|
+
console.log(` ${index + 1}. ${importChunk} (already prefetched)`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const importLink = document.createElement("link");
|
|
216
|
+
importLink.rel = "modulepreload";
|
|
217
|
+
importLink.href = `/${importChunk}`;
|
|
218
|
+
importLink.setAttribute("data-prefetch-import", "true");
|
|
219
|
+
importLink.setAttribute("data-prefetch-parent", target.route);
|
|
220
|
+
document.head.appendChild(importLink);
|
|
221
|
+
this.prefetched.add(importId);
|
|
222
|
+
totalChunksForThisRoute++;
|
|
223
|
+
console.log(` ${index + 1}. ${importChunk} \u2705`);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
this.prefetched.add(target.route);
|
|
227
|
+
console.log(` \u2705 Total chunks injected: ${totalChunksForThisRoute}`);
|
|
228
|
+
console.log(` \u{1F4C8} Overall prefetched count: ${this.prefetched.size}`);
|
|
229
|
+
if (this.debug) {
|
|
230
|
+
console.log(` DOM Status: Link tag added to document.head`);
|
|
231
|
+
setTimeout(() => {
|
|
232
|
+
const addedLink = document.querySelector(`link[data-prefetch-route="${target.route}"]`);
|
|
233
|
+
if (addedLink) {
|
|
234
|
+
console.log(` \u2714\uFE0F DOM Verification: Link exists`);
|
|
235
|
+
console.log(` href:`, addedLink.href);
|
|
236
|
+
} else {
|
|
237
|
+
console.error(` \u274C ERROR: Link tag not found in DOM`);
|
|
238
|
+
}
|
|
239
|
+
}, 100);
|
|
240
|
+
}
|
|
241
|
+
console.groupEnd();
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Check if prefetching should be performed based on network conditions
|
|
245
|
+
*/
|
|
246
|
+
shouldPrefetch() {
|
|
247
|
+
const nav = navigator;
|
|
248
|
+
const connection = nav.connection || nav.mozConnection || nav.webkitConnection;
|
|
249
|
+
if (!connection) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
if (connection.saveData) {
|
|
253
|
+
if (this.debug) console.log(" \u26A0\uFE0F Data Saver enabled");
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const slowConnections = ["slow-2g", "2g"];
|
|
257
|
+
if (slowConnections.includes(connection.effectiveType)) {
|
|
258
|
+
if (this.debug) console.log(` \u26A0\uFE0F Slow connection: ${connection.effectiveType}`);
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
if (connection.type === "cellular" && connection.effectiveType !== "4g") {
|
|
262
|
+
if (this.debug) console.log(" \u26A0\uFE0F Metered connection");
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Initialize IntersectionObserver for 'visible' strategy
|
|
269
|
+
*/
|
|
270
|
+
initIntersectionObserver() {
|
|
271
|
+
this.observer = new IntersectionObserver(
|
|
272
|
+
(entries) => {
|
|
273
|
+
entries.forEach((entry) => {
|
|
274
|
+
if (entry.isIntersecting) {
|
|
275
|
+
const route = entry.target.getAttribute("data-prefetch-route");
|
|
276
|
+
if (route && this.config) {
|
|
277
|
+
Object.values(this.config.routes).forEach((routeConfig) => {
|
|
278
|
+
const target = routeConfig.prefetch.find((t) => t.route === route);
|
|
279
|
+
if (target) {
|
|
280
|
+
this.injectPrefetchLink(target);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
rootMargin: "50px"
|
|
289
|
+
// Start prefetch 50px before element is visible
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Observe an element for visibility-based prefetching
|
|
295
|
+
*/
|
|
296
|
+
observeLink(element, route) {
|
|
297
|
+
if (this.observer) {
|
|
298
|
+
element.setAttribute("data-prefetch-route", route);
|
|
299
|
+
this.observer.observe(element);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Manually trigger prefetch for a specific route
|
|
304
|
+
* Useful for hover events
|
|
305
|
+
*/
|
|
306
|
+
prefetchRoute(route) {
|
|
307
|
+
if (!this.config) return;
|
|
308
|
+
Object.values(this.config.routes).forEach((routeConfig) => {
|
|
309
|
+
const target = routeConfig.prefetch.find((t) => t.route === route);
|
|
310
|
+
if (target) {
|
|
311
|
+
this.injectPrefetchLink(target);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Get prefetch statistics
|
|
317
|
+
*/
|
|
318
|
+
getStats() {
|
|
319
|
+
return {
|
|
320
|
+
totalPrefetched: this.prefetched.size,
|
|
321
|
+
prefetchedRoutes: Array.from(this.prefetched),
|
|
322
|
+
configLoaded: this.config !== null,
|
|
323
|
+
strategy: this.strategy
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Check if a route has been prefetched
|
|
328
|
+
*/
|
|
329
|
+
isPrefetched(route) {
|
|
330
|
+
return this.prefetched.has(route);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Clear all prefetch state
|
|
334
|
+
*/
|
|
335
|
+
clear() {
|
|
336
|
+
this.prefetched.clear();
|
|
337
|
+
if (this.observer) {
|
|
338
|
+
this.observer.disconnect();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Get all prefetch link elements currently in the DOM
|
|
343
|
+
*/
|
|
344
|
+
getActivePrefetchLinks() {
|
|
345
|
+
return Array.from(document.querySelectorAll('link[rel="modulepreload"][data-prefetch-route]'));
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Log current state for debugging
|
|
349
|
+
*/
|
|
350
|
+
logDebugInfo() {
|
|
351
|
+
console.group("\u{1F41B} Smart Prefetch Debug Info");
|
|
352
|
+
console.log("Strategy:", this.strategy);
|
|
353
|
+
console.log("Config loaded:", this.config !== null);
|
|
354
|
+
console.log("Total prefetch entries:", this.prefetched.size);
|
|
355
|
+
console.log("Prefetched items:", Array.from(this.prefetched));
|
|
356
|
+
const links = this.getActivePrefetchLinks();
|
|
357
|
+
console.log("\n\u{1F517} Active Link Tags in DOM:", links.length);
|
|
358
|
+
if (links.length > 0) {
|
|
359
|
+
console.table(links.map((link) => ({
|
|
360
|
+
route: link.getAttribute("data-prefetch-route"),
|
|
361
|
+
chunk: link.href.replace(window.location.origin + "/", ""),
|
|
362
|
+
priority: link.getAttribute("data-prefetch-priority"),
|
|
363
|
+
probability: link.getAttribute("data-prefetch-probability")
|
|
364
|
+
})));
|
|
365
|
+
}
|
|
366
|
+
if (this.config) {
|
|
367
|
+
console.log("\n\u{1F4CA} Configuration Summary:");
|
|
368
|
+
console.log("Available routes in config:", Object.keys(this.config.routes).length);
|
|
369
|
+
console.log("Config environment:", this.config.environment);
|
|
370
|
+
console.group("\u{1F4CB} All configured routes with prefetch targets:");
|
|
371
|
+
Object.entries(this.config.routes).forEach(([route, data]) => {
|
|
372
|
+
console.group(`${route}`);
|
|
373
|
+
data.prefetch.forEach((t, idx) => {
|
|
374
|
+
console.log(` ${idx + 1}. ${t.route} (${t.priority}, ${(t.probability * 100).toFixed(1)}%) \u2192 ${t.chunk}`);
|
|
375
|
+
});
|
|
376
|
+
console.groupEnd();
|
|
377
|
+
});
|
|
378
|
+
console.groupEnd();
|
|
379
|
+
console.log("\n\u{1F4A1} Current location:", window.location.pathname);
|
|
380
|
+
console.log("\u{1F4A1} Clean route:", window.location.pathname.split("?")[0].split("#")[0]);
|
|
381
|
+
}
|
|
382
|
+
console.groupEnd();
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Log all prefetched pages and chunks (summary view)
|
|
386
|
+
*/
|
|
387
|
+
logPrefetchSummary() {
|
|
388
|
+
const links = this.getActivePrefetchLinks();
|
|
389
|
+
const routeLinks = links.filter((l) => !l.getAttribute("data-prefetch-import"));
|
|
390
|
+
const dependencyLinks = links.filter((l) => l.getAttribute("data-prefetch-import"));
|
|
391
|
+
console.group("\u{1F4CA} PREFETCH SUMMARY");
|
|
392
|
+
console.log(`Total pages prefetched: ${routeLinks.length}`);
|
|
393
|
+
console.log(`Total dependency chunks: ${dependencyLinks.length}`);
|
|
394
|
+
console.log(`Total overall entries: ${this.prefetched.size}`);
|
|
395
|
+
if (routeLinks.length > 0) {
|
|
396
|
+
console.group("\u{1F4C4} Pages/Routes Prefetched:");
|
|
397
|
+
const pageTable = routeLinks.map((link) => ({
|
|
398
|
+
route: link.getAttribute("data-prefetch-route"),
|
|
399
|
+
chunk: link.href.replace(window.location.origin + "/", ""),
|
|
400
|
+
priority: link.getAttribute("data-prefetch-priority"),
|
|
401
|
+
probability: `${link.getAttribute("data-prefetch-probability")}`
|
|
402
|
+
}));
|
|
403
|
+
console.table(pageTable);
|
|
404
|
+
console.groupEnd();
|
|
405
|
+
}
|
|
406
|
+
if (dependencyLinks.length > 0) {
|
|
407
|
+
console.group("\u{1F4E6} Dependency Chunks Prefetched:");
|
|
408
|
+
const depsTable = dependencyLinks.map((link) => ({
|
|
409
|
+
parentRoute: link.getAttribute("data-prefetch-parent"),
|
|
410
|
+
chunk: link.href.replace(window.location.origin + "/", "")
|
|
411
|
+
}));
|
|
412
|
+
console.table(depsTable);
|
|
413
|
+
console.groupEnd();
|
|
414
|
+
}
|
|
415
|
+
console.groupEnd();
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// src/frameworks/react/usePrefetch.ts
|
|
420
|
+
var managerInstance = null;
|
|
421
|
+
function usePrefetch() {
|
|
422
|
+
const location = useLocation();
|
|
423
|
+
const initialized = useRef(false);
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
if (!initialized.current) {
|
|
426
|
+
const config = window.__SMART_PREFETCH__ || {};
|
|
427
|
+
const strategy = config.strategy || "hybrid";
|
|
428
|
+
const debug = config.debug || false;
|
|
429
|
+
managerInstance = new PrefetchManager(strategy, debug);
|
|
430
|
+
managerInstance.init();
|
|
431
|
+
initialized.current = true;
|
|
432
|
+
if (debug) {
|
|
433
|
+
console.log("\u2705 usePrefetch initialized");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}, []);
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
if (managerInstance) {
|
|
439
|
+
managerInstance.prefetch(location.pathname);
|
|
440
|
+
}
|
|
441
|
+
}, [location.pathname]);
|
|
442
|
+
return managerInstance;
|
|
443
|
+
}
|
|
444
|
+
function getPrefetchManager() {
|
|
445
|
+
return managerInstance;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/frameworks/react/PrefetchLink.tsx
|
|
449
|
+
import React, { useRef as useRef2, useEffect as useEffect2 } from "react";
|
|
450
|
+
import { Link } from "react-router-dom";
|
|
451
|
+
import { jsx } from "react/jsx-runtime";
|
|
452
|
+
var PrefetchLink = React.forwardRef(
|
|
453
|
+
({ prefetch, to, children, onMouseEnter, ...props }, forwardedRef) => {
|
|
454
|
+
const linkRef = useRef2(null);
|
|
455
|
+
const manager = getPrefetchManager();
|
|
456
|
+
const route = typeof to === "string" ? to : to.pathname || "";
|
|
457
|
+
useEffect2(() => {
|
|
458
|
+
if (prefetch === "visible" && linkRef.current && manager) {
|
|
459
|
+
manager.observeLink(linkRef.current, route);
|
|
460
|
+
}
|
|
461
|
+
}, [prefetch, route, manager]);
|
|
462
|
+
const handleMouseEnter = (e) => {
|
|
463
|
+
if (prefetch === "hover" && manager) {
|
|
464
|
+
manager.prefetchRoute(route);
|
|
465
|
+
}
|
|
466
|
+
if (onMouseEnter) {
|
|
467
|
+
onMouseEnter(e);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
return /* @__PURE__ */ jsx(
|
|
471
|
+
Link,
|
|
472
|
+
{
|
|
473
|
+
ref: (node) => {
|
|
474
|
+
if (node) {
|
|
475
|
+
linkRef.current = node;
|
|
476
|
+
if (typeof forwardedRef === "function") {
|
|
477
|
+
forwardedRef(node);
|
|
478
|
+
} else if (forwardedRef) {
|
|
479
|
+
forwardedRef.current = node;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
to,
|
|
484
|
+
onMouseEnter: handleMouseEnter,
|
|
485
|
+
...props,
|
|
486
|
+
children
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
PrefetchLink.displayName = "PrefetchLink";
|
|
492
|
+
|
|
493
|
+
// src/frameworks/react/PrefetchDebugPanel.tsx
|
|
494
|
+
import { useEffect as useEffect3, useState } from "react";
|
|
495
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
496
|
+
function PrefetchDebugPanel({
|
|
497
|
+
manager,
|
|
498
|
+
position = "bottom-right",
|
|
499
|
+
defaultOpen = false
|
|
500
|
+
}) {
|
|
501
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
502
|
+
const [stats, setStats] = useState(null);
|
|
503
|
+
const [links, setLinks] = useState([]);
|
|
504
|
+
useEffect3(() => {
|
|
505
|
+
if (!manager) return;
|
|
506
|
+
const updateStats = () => {
|
|
507
|
+
setStats(manager.getStats());
|
|
508
|
+
const linkElements = manager.getActivePrefetchLinks();
|
|
509
|
+
setLinks(
|
|
510
|
+
linkElements.map((link) => ({
|
|
511
|
+
route: link.getAttribute("data-prefetch-route") || "",
|
|
512
|
+
href: link.href,
|
|
513
|
+
priority: link.getAttribute("data-prefetch-priority") || "",
|
|
514
|
+
probability: link.getAttribute("data-prefetch-probability") || ""
|
|
515
|
+
}))
|
|
516
|
+
);
|
|
517
|
+
};
|
|
518
|
+
updateStats();
|
|
519
|
+
const interval = setInterval(updateStats, 1e3);
|
|
520
|
+
return () => clearInterval(interval);
|
|
521
|
+
}, [manager]);
|
|
522
|
+
if (!manager) {
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
const positionStyles = {
|
|
526
|
+
"top-left": { top: 10, left: 10 },
|
|
527
|
+
"top-right": { top: 10, right: 10 },
|
|
528
|
+
"bottom-left": { bottom: 10, left: 10 },
|
|
529
|
+
"bottom-right": { bottom: 10, right: 10 }
|
|
530
|
+
};
|
|
531
|
+
const containerStyle = {
|
|
532
|
+
position: "fixed",
|
|
533
|
+
...positionStyles[position],
|
|
534
|
+
zIndex: 99999,
|
|
535
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
536
|
+
fontSize: "12px"
|
|
537
|
+
};
|
|
538
|
+
const buttonStyle = {
|
|
539
|
+
background: "#4CAF50",
|
|
540
|
+
color: "white",
|
|
541
|
+
border: "none",
|
|
542
|
+
borderRadius: "50%",
|
|
543
|
+
width: "48px",
|
|
544
|
+
height: "48px",
|
|
545
|
+
cursor: "pointer",
|
|
546
|
+
boxShadow: "0 4px 8px rgba(0,0,0,0.2)",
|
|
547
|
+
display: "flex",
|
|
548
|
+
alignItems: "center",
|
|
549
|
+
justifyContent: "center",
|
|
550
|
+
fontSize: "20px"
|
|
551
|
+
};
|
|
552
|
+
const panelStyle = {
|
|
553
|
+
background: "white",
|
|
554
|
+
border: "1px solid #ddd",
|
|
555
|
+
borderRadius: "8px",
|
|
556
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
557
|
+
padding: "12px",
|
|
558
|
+
minWidth: "320px",
|
|
559
|
+
maxWidth: "400px",
|
|
560
|
+
maxHeight: "500px",
|
|
561
|
+
overflow: "auto",
|
|
562
|
+
marginBottom: "8px"
|
|
563
|
+
};
|
|
564
|
+
const headerStyle = {
|
|
565
|
+
fontWeight: "bold",
|
|
566
|
+
marginBottom: "8px",
|
|
567
|
+
color: "#333",
|
|
568
|
+
borderBottom: "2px solid #4CAF50",
|
|
569
|
+
paddingBottom: "4px"
|
|
570
|
+
};
|
|
571
|
+
const statStyle = {
|
|
572
|
+
display: "flex",
|
|
573
|
+
justifyContent: "space-between",
|
|
574
|
+
padding: "4px 0",
|
|
575
|
+
borderBottom: "1px solid #eee"
|
|
576
|
+
};
|
|
577
|
+
const linkStyle = {
|
|
578
|
+
background: "#f5f5f5",
|
|
579
|
+
padding: "8px",
|
|
580
|
+
borderRadius: "4px",
|
|
581
|
+
marginBottom: "8px",
|
|
582
|
+
fontSize: "11px"
|
|
583
|
+
};
|
|
584
|
+
return /* @__PURE__ */ jsxs("div", { style: containerStyle, children: [
|
|
585
|
+
isOpen && stats && /* @__PURE__ */ jsxs("div", { style: panelStyle, children: [
|
|
586
|
+
/* @__PURE__ */ jsx2("div", { style: headerStyle, children: "\u{1F680} Smart Prefetch Debug" }),
|
|
587
|
+
/* @__PURE__ */ jsxs("div", { style: { marginBottom: "12px" }, children: [
|
|
588
|
+
/* @__PURE__ */ jsxs("div", { style: statStyle, children: [
|
|
589
|
+
/* @__PURE__ */ jsx2("span", { children: "Strategy:" }),
|
|
590
|
+
/* @__PURE__ */ jsx2("strong", { children: stats.strategy })
|
|
591
|
+
] }),
|
|
592
|
+
/* @__PURE__ */ jsxs("div", { style: statStyle, children: [
|
|
593
|
+
/* @__PURE__ */ jsx2("span", { children: "Config Loaded:" }),
|
|
594
|
+
/* @__PURE__ */ jsx2("strong", { children: stats.configLoaded ? "\u2705" : "\u274C" })
|
|
595
|
+
] }),
|
|
596
|
+
/* @__PURE__ */ jsxs("div", { style: statStyle, children: [
|
|
597
|
+
/* @__PURE__ */ jsx2("span", { children: "Total Prefetched:" }),
|
|
598
|
+
/* @__PURE__ */ jsx2("strong", { children: stats.totalPrefetched })
|
|
599
|
+
] }),
|
|
600
|
+
/* @__PURE__ */ jsxs("div", { style: statStyle, children: [
|
|
601
|
+
/* @__PURE__ */ jsx2("span", { children: "Link Tags in DOM:" }),
|
|
602
|
+
/* @__PURE__ */ jsx2("strong", { children: links.length })
|
|
603
|
+
] })
|
|
604
|
+
] }),
|
|
605
|
+
stats.prefetchedRoutes.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
606
|
+
/* @__PURE__ */ jsx2("div", { style: { ...headerStyle, fontSize: "11px" }, children: "Prefetched Routes:" }),
|
|
607
|
+
/* @__PURE__ */ jsx2("div", { style: { fontSize: "11px", color: "#666", marginBottom: "8px" }, children: stats.prefetchedRoutes.join(", ") })
|
|
608
|
+
] }),
|
|
609
|
+
links.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
610
|
+
/* @__PURE__ */ jsx2("div", { style: { ...headerStyle, fontSize: "11px" }, children: "Active Link Tags:" }),
|
|
611
|
+
links.map((link, i) => /* @__PURE__ */ jsxs("div", { style: linkStyle, children: [
|
|
612
|
+
/* @__PURE__ */ jsx2("div", { children: /* @__PURE__ */ jsx2("strong", { children: link.route }) }),
|
|
613
|
+
/* @__PURE__ */ jsxs("div", { style: { color: "#666", marginTop: "4px" }, children: [
|
|
614
|
+
"Priority: ",
|
|
615
|
+
link.priority,
|
|
616
|
+
" | Prob: ",
|
|
617
|
+
(parseFloat(link.probability) * 100).toFixed(1),
|
|
618
|
+
"%"
|
|
619
|
+
] }),
|
|
620
|
+
/* @__PURE__ */ jsx2("div", { style: { color: "#999", marginTop: "2px", wordBreak: "break-all" }, children: link.href.split("/").pop() })
|
|
621
|
+
] }, i))
|
|
622
|
+
] }),
|
|
623
|
+
/* @__PURE__ */ jsx2(
|
|
624
|
+
"button",
|
|
625
|
+
{
|
|
626
|
+
onClick: () => {
|
|
627
|
+
manager.logDebugInfo();
|
|
628
|
+
},
|
|
629
|
+
style: {
|
|
630
|
+
width: "100%",
|
|
631
|
+
padding: "8px",
|
|
632
|
+
background: "#2196F3",
|
|
633
|
+
color: "white",
|
|
634
|
+
border: "none",
|
|
635
|
+
borderRadius: "4px",
|
|
636
|
+
cursor: "pointer",
|
|
637
|
+
marginTop: "8px"
|
|
638
|
+
},
|
|
639
|
+
children: "Log Debug Info to Console"
|
|
640
|
+
}
|
|
641
|
+
)
|
|
642
|
+
] }),
|
|
643
|
+
/* @__PURE__ */ jsx2(
|
|
644
|
+
"button",
|
|
645
|
+
{
|
|
646
|
+
style: buttonStyle,
|
|
647
|
+
onClick: () => setIsOpen(!isOpen),
|
|
648
|
+
title: "Smart Prefetch Debug Panel",
|
|
649
|
+
children: "\u{1F517}"
|
|
650
|
+
}
|
|
651
|
+
)
|
|
652
|
+
] });
|
|
653
|
+
}
|
|
654
|
+
export {
|
|
655
|
+
PrefetchDebugPanel,
|
|
656
|
+
PrefetchLink,
|
|
657
|
+
getPrefetchManager,
|
|
658
|
+
usePrefetch
|
|
659
|
+
};
|
|
660
|
+
//# sourceMappingURL=index.js.map
|