@useavalon/avalon 0.1.11 → 0.1.13

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.
Files changed (141) hide show
  1. package/README.md +54 -54
  2. package/mod.ts +302 -302
  3. package/package.json +49 -26
  4. package/src/build/integration-bundler-plugin.ts +116 -116
  5. package/src/build/integration-config.ts +168 -168
  6. package/src/build/integration-detection-plugin.ts +117 -117
  7. package/src/build/integration-resolver-plugin.ts +90 -90
  8. package/src/build/island-manifest.ts +269 -269
  9. package/src/build/island-types-generator.ts +476 -476
  10. package/src/build/mdx-island-transform.ts +464 -464
  11. package/src/build/mdx-plugin.ts +98 -98
  12. package/src/build/page-island-transform.ts +598 -598
  13. package/src/build/prop-extractors/index.ts +21 -21
  14. package/src/build/prop-extractors/lit.ts +140 -140
  15. package/src/build/prop-extractors/qwik.ts +16 -16
  16. package/src/build/prop-extractors/solid.ts +125 -125
  17. package/src/build/prop-extractors/svelte.ts +194 -194
  18. package/src/build/prop-extractors/vue.ts +111 -111
  19. package/src/build/sidecar-file-manager.ts +104 -104
  20. package/src/build/sidecar-renderer.ts +30 -30
  21. package/src/client/adapters/index.ts +21 -13
  22. package/src/client/components.ts +35 -35
  23. package/src/client/css-hmr-handler.ts +344 -344
  24. package/src/client/framework-adapter.ts +462 -462
  25. package/src/client/hmr-coordinator.ts +396 -396
  26. package/src/client/hmr-error-overlay.js +533 -533
  27. package/src/client/main.js +824 -816
  28. package/src/client/types/framework-runtime.d.ts +68 -68
  29. package/src/client/types/vite-hmr.d.ts +46 -46
  30. package/src/client/types/vite-virtual-modules.d.ts +70 -60
  31. package/src/components/Image.tsx +123 -123
  32. package/src/components/IslandErrorBoundary.tsx +145 -145
  33. package/src/components/LayoutDataErrorBoundary.tsx +141 -141
  34. package/src/components/LayoutErrorBoundary.tsx +127 -127
  35. package/src/components/PersistentIsland.tsx +52 -52
  36. package/src/components/StreamingErrorBoundary.tsx +233 -233
  37. package/src/components/StreamingLayout.tsx +538 -538
  38. package/src/core/components/component-analyzer.ts +192 -192
  39. package/src/core/components/component-detection.ts +508 -508
  40. package/src/core/components/enhanced-framework-detector.ts +500 -500
  41. package/src/core/components/framework-registry.ts +563 -563
  42. package/src/core/content/mdx-processor.ts +46 -46
  43. package/src/core/integrations/index.ts +19 -19
  44. package/src/core/integrations/loader.ts +125 -125
  45. package/src/core/integrations/registry.ts +175 -175
  46. package/src/core/islands/island-persistence.ts +325 -325
  47. package/src/core/islands/island-state-serializer.ts +258 -258
  48. package/src/core/islands/persistent-island-context.tsx +80 -80
  49. package/src/core/islands/use-persistent-state.ts +68 -68
  50. package/src/core/layout/enhanced-layout-resolver.ts +322 -322
  51. package/src/core/layout/layout-cache-manager.ts +485 -485
  52. package/src/core/layout/layout-composer.ts +357 -357
  53. package/src/core/layout/layout-data-loader.ts +516 -516
  54. package/src/core/layout/layout-discovery.ts +243 -243
  55. package/src/core/layout/layout-matcher.ts +299 -299
  56. package/src/core/layout/layout-types.ts +110 -110
  57. package/src/core/modules/framework-module-resolver.ts +273 -273
  58. package/src/islands/component-analysis.ts +213 -213
  59. package/src/islands/css-utils.ts +565 -565
  60. package/src/islands/discovery/index.ts +80 -80
  61. package/src/islands/discovery/registry.ts +340 -340
  62. package/src/islands/discovery/resolver.ts +477 -477
  63. package/src/islands/discovery/scanner.ts +386 -386
  64. package/src/islands/discovery/types.ts +117 -117
  65. package/src/islands/discovery/validator.ts +544 -544
  66. package/src/islands/discovery/watcher.ts +368 -368
  67. package/src/islands/framework-detection.ts +428 -428
  68. package/src/islands/integration-loader.ts +490 -490
  69. package/src/islands/island.tsx +565 -565
  70. package/src/islands/render-cache.ts +550 -550
  71. package/src/islands/types.ts +80 -80
  72. package/src/islands/universal-css-collector.ts +157 -157
  73. package/src/islands/universal-head-collector.ts +137 -137
  74. package/src/layout-system.d.ts +592 -592
  75. package/src/layout-system.ts +218 -218
  76. package/src/middleware/discovery.ts +268 -268
  77. package/src/middleware/executor.ts +315 -315
  78. package/src/middleware/index.ts +76 -76
  79. package/src/middleware/types.ts +99 -99
  80. package/src/nitro/build-config.ts +575 -575
  81. package/src/nitro/config.ts +483 -483
  82. package/src/nitro/error-handler.ts +636 -636
  83. package/src/nitro/index.ts +173 -173
  84. package/src/nitro/island-manifest.ts +584 -584
  85. package/src/nitro/middleware-adapter.ts +260 -260
  86. package/src/nitro/renderer.ts +1471 -1471
  87. package/src/nitro/route-discovery.ts +439 -439
  88. package/src/nitro/types.ts +321 -321
  89. package/src/render/collect-css.ts +198 -198
  90. package/src/render/error-pages.ts +79 -79
  91. package/src/render/isolated-ssr-renderer.ts +654 -654
  92. package/src/render/ssr.ts +1030 -1030
  93. package/src/schemas/api.ts +30 -30
  94. package/src/schemas/core.ts +64 -64
  95. package/src/schemas/index.ts +212 -212
  96. package/src/schemas/layout.ts +279 -279
  97. package/src/schemas/routing/index.ts +38 -38
  98. package/src/schemas/routing.ts +376 -376
  99. package/src/types/as-island.ts +20 -20
  100. package/src/types/image.d.ts +106 -106
  101. package/src/types/index.d.ts +22 -22
  102. package/src/types/island-jsx.d.ts +33 -33
  103. package/src/types/island-prop.d.ts +20 -20
  104. package/src/types/layout.ts +285 -285
  105. package/src/types/mdx.d.ts +6 -6
  106. package/src/types/routing.ts +555 -555
  107. package/src/types/types.ts +5 -5
  108. package/src/types/urlpattern.d.ts +49 -49
  109. package/src/types/vite-env.d.ts +11 -11
  110. package/src/utils/dev-logger.ts +299 -299
  111. package/src/utils/fs.ts +151 -151
  112. package/src/vite-plugin/auto-discover.ts +551 -551
  113. package/src/vite-plugin/config.ts +266 -266
  114. package/src/vite-plugin/errors.ts +127 -127
  115. package/src/vite-plugin/image-optimization.ts +156 -156
  116. package/src/vite-plugin/integration-activator.ts +126 -126
  117. package/src/vite-plugin/island-sidecar-plugin.ts +176 -176
  118. package/src/vite-plugin/module-discovery.ts +189 -189
  119. package/src/vite-plugin/nitro-integration.ts +1354 -1354
  120. package/src/vite-plugin/plugin.ts +403 -409
  121. package/src/vite-plugin/types.ts +327 -327
  122. package/src/vite-plugin/validation.ts +228 -228
  123. package/src/client/adapters/index.js +0 -12
  124. package/src/client/adapters/lit-adapter.js +0 -467
  125. package/src/client/adapters/lit-adapter.ts +0 -654
  126. package/src/client/adapters/preact-adapter.js +0 -223
  127. package/src/client/adapters/preact-adapter.ts +0 -331
  128. package/src/client/adapters/qwik-adapter.js +0 -259
  129. package/src/client/adapters/qwik-adapter.ts +0 -345
  130. package/src/client/adapters/react-adapter.js +0 -220
  131. package/src/client/adapters/react-adapter.ts +0 -353
  132. package/src/client/adapters/solid-adapter.js +0 -295
  133. package/src/client/adapters/solid-adapter.ts +0 -451
  134. package/src/client/adapters/svelte-adapter.js +0 -368
  135. package/src/client/adapters/svelte-adapter.ts +0 -524
  136. package/src/client/adapters/vue-adapter.js +0 -278
  137. package/src/client/adapters/vue-adapter.ts +0 -467
  138. package/src/client/components.js +0 -23
  139. package/src/client/css-hmr-handler.js +0 -263
  140. package/src/client/framework-adapter.js +0 -283
  141. package/src/client/hmr-coordinator.js +0 -274
