vite-plugin-smart-prefetch 0.3.7 → 0.3.9

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/index.cjs CHANGED
@@ -40,19 +40,24 @@ var import_bigquery = require("@google-cloud/bigquery");
40
40
  var import_fs = require("fs");
41
41
  var import_path = require("path");
42
42
  var BigQueryAnalyticsConnector = class {
43
- constructor(projectId, datasetId, location = "asia-south1", debug = false) {
43
+ constructor(projectId, datasetId, location = "asia-south1", debug = false, keyFilePath) {
44
44
  this.projectId = projectId;
45
45
  this.datasetId = datasetId;
46
46
  this.location = location && location !== "asia" ? location : "asia-south1";
47
47
  this.debug = debug;
48
- this.bigquery = new import_bigquery.BigQuery({
48
+ const bigqueryConfig = {
49
49
  projectId
50
- });
50
+ };
51
+ if (keyFilePath) {
52
+ bigqueryConfig.keyFilename = keyFilePath;
53
+ }
54
+ this.bigquery = new import_bigquery.BigQuery(bigqueryConfig);
51
55
  if (this.debug) {
52
56
  console.log("\u2705 BigQuery Analytics connector initialized");
53
57
  console.log(` Project ID: ${projectId}`);
54
58
  console.log(` Dataset ID: ${datasetId}`);
55
59
  console.log(` Location: ${this.location}`);
60
+ console.log(` Authentication: ${keyFilePath ? `Service Account (${keyFilePath})` : "Application Default Credentials"}`);
56
61
  console.log(` Querying daily events tables: ${projectId}.${datasetId}.events_*`);
57
62
  }
58
63
  }
@@ -812,7 +817,15 @@ var MarkovChainTrainer = class {
812
817
  };
813
818
 
814
819
  // src/plugin/config-generator.ts
815
- var _ConfigGenerator = class _ConfigGenerator {
820
+ var ConfigGenerator = class {
821
+ /**
822
+ * NOTE: Hardcoded route/component mappings have been REMOVED to make the plugin generic.
823
+ * The plugin now uses dynamic strategies (direct match, pattern match, fuzzy match)
824
+ * to discover routes from the Vite manifest and actual file structure.
825
+ *
826
+ * This allows the plugin to work with ANY project structure without project-specific
827
+ * configuration hardcoded in the plugin code.
828
+ */
816
829
  constructor(manifest, manualRules = {}, debug = false, vite) {
817
830
  this.vite = null;
818
831
  this.isDev = false;
@@ -1001,224 +1014,69 @@ var _ConfigGenerator = class _ConfigGenerator {
1001
1014
  return merged;
1002
1015
  }
1003
1016
  /**
1004
- * NEW STRATEGY 0: Route to Chunk Name Mapping
1005
- * Maps route → component name → chunk name → manifest file
1006
- * This is the most reliable method as it uses the actual vite.config.ts chunking strategy
1007
- */
1008
- routeToChunkViaName(route) {
1009
- const normalizedRoute = route.toLowerCase();
1010
- const componentName = _ConfigGenerator.ROUTE_TO_COMPONENT_NAME[normalizedRoute];
1011
- if (!componentName) {
1012
- if (this.debug) {
1013
- console.log(` \u274C Route ${route} not in ROUTE_TO_COMPONENT_NAME mapping`);
1014
- }
1015
- return null;
1016
- }
1017
- const chunkName = _ConfigGenerator.COMPONENT_TO_CHUNK_NAME[componentName];
1018
- if (!chunkName) {
1019
- if (this.debug) {
1020
- console.log(` \u274C Component ${componentName} not in COMPONENT_TO_CHUNK_NAME mapping`);
1021
- }
1022
- return null;
1023
- }
1024
- const chunkEntry = Object.entries(this.manifest).find(
1025
- ([key, entry]) => entry.name === chunkName || key.includes(chunkName)
1026
- );
1027
- if (!chunkEntry) {
1028
- if (this.debug) {
1029
- console.log(` \u274C Chunk name ${chunkName} not found in manifest`);
1030
- }
1031
- return null;
1032
- }
1033
- const chunkFile = chunkEntry[1].file;
1034
- if (this.debug) {
1035
- console.log(
1036
- ` \u2705 Via-Name Strategy: ${route} \u2192 ${componentName} \u2192 ${chunkName} \u2192 ${chunkFile}`
1037
- );
1038
- }
1039
- return chunkFile;
1040
- }
1041
- /**
1042
- * Map route to chunk file using Vite manifest
1043
- * Tries multiple strategies to find the correct chunk
1017
+ * Map BigQuery route to Vite manifest chunk file
1018
+ *
1019
+ * The routes come from BigQuery navigation data (e.g., /purchase-order, /dispatch-order/:id)
1020
+ * We match them directly to manifest entries without trying to discover routes.
1021
+ *
1022
+ * Strategy: Match route segments against manifest file paths, ignoring hash suffixes
1023
+ * Example:
1024
+ * Route: /purchase-order/:id
1025
+ * Normalized: /purchase-order
1026
+ * Manifest: src/pages/purchase-order/index.tsx {file: assets/purchase-order-abc123.js}
1027
+ * Match: YES → return assets/purchase-order-abc123.js
1044
1028
  */
1045
1029
  routeToChunk(route) {
1046
- const viaName = this.routeToChunkViaName(route);
1047
- if (viaName) {
1048
- return viaName;
1049
- }
1050
- const directMatch = this.findDirectMatch(route);
1051
- if (directMatch) {
1052
- return directMatch;
1053
- }
1054
- const patternMatch = this.findPatternMatch(route);
1055
- if (patternMatch) {
1056
- return patternMatch;
1057
- }
1058
- const fuzzyMatch = this.findFuzzyMatch(route);
1059
- if (fuzzyMatch) {
1060
- return fuzzyMatch;
1061
- }
1062
- return null;
1063
- }
1064
- /**
1065
- * Strategy 1: Direct path match
1066
- */
1067
- findDirectMatch(route) {
1068
1030
  const normalizedRoute = route.replace(/\/[:$]\w+/g, "");
1069
- const cleanRoutePath = normalizedRoute.replace(/^\//, "");
1070
1031
  const routeSegments = normalizedRoute.split("/").filter(Boolean);
1071
- const pascalCaseComponent = routeSegments.map((segment) => {
1072
- const words = segment.split("-");
1073
- return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
1074
- }).join("/");
1075
- let singleSegmentPascal = null;
1076
- if (routeSegments.length === 1) {
1077
- const segment = routeSegments[0];
1078
- const words = segment.split("-");
1079
- singleSegmentPascal = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
1080
- }
1081
- const patterns = [
1082
- // HIGHEST PRIORITY: Exact page component name matches
1083
- singleSegmentPascal ? `src/pages/${singleSegmentPascal}.tsx` : null,
1084
- singleSegmentPascal ? `src/pages/${singleSegmentPascal}.ts` : null,
1085
- singleSegmentPascal ? `src/pages/${singleSegmentPascal}.jsx` : null,
1086
- singleSegmentPascal ? `src/pages/${singleSegmentPascal}.js` : null,
1087
- // For multi-segment routes with hyphens
1088
- `src/pages/${pascalCaseComponent}.tsx`,
1089
- `src/pages/${pascalCaseComponent}.ts`,
1090
- `src/pages/${pascalCaseComponent}.jsx`,
1091
- `src/pages/${pascalCaseComponent}.js`,
1092
- // Features folder
1093
- `src/features${normalizedRoute}/index.ts`,
1094
- `src/features${normalizedRoute}/index.tsx`,
1095
- `src/features${normalizedRoute}/index.js`,
1096
- `src/features${normalizedRoute}/index.jsx`,
1097
- // Pages folder - try both directory and file formats
1098
- `src/pages${normalizedRoute}/index.tsx`,
1099
- `src/pages${normalizedRoute}/index.ts`,
1100
- `src/pages${normalizedRoute}/index.jsx`,
1101
- `src/pages${normalizedRoute}/index.js`,
1102
- `src/pages${normalizedRoute}.tsx`,
1103
- `src/pages${normalizedRoute}.ts`,
1104
- `src/pages${normalizedRoute}.jsx`,
1105
- `src/pages${normalizedRoute}.js`,
1106
- // Fallback to old capitalize method (single capital letter)
1107
- `src/pages/${this.capitalize(cleanRoutePath)}.tsx`,
1108
- `src/pages/${this.capitalize(cleanRoutePath)}.ts`,
1109
- // Full paths with app prefix
1110
- `apps/farmart-pro/src/features${normalizedRoute}/index.ts`,
1111
- `apps/farmart-pro/src/features${normalizedRoute}/index.tsx`,
1112
- `apps/farmart-pro/src/pages${normalizedRoute}/index.tsx`
1113
- ].filter(Boolean);
1114
- for (let i = 0; i < patterns.length; i++) {
1115
- const pattern = patterns[i];
1116
- if (!pattern) continue;
1117
- const entry = this.manifest[pattern];
1118
- if (entry) {
1119
- return entry.file;
1120
- }
1121
- }
1122
- return null;
1123
- }
1124
- /**
1125
- * Capitalize first letter of string
1126
- */
1127
- capitalize(str) {
1128
- return str.charAt(0).toUpperCase() + str.slice(1);
1129
- }
1130
- /**
1131
- * Strategy 2: Pattern matching with wildcards
1132
- */
1133
- findPatternMatch(route) {
1134
- const routeSegments = route.split("/").filter(Boolean);
1135
- if (routeSegments.length === 0) return null;
1136
- const candidates = Object.entries(this.manifest).filter(([path2]) => {
1137
- const pathSegments = path2.split("/").filter(Boolean);
1138
- return routeSegments.every(
1139
- (segment) => pathSegments.some(
1140
- (ps) => ps.toLowerCase().includes(segment.replace(/[:$]\w+/, "").toLowerCase())
1141
- )
1032
+ if (routeSegments.length === 0) {
1033
+ const candidates2 = Object.entries(this.manifest).filter(
1034
+ ([path2]) => path2 === "src/pages/index.tsx" || path2 === "src/app.tsx" || path2 === "src/App.tsx" || path2 === "src/main.tsx"
1142
1035
  );
1143
- }).sort(([pathA], [pathB]) => {
1144
- if (pathA.includes("/pages/") && !pathB.includes("/pages/")) return -1;
1145
- if (!pathA.includes("/pages/") && pathB.includes("/pages/")) return 1;
1146
- const aIsEntry = pathA.includes("index.tsx") || pathA.includes("index.ts");
1147
- const bIsEntry = pathB.includes("index.tsx") || pathB.includes("index.ts");
1148
- if (aIsEntry && !bIsEntry) return -1;
1149
- if (!aIsEntry && bIsEntry) return 1;
1150
- if (pathA.includes(".tsx") && !pathB.includes(".tsx")) return -1;
1151
- if (!pathA.includes(".tsx") && pathB.includes(".tsx")) return 1;
1152
- return 0;
1153
- });
1154
- if (candidates.length > 0) {
1155
- if (this.debug) {
1156
- console.log(` \u2705 Pattern match found: ${candidates[0][0]} \u2192 ${candidates[0][1].file}`);
1157
- }
1158
- return candidates[0][1].file;
1159
- }
1160
- if (this.debug) {
1161
- console.log(` No pattern match found`);
1162
- }
1163
- return null;
1164
- }
1165
- /**
1166
- * Strategy 3: Fuzzy matching
1167
- * Converts route to camelCase/PascalCase and searches
1168
- */
1169
- findFuzzyMatch(route) {
1170
- const cleanRoute = route.replace(/\/[:$]\w+/g, "");
1171
- const routeSegments = cleanRoute.split("/").filter(Boolean);
1172
- const pascalCase = routeSegments.map((segment) => {
1173
- const words = segment.split("-");
1174
- return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
1175
- }).join("");
1176
- const camelCase = pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
1177
- if (this.debug) {
1178
- console.log(` Fuzzy match - Route: ${route}`);
1179
- console.log(` Trying PascalCase: ${pascalCase}, camelCase: ${camelCase}`);
1036
+ if (candidates2.length > 0) return candidates2[0][1].file;
1037
+ return null;
1180
1038
  }
1181
- const candidates = Object.entries(this.manifest).filter(([path2, entry]) => {
1182
- if (path2.startsWith("_") || path2.startsWith("node_modules")) {
1039
+ const candidates = Object.entries(this.manifest).filter(([path2]) => {
1040
+ if (path2.startsWith("node_modules") || path2.startsWith("_")) {
1183
1041
  return false;
1184
1042
  }
1185
- const fileName = path2.split("/").pop() || "";
1186
- const fileNameWithoutExt = fileName.replace(/\.[^.]+$/, "");
1187
- if (fileNameWithoutExt === pascalCase || fileNameWithoutExt === camelCase) {
1188
- return true;
1189
- }
1190
- if (fileName.includes(pascalCase) || fileName.includes(camelCase)) {
1191
- return true;
1192
- }
1193
- const pathSegments = path2.toLowerCase().split("/");
1194
- const lowerPascal = pascalCase.toLowerCase();
1195
- const lowerCamel = camelCase.toLowerCase();
1196
- return pathSegments.some(
1197
- (seg) => seg.includes(lowerPascal) || seg.includes(lowerCamel)
1043
+ const pathLower = path2.toLowerCase();
1044
+ return routeSegments.every(
1045
+ (segment) => pathLower.includes(segment.toLowerCase())
1198
1046
  );
1199
- }).sort(([pathA, entryA], [pathB, entryB]) => {
1200
- const aHasSrc = entryA.src ? 1 : 0;
1201
- const bHasSrc = entryB.src ? 1 : 0;
1202
- if (aHasSrc !== bHasSrc) return bHasSrc - aHasSrc;
1203
- const fileA = pathA.split("/").pop()?.replace(/\.[^.]+$/, "") || "";
1204
- const fileB = pathB.split("/").pop()?.replace(/\.[^.]+$/, "") || "";
1205
- if (fileA === pascalCase) return -1;
1206
- if (fileB === pascalCase) return 1;
1207
- if (fileA === camelCase) return -1;
1208
- if (fileB === camelCase) return 1;
1209
- if (pathA.includes("/pages/") && !pathB.includes("/pages/")) return -1;
1210
- if (!pathA.includes("/pages/") && pathB.includes("/pages/")) return 1;
1211
- return 0;
1047
+ }).sort(([pathA], [pathB]) => {
1048
+ let scoreA = 0;
1049
+ let scoreB = 0;
1050
+ if (pathA.includes("/pages/")) scoreA += 10;
1051
+ if (pathB.includes("/pages/")) scoreB += 10;
1052
+ if (pathA.includes("index.tsx")) scoreA += 8;
1053
+ if (pathB.includes("index.tsx")) scoreB += 8;
1054
+ if (pathA.includes("index.ts")) scoreA += 7;
1055
+ if (pathB.includes("index.ts")) scoreB += 7;
1056
+ const pathASegments = pathA.split("/").map((s) => s.toLowerCase()).filter((s) => s && !s.startsWith("."));
1057
+ const pathBSegments = pathB.split("/").map((s) => s.toLowerCase()).filter((s) => s && !s.startsWith("."));
1058
+ const matchCountA = routeSegments.filter(
1059
+ (seg) => pathASegments.some((ps) => ps.includes(seg.toLowerCase()))
1060
+ ).length;
1061
+ const matchCountB = routeSegments.filter(
1062
+ (seg) => pathBSegments.some((ps) => ps.includes(seg.toLowerCase()))
1063
+ ).length;
1064
+ scoreA += matchCountA * 5;
1065
+ scoreB += matchCountB * 5;
1066
+ return scoreB - scoreA;
1212
1067
  });
1213
1068
  if (candidates.length > 0) {
1214
- const result = candidates[0][1].file;
1069
+ const manifestEntry = candidates[0][1];
1215
1070
  if (this.debug) {
1216
- console.log(` \u2705 Fuzzy match found: ${candidates[0][0]} \u2192 ${result}`);
1071
+ console.log(` \u2705 Found chunk for route ${route}`);
1072
+ console.log(` Manifest: ${candidates[0][0]}`);
1073
+ console.log(` Chunk file: ${manifestEntry.file}`);
1217
1074
  }
1218
- return result;
1075
+ return manifestEntry.file;
1219
1076
  }
1220
1077
  if (this.debug) {
1221
- console.log(` No fuzzy match found`);
1078
+ console.log(` \u26A0\uFE0F No chunk found for route: ${route}`);
1079
+ console.log(` Searched for segments: ${routeSegments.join(", ")}`);
1222
1080
  }
1223
1081
  return null;
1224
1082
  }
@@ -1336,90 +1194,6 @@ var _ConfigGenerator = class _ConfigGenerator {
1336
1194
  return segmentConfigs;
1337
1195
  }
1338
1196
  };
1339
- /**
1340
- * Maps routes to their component names based on vite.config.ts chunk strategy
1341
- * This is derived from the route configuration in src/routes/index.ts
1342
- * Note: These are the core routes from vite.config.ts chunking strategy
1343
- * Routes not listed here will fall through to fuzzy matching
1344
- */
1345
- _ConfigGenerator.ROUTE_TO_COMPONENT_NAME = {
1346
- "/": "Home",
1347
- "/home": "Home",
1348
- "/dashboard": "Dashboard",
1349
- "/profile": "Profile",
1350
- "/settings": "Settings",
1351
- "/preferences": "Preferences",
1352
- "/privacy": "Privacy",
1353
- "/security": "Security",
1354
- "/analytics": "Analytics",
1355
- "/reports": "Reports",
1356
- "/metrics": "Metrics",
1357
- "/projects": "Projects",
1358
- "/tasks": "Tasks",
1359
- "/teams": "Teams",
1360
- "/workspaces": "Workspaces",
1361
- "/workflows": "Workflows",
1362
- "/templates": "Templates",
1363
- "/logs": "Logs",
1364
- "/audit-logs": "AuditLogs",
1365
- "/integrations": "Integrations",
1366
- "/api-docs": "ApiDocs",
1367
- "/api-documentation": "ApiDocs",
1368
- // Alias for space-separated variant
1369
- "/support": "Support",
1370
- "/help": "Help",
1371
- "/billing": "Billing",
1372
- "/plans": "Plans",
1373
- "/usage": "Usage",
1374
- "/permissions": "Permissions",
1375
- "/notifications": "Notifications"
1376
- };
1377
- /**
1378
- * Maps component names to chunk names based on vite.config.ts manualChunks strategy
1379
- * Each component is assigned to a specific chunk group for code splitting
1380
- *
1381
- * Note: Core components (Home, Dashboard) are not in manual chunks - they're part of main bundle
1382
- * For these, we return the main bundle file path 'js/index-*.js' which will be resolved from manifest
1383
- */
1384
- _ConfigGenerator.COMPONENT_TO_CHUNK_NAME = {
1385
- // Core components - loaded with main bundle (not code-split)
1386
- Home: "index",
1387
- // Special marker for main entry point
1388
- Dashboard: "index",
1389
- // User Profile & Settings chunk
1390
- Profile: "chunk-user-profile",
1391
- Settings: "chunk-user-profile",
1392
- Preferences: "chunk-user-profile",
1393
- Privacy: "chunk-user-profile",
1394
- Security: "chunk-user-profile",
1395
- // Analytics & Reporting chunk
1396
- Analytics: "chunk-analytics",
1397
- Reports: "chunk-analytics",
1398
- Metrics: "chunk-analytics",
1399
- // Project Management chunk
1400
- Projects: "chunk-projects",
1401
- Tasks: "chunk-projects",
1402
- Teams: "chunk-projects",
1403
- Workspaces: "chunk-projects",
1404
- // Workflows & Operations chunk
1405
- Workflows: "chunk-operations",
1406
- Templates: "chunk-operations",
1407
- Logs: "chunk-operations",
1408
- AuditLogs: "chunk-operations",
1409
- // Integration chunk
1410
- Integrations: "chunk-integrations",
1411
- ApiDocs: "chunk-integrations",
1412
- Support: "chunk-integrations",
1413
- Help: "chunk-integrations",
1414
- // Billing & Plans chunk
1415
- Billing: "chunk-billing",
1416
- Plans: "chunk-billing",
1417
- Usage: "chunk-billing",
1418
- // Admin & Notifications chunk
1419
- Permissions: "chunk-admin",
1420
- Notifications: "chunk-admin"
1421
- };
1422
- var ConfigGenerator = _ConfigGenerator;
1423
1197
 
1424
1198
  // src/plugin/cache-manager.ts
1425
1199
  var fs = __toESM(require("fs"), 1);
@@ -1712,7 +1486,8 @@ function smartPrefetch(options = {}) {
1712
1486
  analytics.credentials.projectId,
1713
1487
  analytics.credentials.datasetId,
1714
1488
  analytics.credentials.region || "asia-south1",
1715
- debug
1489
+ debug,
1490
+ analytics.credentials.keyFilePath
1716
1491
  );
1717
1492
  console.log("\n\u{1F3AF} Using real navigation data from BigQuery GA4 export...");
1718
1493
  const navigationData = await bqConnector.fetchNavigationSequences(analytics.dataRange);
@@ -1841,6 +1616,37 @@ function smartPrefetch(options = {}) {
1841
1616
  framework: '${framework}',
1842
1617
  debug: ${debug},
1843
1618
  };
1619
+
1620
+ // Initialize prefetch manager after page load
1621
+ // This allows the app to be built first, then PrefetchManager can load the config
1622
+ if (document.readyState === 'loading') {
1623
+ document.addEventListener('DOMContentLoaded', initPrefetch);
1624
+ } else {
1625
+ initPrefetch();
1626
+ }
1627
+
1628
+ async function initPrefetch() {
1629
+ try {
1630
+ // Dynamically import PrefetchManager from the bundled app
1631
+ // The app should have @farmart/vite-plugin-smart-prefetch/runtime in its dependencies
1632
+ const { PrefetchManager } = await import('@farmart/vite-plugin-smart-prefetch/runtime');
1633
+ const manager = new PrefetchManager('${strategy}', ${debug});
1634
+ await manager.init();
1635
+
1636
+ // Expose manager globally for debugging
1637
+ window.__PREFETCH_MANAGER__ = manager;
1638
+ if (${debug}) {
1639
+ console.log('\u2705 Smart Prefetch Manager initialized');
1640
+ }
1641
+ } catch (error) {
1642
+ if (${debug}) {
1643
+ console.warn('\u26A0\uFE0F Could not initialize Smart Prefetch Manager:', error);
1644
+ console.log('This is expected if:');
1645
+ console.log('1. @farmart/vite-plugin-smart-prefetch/runtime is not installed');
1646
+ console.log('2. prefetch-config.json was not generated (BigQuery data fetch failed)');
1647
+ }
1648
+ }
1649
+ }
1844
1650
  `,
1845
1651
  injectTo: "head"
1846
1652
  }