@@ -1,550 +1,550 @@
1
- /**
2
- * Island Render Cache Module
3
- *
4
- * Provides caching for expensive operations in the island rendering pipeline:
5
- * - Component analysis results
6
- * - Resolved paths
7
- * - Framework detection results
8
- *
9
- * This module helps optimize island rendering by avoiding repeated file I/O
10
- * and analysis operations for the same components.
11
- *
12
- * @module render-cache
13
- */
14
-
15
- import type { AnalysisReport } from "../core/components/component-analyzer.ts";
16
- import type { Framework } from "./types.ts";
17
-
18
- /**
19
- * Cache entry for component analysis results
20
- */
21
- export interface AnalysisCacheEntry {
22
- /** The analysis result */
23
- result: AnalysisReport;
24
- /** Timestamp when the entry was cached */
25
- timestamp: number;
26
- }
27
-
28
- /**
29
- * Cache entry for resolved paths
30
- */
31
- export interface PathCacheEntry {
32
- /** The resolved path */
33
- resolved: string;
34
- /** Timestamp when the entry was cached */
35
- timestamp: number;
36
- }
37
-
38
- /**
39
- * Cache entry for framework detection results
40
- */
41
- export interface FrameworkCacheEntry {
42
- /** The detected framework */
43
- framework: Framework;
44
- /** Timestamp when the entry was cached */
45
- timestamp: number;
46
- }
47
-
48
- /**
49
- * Configuration options for the island render cache
50
- */
51
- export interface CacheConfig {
52
- /** TTL for analysis cache entries in milliseconds (default: 60000ms = 1 minute) */
53
- analysisTTL: number;
54
- /** TTL for path cache entries in milliseconds (default: 60000ms = 1 minute) */
55
- pathTTL: number;
56
- /** TTL for framework cache entries in milliseconds (default: 60000ms = 1 minute) */
57
- frameworkTTL: number;
58
- /** Maximum number of entries per cache (default: 1000) */
59
- maxEntries: number;
60
- }
61
-
62
- /**
63
- * Cache statistics for monitoring and debugging
64
- */
65
- export interface CacheStats {
66
- /** Number of analysis cache entries */
67
- analysisSize: number;
68
- /** Number of path cache entries */
69
- pathsSize: number;
70
- /** Number of framework cache entries */
71
- frameworksSize: number;
72
- /** Number of cache hits for analysis */
73
- analysisHits: number;
74
- /** Number of cache misses for analysis */
75
- analysisMisses: number;
76
- /** Number of cache hits for paths */
77
- pathHits: number;
78
- /** Number of cache misses for paths */
79
- pathMisses: number;
80
- /** Number of cache hits for frameworks */
81
- frameworkHits: number;
82
- /** Number of cache misses for frameworks */
83
- frameworkMisses: number;
84
- }
85
-
86
- /**
87
- * Island Render Cache interface
88
- * Centralized cache for all expensive operations in island rendering
89
- */
90
- export interface IslandRenderCache {
91
- /** Analysis results by component path */
92
- analysis: Map<string, AnalysisCacheEntry>;
93
- /** Resolved paths by source path */
94
- paths: Map<string, PathCacheEntry>;
95
- /** Framework detection results by source path */
96
- frameworks: Map<string, FrameworkCacheEntry>;
97
- /** Cache configuration */
98
- config: CacheConfig;
99
- /** Cache statistics */
100
- stats: CacheStats;
101
- }
102
-
103
- /**
104
- * Default cache configuration
105
- */
106
- const DEFAULT_CONFIG: CacheConfig = {
107
- analysisTTL: 60000, // 1 minute
108
- pathTTL: 60000, // 1 minute
109
- frameworkTTL: 60000, // 1 minute
110
- maxEntries: 1000,
111
- };
112
-
113
- /**
114
- * Initial cache statistics
115
- */
116
- const INITIAL_STATS: CacheStats = {
117
- analysisSize: 0,
118
- pathsSize: 0,
119
- frameworksSize: 0,
120
- analysisHits: 0,
121
- analysisMisses: 0,
122
- pathHits: 0,
123
- pathMisses: 0,
124
- frameworkHits: 0,
125
- frameworkMisses: 0,
126
- };
127
-
128
- /**
129
- * Global cache instance
130
- */
131
- const islandCache: IslandRenderCache = {
132
- analysis: new Map(),
133
- paths: new Map(),
134
- frameworks: new Map(),
135
- config: { ...DEFAULT_CONFIG },
136
- stats: { ...INITIAL_STATS },
137
- };
138
-
139
- /**
140
- * Check if we're in development mode
141
- */
142
- function isDev(): boolean {
143
- try {
144
- return process.env.NODE_ENV !== "production";
145
- } catch {
146
- return true; // Default to dev mode if we can't check
147
- }
148
- }
149
-
150
- function isVerbose(): boolean {
151
- try {
152
- return process.env.AVALON_VERBOSE === "1";
153
- } catch {
154
- return false;
155
- }
156
- }
157
-
158
- /**
159
- * Check if a cache entry is expired
160
- */
161
- function isExpired(timestamp: number, ttl: number): boolean {
162
- return Date.now() - timestamp > ttl;
163
- }
164
-
165
- /**
166
- * Enforce max entries limit by removing oldest entries
167
- */
168
- function enforceMaxEntries<T>(cache: Map<string, T & { timestamp: number }>, maxEntries: number): void {
169
- if (cache.size <= maxEntries) return;
170
-
171
- // Sort entries by timestamp and remove oldest
172
- const entries = Array.from(cache.entries())
173
- .sort((a, b) => a[1].timestamp - b[1].timestamp);
174
-
175
- const toRemove = entries.slice(0, cache.size - maxEntries);
176
- for (const [key] of toRemove) {
177
- cache.delete(key);
178
- }
179
- }
180
-
181
- // ============================================================================
182
- // Analysis Cache Functions
183
- // ============================================================================
184
-
185
- /**
186
- * Get cached analysis result for a component path
187
- *
188
- * @param src - The component source path
189
- * @returns The cached analysis result or null if not found/expired
190
- */
191
- export function getCachedAnalysis(src: string): AnalysisReport | null {
192
- const cached = islandCache.analysis.get(src);
193
-
194
- if (cached) {
195
- if (!isExpired(cached.timestamp, islandCache.config.analysisTTL)) {
196
- islandCache.stats.analysisHits++;
197
- return cached.result;
198
- }
199
- // Expired, remove from cache
200
- islandCache.analysis.delete(src);
201
- }
202
-
203
- islandCache.stats.analysisMisses++;
204
- return null;
205
- }
206
-
207
- /**
208
- * Store analysis result in cache
209
- *
210
- * @param src - The component source path
211
- * @param result - The analysis result to cache
212
- */
213
- export function setCachedAnalysis(src: string, result: AnalysisReport): void {
214
- islandCache.analysis.set(src, {
215
- result,
216
- timestamp: Date.now(),
217
- });
218
-
219
- islandCache.stats.analysisSize = islandCache.analysis.size;
220
- enforceMaxEntries(islandCache.analysis, islandCache.config.maxEntries);
221
- }
222
-
223
- // ============================================================================
224
- // Path Cache Functions
225
- // ============================================================================
226
-
227
- /**
228
- * Get cached resolved path for a source path
229
- *
230
- * @param src - The source path
231
- * @returns The cached resolved path or null if not found/expired
232
- */
233
- export function getCachedPath(src: string): string | null {
234
- const cached = islandCache.paths.get(src);
235
-
236
- if (cached) {
237
- if (!isExpired(cached.timestamp, islandCache.config.pathTTL)) {
238
- islandCache.stats.pathHits++;
239
- return cached.resolved;
240
- }
241
- // Expired, remove from cache
242
- islandCache.paths.delete(src);
243
- }
244
-
245
- islandCache.stats.pathMisses++;
246
- return null;
247
- }
248
-
249
- /**
250
- * Store resolved path in cache
251
- *
252
- * @param src - The source path
253
- * @param resolved - The resolved path to cache
254
- */
255
- export function setCachedPath(src: string, resolved: string): void {
256
- islandCache.paths.set(src, {
257
- resolved,
258
- timestamp: Date.now(),
259
- });
260
-
261
- islandCache.stats.pathsSize = islandCache.paths.size;
262
- enforceMaxEntries(islandCache.paths, islandCache.config.maxEntries);
263
- }
264
-
265
- // ============================================================================
266
- // Framework Cache Functions
267
- // ============================================================================
268
-
269
- /**
270
- * Get cached framework detection result for a source path
271
- *
272
- * @param src - The source path
273
- * @returns The cached framework or null if not found/expired
274
- */
275
- export function getCachedFramework(src: string): Framework | null {
276
- const cached = islandCache.frameworks.get(src);
277
-
278
- if (cached) {
279
- if (!isExpired(cached.timestamp, islandCache.config.frameworkTTL)) {
280
- islandCache.stats.frameworkHits++;
281
- return cached.framework;
282
- }
283
- // Expired, remove from cache
284
- islandCache.frameworks.delete(src);
285
- }
286
-
287
- islandCache.stats.frameworkMisses++;
288
- return null;
289
- }
290
-
291
- /**
292
- * Store framework detection result in cache
293
- *
294
- * @param src - The source path
295
- * @param framework - The detected framework to cache
296
- */
297
- export function setCachedFramework(src: string, framework: Framework): void {
298
- islandCache.frameworks.set(src, {
299
- framework,
300
- timestamp: Date.now(),
301
- });
302
-
303
- islandCache.stats.frameworksSize = islandCache.frameworks.size;
304
- enforceMaxEntries(islandCache.frameworks, islandCache.config.maxEntries);
305
- }
306
-
307
- // ============================================================================
308
- // Cache Management Functions
309
- // ============================================================================
310
-
311
- /**
312
- * Clear all caches
313
- * Useful for testing or when file structure changes
314
- */
315
- export function clearCache(): void {
316
- islandCache.analysis.clear();
317
- islandCache.paths.clear();
318
- islandCache.frameworks.clear();
319
-
320
- // Reset stats
321
- islandCache.stats = { ...INITIAL_STATS };
322
- }
323
-
324
- /**
325
- * Clear cache for a specific component path
326
- * Useful for HMR when a specific file changes
327
- *
328
- * @param src - The component source path to invalidate
329
- */
330
- export function invalidateCacheForPath(src: string): void {
331
- islandCache.analysis.delete(src);
332
- islandCache.paths.delete(src);
333
- islandCache.frameworks.delete(src);
334
-
335
- // Update sizes
336
- islandCache.stats.analysisSize = islandCache.analysis.size;
337
- islandCache.stats.pathsSize = islandCache.paths.size;
338
- islandCache.stats.frameworksSize = islandCache.frameworks.size;
339
- }
340
-
341
- /**
342
- * Configure cache settings
343
- *
344
- * @param config - Partial configuration to merge with defaults
345
- */
346
- export function configureCache(config: Partial<CacheConfig>): void {
347
- islandCache.config = {
348
- ...islandCache.config,
349
- ...config,
350
- };
351
- }
352
-
353
- /**
354
- * Get current cache configuration
355
- *
356
- * @returns The current cache configuration
357
- */
358
- export function getCacheConfig(): CacheConfig {
359
- return { ...islandCache.config };
360
- }
361
-
362
- /**
363
- * Get cache statistics for monitoring and debugging
364
- * Only logs in development mode
365
- *
366
- * @returns Current cache statistics
367
- */
368
- export function getCacheStats(): CacheStats {
369
- return {
370
- analysisSize: islandCache.analysis.size,
371
- pathsSize: islandCache.paths.size,
372
- frameworksSize: islandCache.frameworks.size,
373
- analysisHits: islandCache.stats.analysisHits,
374
- analysisMisses: islandCache.stats.analysisMisses,
375
- pathHits: islandCache.stats.pathHits,
376
- pathMisses: islandCache.stats.pathMisses,
377
- frameworkHits: islandCache.stats.frameworkHits,
378
- frameworkMisses: islandCache.stats.frameworkMisses,
379
- };
380
- }
381
-
382
- /**
383
- * Log cache statistics to console (dev mode only)
384
- * Useful for debugging and performance monitoring
385
- */
386
- export function logCacheStats(): void {
387
- if (!isDev() || !isVerbose()) return;
388
-
389
- const stats = getCacheStats();
390
- const totalHits = stats.analysisHits + stats.pathHits + stats.frameworkHits;
391
- const totalMisses = stats.analysisMisses + stats.pathMisses + stats.frameworkMisses;
392
- const hitRate = totalHits + totalMisses > 0
393
- ? ((totalHits / (totalHits + totalMisses)) * 100).toFixed(1)
394
- : "0.0";
395
-
396
- console.log("📊 Island Render Cache Stats:");
397
- console.log(` Analysis: ${stats.analysisSize} entries (${stats.analysisHits} hits / ${stats.analysisMisses} misses)`);
398
- console.log(` Paths: ${stats.pathsSize} entries (${stats.pathHits} hits / ${stats.pathMisses} misses)`);
399
- console.log(` Frameworks: ${stats.frameworksSize} entries (${stats.frameworkHits} hits / ${stats.frameworkMisses} misses)`);
400
- console.log(` Overall hit rate: ${hitRate}%`);
401
- }
402
-
403
- /**
404
- * Get the raw cache instance (for testing purposes only)
405
- * @internal
406
- */
407
- export function _getCache(): IslandRenderCache {
408
- return islandCache;
409
- }
410
-
411
- // ============================================================================
412
- // File Watcher Integration Functions
413
- // ============================================================================
414
-
415
- /**
416
- * Normalize a file path for cache key matching
417
- * Handles various path formats from Vite file watcher
418
- *
419
- * @param filePath - The file path to normalize
420
- * @returns Normalized path for cache key matching
421
- */
422
- function normalizePathForCache(filePath: string): string {
423
- return filePath
424
- .replace(/\\/g, '/') // Normalize Windows paths
425
- .replace(/^\//, '') // Remove leading slash
426
- .replace(/\?.*$/, '') // Remove query strings
427
- .replace(/#.*$/, ''); // Remove hash fragments
428
- }
429
-
430
- /**
431
- * Check if a file path matches any cached entry
432
- * Uses partial matching to handle different path formats
433
- *
434
- * @param filePath - The file path to check
435
- * @param cacheKey - The cache key to match against
436
- * @returns True if the paths match
437
- */
438
- function pathMatchesCacheKey(filePath: string, cacheKey: string): boolean {
439
- const normalizedFile = normalizePathForCache(filePath);
440
- const normalizedKey = normalizePathForCache(cacheKey);
441
-
442
- // Exact match
443
- if (normalizedFile === normalizedKey) return true;
444
-
445
- // Partial match (file path ends with cache key or vice versa)
446
- if (normalizedFile.endsWith(normalizedKey) || normalizedKey.endsWith(normalizedFile)) return true;
447
-
448
- // Filename match (for cases where full paths differ)
449
- const fileName = normalizedFile.split('/').pop();
450
- const keyFileName = normalizedKey.split('/').pop();
451
- if (fileName && keyFileName && fileName === keyFileName) return true;
452
-
453
- return false;
454
- }
455
-
456
- /**
457
- * Invalidate cache entries for a specific file path
458
- * Called when a component file changes during development
459
- *
460
- * @param filePath - The path of the changed file
461
- * @returns Number of cache entries invalidated
462
- */
463
- export function invalidateCacheForFile(filePath: string): number {
464
- let invalidatedCount = 0;
465
-
466
- // Find and remove matching entries from analysis cache
467
- for (const key of islandCache.analysis.keys()) {
468
- if (pathMatchesCacheKey(filePath, key)) {
469
- islandCache.analysis.delete(key);
470
- invalidatedCount++;
471
- if (isVerbose()) {
472
- console.log(`🗑️ [Cache] Invalidated analysis cache for: ${key}`);
473
- }
474
- }
475
- }
476
-
477
- // Find and remove matching entries from paths cache
478
- for (const key of islandCache.paths.keys()) {
479
- if (pathMatchesCacheKey(filePath, key)) {
480
- islandCache.paths.delete(key);
481
- invalidatedCount++;
482
- if (isVerbose()) {
483
- console.log(`🗑️ [Cache] Invalidated path cache for: ${key}`);
484
- }
485
- }
486
- }
487
-
488
- // Find and remove matching entries from frameworks cache
489
- for (const key of islandCache.frameworks.keys()) {
490
- if (pathMatchesCacheKey(filePath, key)) {
491
- islandCache.frameworks.delete(key);
492
- invalidatedCount++;
493
- if (isVerbose()) {
494
- console.log(`🗑️ [Cache] Invalidated framework cache for: ${key}`);
495
- }
496
- }
497
- }
498
-
499
- // Update stats
500
- islandCache.stats.analysisSize = islandCache.analysis.size;
501
- islandCache.stats.pathsSize = islandCache.paths.size;
502
- islandCache.stats.frameworksSize = islandCache.frameworks.size;
503
-
504
- return invalidatedCount;
505
- }
506
-
507
- /**
508
- * Check if a file path is an island component file
509
- * Used to determine if cache invalidation is needed
510
- *
511
- * @param filePath - The file path to check
512
- * @returns True if the file is an island component
513
- */
514
- export function isIslandComponentFile(filePath: string): boolean {
515
- const normalized = normalizePathForCache(filePath);
516
-
517
- // Check if it's in an islands directory
518
- if (normalized.includes('/islands/') || normalized.includes('\\islands\\')) {
519
- return true;
520
- }
521
-
522
- // Check for common component file extensions
523
- const componentExtensions = ['.tsx', '.jsx', '.vue', '.svelte', '.solid.tsx', '.lit.ts'];
524
- return componentExtensions.some(ext => normalized.endsWith(ext));
525
- }
526
-
527
- /**
528
- * Clear all caches when file structure changes significantly
529
- * Called when files are added or removed
530
- */
531
- export function clearPathCacheOnStructureChange(): void {
532
- islandCache.paths.clear();
533
- islandCache.stats.pathsSize = 0;
534
-
535
- if (isVerbose()) {
536
- console.log('🗑️ [Cache] Cleared path cache due to file structure change');
537
- }
538
- }
539
-
540
- /**
541
- * Alias for clearCache - provides a more descriptive name for external use
542
- * Clears all island render caches
543
- */
544
- export function clearIslandCache(): void {
545
- clearCache();
546
-
547
- if (isVerbose()) {
548
- console.log('🗑️ [Cache] Cleared all island render caches');
549
- }
550
- }
1
+ /**
2
+ * Island Render Cache Module
3
+ *
4
+ * Provides caching for expensive operations in the island rendering pipeline:
5
+ * - Component analysis results
6
+ * - Resolved paths
7
+ * - Framework detection results
8
+ *
9
+ * This module helps optimize island rendering by avoiding repeated file I/O
10
+ * and analysis operations for the same components.
11
+ *
12
+ * @module render-cache
13
+ */
14
+
15
+ import type { AnalysisReport } from "../core/components/component-analyzer.ts";
16
+ import type { Framework } from "./types.ts";
17
+
18
+ /**
19
+ * Cache entry for component analysis results
20
+ */
21
+ export interface AnalysisCacheEntry {
22
+ /** The analysis result */
23
+ result: AnalysisReport;
24
+ /** Timestamp when the entry was cached */
25
+ timestamp: number;
26
+ }
27
+
28
+ /**
29
+ * Cache entry for resolved paths
30
+ */
31
+ export interface PathCacheEntry {
32
+ /** The resolved path */
33
+ resolved: string;
34
+ /** Timestamp when the entry was cached */
35
+ timestamp: number;
36
+ }
37
+
38
+ /**
39
+ * Cache entry for framework detection results
40
+ */
41
+ export interface FrameworkCacheEntry {
42
+ /** The detected framework */
43
+ framework: Framework;
44
+ /** Timestamp when the entry was cached */
45
+ timestamp: number;
46
+ }
47
+
48
+ /**
49
+ * Configuration options for the island render cache
50
+ */
51
+ export interface CacheConfig {
52
+ /** TTL for analysis cache entries in milliseconds (default: 60000ms = 1 minute) */
53
+ analysisTTL: number;
54
+ /** TTL for path cache entries in milliseconds (default: 60000ms = 1 minute) */
55
+ pathTTL: number;
56
+ /** TTL for framework cache entries in milliseconds (default: 60000ms = 1 minute) */
57
+ frameworkTTL: number;
58
+ /** Maximum number of entries per cache (default: 1000) */
59
+ maxEntries: number;
60
+ }
61
+
62
+ /**
63
+ * Cache statistics for monitoring and debugging
64
+ */
65
+ export interface CacheStats {
66
+ /** Number of analysis cache entries */
67
+ analysisSize: number;
68
+ /** Number of path cache entries */
69
+ pathsSize: number;
70
+ /** Number of framework cache entries */
71
+ frameworksSize: number;
72
+ /** Number of cache hits for analysis */
73
+ analysisHits: number;
74
+ /** Number of cache misses for analysis */
75
+ analysisMisses: number;
76
+ /** Number of cache hits for paths */
77
+ pathHits: number;
78
+ /** Number of cache misses for paths */
79
+ pathMisses: number;
80
+ /** Number of cache hits for frameworks */
81
+ frameworkHits: number;
82
+ /** Number of cache misses for frameworks */
83
+ frameworkMisses: number;
84
+ }
85
+
86
+ /**
87
+ * Island Render Cache interface
88
+ * Centralized cache for all expensive operations in island rendering
89
+ */
90
+ export interface IslandRenderCache {
91
+ /** Analysis results by component path */
92
+ analysis: Map<string, AnalysisCacheEntry>;
93
+ /** Resolved paths by source path */
94
+ paths: Map<string, PathCacheEntry>;
95
+ /** Framework detection results by source path */
96
+ frameworks: Map<string, FrameworkCacheEntry>;
97
+ /** Cache configuration */
98
+ config: CacheConfig;
99
+ /** Cache statistics */
100
+ stats: CacheStats;
101
+ }
102
+
103
+ /**
104
+ * Default cache configuration
105
+ */
106
+ const DEFAULT_CONFIG: CacheConfig = {
107
+ analysisTTL: 60000, // 1 minute
108
+ pathTTL: 60000, // 1 minute
109
+ frameworkTTL: 60000, // 1 minute
110
+ maxEntries: 1000,
111
+ };
112
+
113
+ /**
114
+ * Initial cache statistics
115
+ */
116
+ const INITIAL_STATS: CacheStats = {
117
+ analysisSize: 0,
118
+ pathsSize: 0,
119
+ frameworksSize: 0,
120
+ analysisHits: 0,
121
+ analysisMisses: 0,
122
+ pathHits: 0,
123
+ pathMisses: 0,
124
+ frameworkHits: 0,
125
+ frameworkMisses: 0,
126
+ };
127
+
128
+ /**
129
+ * Global cache instance
130
+ */
131
+ const islandCache: IslandRenderCache = {
132
+ analysis: new Map(),
133
+ paths: new Map(),
134
+ frameworks: new Map(),
135
+ config: { ...DEFAULT_CONFIG },
136
+ stats: { ...INITIAL_STATS },
137
+ };
138
+
139
+ /**
140
+ * Check if we're in development mode
141
+ */
142
+ function isDev(): boolean {
143
+ try {
144
+ return process.env.NODE_ENV !== "production";
145
+ } catch {
146
+ return true; // Default to dev mode if we can't check
147
+ }
148
+ }
149
+
150
+ function isVerbose(): boolean {
151
+ try {
152
+ return process.env.AVALON_VERBOSE === "1";
153
+ } catch {
154
+ return false;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Check if a cache entry is expired
160
+ */
161
+ function isExpired(timestamp: number, ttl: number): boolean {
162
+ return Date.now() - timestamp > ttl;
163
+ }
164
+
165
+ /**
166
+ * Enforce max entries limit by removing oldest entries
167
+ */
168
+ function enforceMaxEntries<T>(cache: Map<string, T & { timestamp: number }>, maxEntries: number): void {
169
+ if (cache.size <= maxEntries) return;
170
+
171
+ // Sort entries by timestamp and remove oldest
172
+ const entries = Array.from(cache.entries())
173
+ .sort((a, b) => a[1].timestamp - b[1].timestamp);
174
+
175
+ const toRemove = entries.slice(0, cache.size - maxEntries);
176
+ for (const [key] of toRemove) {
177
+ cache.delete(key);
178
+ }
179
+ }
180
+
181
+ // ============================================================================
182
+ // Analysis Cache Functions
183
+ // ============================================================================
184
+
185
+ /**
186
+ * Get cached analysis result for a component path
187
+ *
188
+ * @param src - The component source path
189
+ * @returns The cached analysis result or null if not found/expired
190
+ */
191
+ export function getCachedAnalysis(src: string): AnalysisReport | null {
192
+ const cached = islandCache.analysis.get(src);
193
+
194
+ if (cached) {
195
+ if (!isExpired(cached.timestamp, islandCache.config.analysisTTL)) {
196
+ islandCache.stats.analysisHits++;
197
+ return cached.result;
198
+ }
199
+ // Expired, remove from cache
200
+ islandCache.analysis.delete(src);
201
+ }
202
+
203
+ islandCache.stats.analysisMisses++;
204
+ return null;
205
+ }
206
+
207
+ /**
208
+ * Store analysis result in cache
209
+ *
210
+ * @param src - The component source path
211
+ * @param result - The analysis result to cache
212
+ */
213
+ export function setCachedAnalysis(src: string, result: AnalysisReport): void {
214
+ islandCache.analysis.set(src, {
215
+ result,
216
+ timestamp: Date.now(),
217
+ });
218
+
219
+ islandCache.stats.analysisSize = islandCache.analysis.size;
220
+ enforceMaxEntries(islandCache.analysis, islandCache.config.maxEntries);
221
+ }
222
+
223
+ // ============================================================================
224
+ // Path Cache Functions
225
+ // ============================================================================
226
+
227
+ /**
228
+ * Get cached resolved path for a source path
229
+ *
230
+ * @param src - The source path
231
+ * @returns The cached resolved path or null if not found/expired
232
+ */
233
+ export function getCachedPath(src: string): string | null {
234
+ const cached = islandCache.paths.get(src);
235
+
236
+ if (cached) {
237
+ if (!isExpired(cached.timestamp, islandCache.config.pathTTL)) {
238
+ islandCache.stats.pathHits++;
239
+ return cached.resolved;
240
+ }
241
+ // Expired, remove from cache
242
+ islandCache.paths.delete(src);
243
+ }
244
+
245
+ islandCache.stats.pathMisses++;
246
+ return null;
247
+ }
248
+
249
+ /**
250
+ * Store resolved path in cache
251
+ *
252
+ * @param src - The source path
253
+ * @param resolved - The resolved path to cache
254
+ */
255
+ export function setCachedPath(src: string, resolved: string): void {
256
+ islandCache.paths.set(src, {
257
+ resolved,
258
+ timestamp: Date.now(),
259
+ });
260
+
261
+ islandCache.stats.pathsSize = islandCache.paths.size;
262
+ enforceMaxEntries(islandCache.paths, islandCache.config.maxEntries);
263
+ }
264
+
265
+ // ============================================================================
266
+ // Framework Cache Functions
267
+ // ============================================================================
268
+
269
+ /**
270
+ * Get cached framework detection result for a source path
271
+ *
272
+ * @param src - The source path
273
+ * @returns The cached framework or null if not found/expired
274
+ */
275
+ export function getCachedFramework(src: string): Framework | null {
276
+ const cached = islandCache.frameworks.get(src);
277
+
278
+ if (cached) {
279
+ if (!isExpired(cached.timestamp, islandCache.config.frameworkTTL)) {
280
+ islandCache.stats.frameworkHits++;
281
+ return cached.framework;
282
+ }
283
+ // Expired, remove from cache
284
+ islandCache.frameworks.delete(src);
285
+ }
286
+
287
+ islandCache.stats.frameworkMisses++;
288
+ return null;
289
+ }
290
+
291
+ /**
292
+ * Store framework detection result in cache
293
+ *
294
+ * @param src - The source path
295
+ * @param framework - The detected framework to cache
296
+ */
297
+ export function setCachedFramework(src: string, framework: Framework): void {
298
+ islandCache.frameworks.set(src, {
299
+ framework,
300
+ timestamp: Date.now(),
301
+ });
302
+
303
+ islandCache.stats.frameworksSize = islandCache.frameworks.size;
304
+ enforceMaxEntries(islandCache.frameworks, islandCache.config.maxEntries);
305
+ }
306
+
307
+ // ============================================================================
308
+ // Cache Management Functions
309
+ // ============================================================================
310
+
311
+ /**
312
+ * Clear all caches
313
+ * Useful for testing or when file structure changes
314
+ */
315
+ export function clearCache(): void {
316
+ islandCache.analysis.clear();
317
+ islandCache.paths.clear();
318
+ islandCache.frameworks.clear();
319
+
320
+ // Reset stats
321
+ islandCache.stats = { ...INITIAL_STATS };
322
+ }
323
+
324
+ /**
325
+ * Clear cache for a specific component path
326
+ * Useful for HMR when a specific file changes
327
+ *
328
+ * @param src - The component source path to invalidate
329
+ */
330
+ export function invalidateCacheForPath(src: string): void {
331
+ islandCache.analysis.delete(src);
332
+ islandCache.paths.delete(src);
333
+ islandCache.frameworks.delete(src);
334
+
335
+ // Update sizes
336
+ islandCache.stats.analysisSize = islandCache.analysis.size;
337
+ islandCache.stats.pathsSize = islandCache.paths.size;
338
+ islandCache.stats.frameworksSize = islandCache.frameworks.size;
339
+ }
340
+
341
+ /**
342
+ * Configure cache settings
343
+ *
344
+ * @param config - Partial configuration to merge with defaults
345
+ */
346
+ export function configureCache(config: Partial<CacheConfig>): void {
347
+ islandCache.config = {
348
+ ...islandCache.config,
349
+ ...config,
350
+ };
351
+ }
352
+
353
+ /**
354
+ * Get current cache configuration
355
+ *
356
+ * @returns The current cache configuration
357
+ */
358
+ export function getCacheConfig(): CacheConfig {
359
+ return { ...islandCache.config };
360
+ }
361
+
362
+ /**
363
+ * Get cache statistics for monitoring and debugging
364
+ * Only logs in development mode
365
+ *
366
+ * @returns Current cache statistics
367
+ */
368
+ export function getCacheStats(): CacheStats {
369
+ return {
370
+ analysisSize: islandCache.analysis.size,
371
+ pathsSize: islandCache.paths.size,
372
+ frameworksSize: islandCache.frameworks.size,
373
+ analysisHits: islandCache.stats.analysisHits,
374
+ analysisMisses: islandCache.stats.analysisMisses,
375
+ pathHits: islandCache.stats.pathHits,
376
+ pathMisses: islandCache.stats.pathMisses,
377
+ frameworkHits: islandCache.stats.frameworkHits,
378
+ frameworkMisses: islandCache.stats.frameworkMisses,
379
+ };
380
+ }
381
+
382
+ /**
383
+ * Log cache statistics to console (dev mode only)
384
+ * Useful for debugging and performance monitoring
385
+ */
386
+ export function logCacheStats(): void {
387
+ if (!isDev() || !isVerbose()) return;
388
+
389
+ const stats = getCacheStats();
390
+ const totalHits = stats.analysisHits + stats.pathHits + stats.frameworkHits;
391
+ const totalMisses = stats.analysisMisses + stats.pathMisses + stats.frameworkMisses;
392
+ const hitRate = totalHits + totalMisses > 0
393
+ ? ((totalHits / (totalHits + totalMisses)) * 100).toFixed(1)
394
+ : "0.0";
395
+
396
+ console.log("📊 Island Render Cache Stats:");
397
+ console.log(` Analysis: ${stats.analysisSize} entries (${stats.analysisHits} hits / ${stats.analysisMisses} misses)`);
398
+ console.log(` Paths: ${stats.pathsSize} entries (${stats.pathHits} hits / ${stats.pathMisses} misses)`);
399
+ console.log(` Frameworks: ${stats.frameworksSize} entries (${stats.frameworkHits} hits / ${stats.frameworkMisses} misses)`);
400
+ console.log(` Overall hit rate: ${hitRate}%`);
401
+ }
402
+
403
+ /**
404
+ * Get the raw cache instance (for testing purposes only)
405
+ * @internal
406
+ */
407
+ export function _getCache(): IslandRenderCache {
408
+ return islandCache;
409
+ }
410
+
411
+ // ============================================================================
412
+ // File Watcher Integration Functions
413
+ // ============================================================================
414
+
415
+ /**
416
+ * Normalize a file path for cache key matching
417
+ * Handles various path formats from Vite file watcher
418
+ *
419
+ * @param filePath - The file path to normalize
420
+ * @returns Normalized path for cache key matching
421
+ */
422
+ function normalizePathForCache(filePath: string): string {
423
+ return filePath
424
+ .replace(/\\/g, '/') // Normalize Windows paths
425
+ .replace(/^\//, '') // Remove leading slash
426
+ .replace(/\?.*$/, '') // Remove query strings
427
+ .replace(/#.*$/, ''); // Remove hash fragments
428
+ }
429
+
430
+ /**
431
+ * Check if a file path matches any cached entry
432
+ * Uses partial matching to handle different path formats
433
+ *
434
+ * @param filePath - The file path to check
435
+ * @param cacheKey - The cache key to match against
436
+ * @returns True if the paths match
437
+ */
438
+ function pathMatchesCacheKey(filePath: string, cacheKey: string): boolean {
439
+ const normalizedFile = normalizePathForCache(filePath);
440
+ const normalizedKey = normalizePathForCache(cacheKey);
441
+
442
+ // Exact match
443
+ if (normalizedFile === normalizedKey) return true;
444
+
445
+ // Partial match (file path ends with cache key or vice versa)
446
+ if (normalizedFile.endsWith(normalizedKey) || normalizedKey.endsWith(normalizedFile)) return true;
447
+
448
+ // Filename match (for cases where full paths differ)
449
+ const fileName = normalizedFile.split('/').pop();
450
+ const keyFileName = normalizedKey.split('/').pop();
451
+ if (fileName && keyFileName && fileName === keyFileName) return true;
452
+
453
+ return false;
454
+ }
455
+
456
+ /**
457
+ * Invalidate cache entries for a specific file path
458
+ * Called when a component file changes during development
459
+ *
460
+ * @param filePath - The path of the changed file
461
+ * @returns Number of cache entries invalidated
462
+ */
463
+ export function invalidateCacheForFile(filePath: string): number {
464
+ let invalidatedCount = 0;
465
+
466
+ // Find and remove matching entries from analysis cache
467
+ for (const key of islandCache.analysis.keys()) {
468
+ if (pathMatchesCacheKey(filePath, key)) {
469
+ islandCache.analysis.delete(key);
470
+ invalidatedCount++;
471
+ if (isVerbose()) {
472
+ console.log(`🗑️ [Cache] Invalidated analysis cache for: ${key}`);
473
+ }
474
+ }
475
+ }
476
+
477
+ // Find and remove matching entries from paths cache
478
+ for (const key of islandCache.paths.keys()) {
479
+ if (pathMatchesCacheKey(filePath, key)) {
480
+ islandCache.paths.delete(key);
481
+ invalidatedCount++;
482
+ if (isVerbose()) {
483
+ console.log(`🗑️ [Cache] Invalidated path cache for: ${key}`);
484
+ }
485
+ }
486
+ }
487
+
488
+ // Find and remove matching entries from frameworks cache
489
+ for (const key of islandCache.frameworks.keys()) {
490
+ if (pathMatchesCacheKey(filePath, key)) {
491
+ islandCache.frameworks.delete(key);
492
+ invalidatedCount++;
493
+ if (isVerbose()) {
494
+ console.log(`🗑️ [Cache] Invalidated framework cache for: ${key}`);
495
+ }
496
+ }
497
+ }
498
+
499
+ // Update stats
500
+ islandCache.stats.analysisSize = islandCache.analysis.size;
501
+ islandCache.stats.pathsSize = islandCache.paths.size;
502
+ islandCache.stats.frameworksSize = islandCache.frameworks.size;
503
+
504
+ return invalidatedCount;
505
+ }
506
+
507
+ /**
508
+ * Check if a file path is an island component file
509
+ * Used to determine if cache invalidation is needed
510
+ *
511
+ * @param filePath - The file path to check
512
+ * @returns True if the file is an island component
513
+ */
514
+ export function isIslandComponentFile(filePath: string): boolean {
515
+ const normalized = normalizePathForCache(filePath);
516
+
517
+ // Check if it's in an islands directory
518
+ if (normalized.includes('/islands/') || normalized.includes('\\islands\\')) {
519
+ return true;
520
+ }
521
+
522
+ // Check for common component file extensions
523
+ const componentExtensions = ['.tsx', '.jsx', '.vue', '.svelte', '.solid.tsx', '.lit.ts'];
524
+ return componentExtensions.some(ext => normalized.endsWith(ext));
525
+ }
526
+
527
+ /**
528
+ * Clear all caches when file structure changes significantly
529
+ * Called when files are added or removed
530
+ */
531
+ export function clearPathCacheOnStructureChange(): void {
532
+ islandCache.paths.clear();
533
+ islandCache.stats.pathsSize = 0;
534
+
535
+ if (isVerbose()) {
536
+ console.log('🗑️ [Cache] Cleared path cache due to file structure change');
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Alias for clearCache - provides a more descriptive name for external use
542
+ * Clears all island render caches
543
+ */
544
+ export function clearIslandCache(): void {
545
+ clearCache();
546
+
547
+ if (isVerbose()) {
548
+ console.log('🗑️ [Cache] Cleared all island render caches');
549
+ }
550
+ }