@useavalon/avalon 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +54 -0
  2. package/mod.ts +301 -0
  3. package/package.json +85 -0
  4. package/src/build/README.md +310 -0
  5. package/src/build/integration-bundler-plugin.ts +116 -0
  6. package/src/build/integration-config.ts +168 -0
  7. package/src/build/integration-detection-plugin.ts +117 -0
  8. package/src/build/integration-resolver-plugin.ts +90 -0
  9. package/src/build/island-manifest.ts +269 -0
  10. package/src/build/island-types-generator.ts +476 -0
  11. package/src/build/mdx-island-transform.ts +464 -0
  12. package/src/build/mdx-plugin.ts +98 -0
  13. package/src/build/page-island-transform.ts +598 -0
  14. package/src/build/prop-extractors/index.ts +21 -0
  15. package/src/build/prop-extractors/lit.ts +140 -0
  16. package/src/build/prop-extractors/qwik.ts +16 -0
  17. package/src/build/prop-extractors/solid.ts +125 -0
  18. package/src/build/prop-extractors/svelte.ts +194 -0
  19. package/src/build/prop-extractors/vue.ts +111 -0
  20. package/src/build/sidecar-file-manager.ts +104 -0
  21. package/src/build/sidecar-renderer.ts +30 -0
  22. package/src/client/adapters/index.ts +13 -0
  23. package/src/client/adapters/lit-adapter.ts +654 -0
  24. package/src/client/adapters/preact-adapter.ts +331 -0
  25. package/src/client/adapters/qwik-adapter.ts +345 -0
  26. package/src/client/adapters/react-adapter.ts +353 -0
  27. package/src/client/adapters/solid-adapter.ts +451 -0
  28. package/src/client/adapters/svelte-adapter.ts +524 -0
  29. package/src/client/adapters/vue-adapter.ts +467 -0
  30. package/src/client/components.ts +35 -0
  31. package/src/client/css-hmr-handler.ts +344 -0
  32. package/src/client/framework-adapter.ts +462 -0
  33. package/src/client/hmr-coordinator.ts +396 -0
  34. package/src/client/hmr-error-overlay.js +533 -0
  35. package/src/client/main.js +816 -0
  36. package/src/client/tests/css-hmr-handler.test.ts +360 -0
  37. package/src/client/tests/framework-adapter.test.ts +519 -0
  38. package/src/client/tests/hmr-coordinator.test.ts +176 -0
  39. package/src/client/tests/hydration-option-parsing.test.ts +107 -0
  40. package/src/client/tests/lit-adapter.test.ts +427 -0
  41. package/src/client/tests/preact-adapter.test.ts +353 -0
  42. package/src/client/tests/qwik-adapter.test.ts +343 -0
  43. package/src/client/tests/react-adapter.test.ts +317 -0
  44. package/src/client/tests/solid-adapter.test.ts +396 -0
  45. package/src/client/tests/svelte-adapter.test.ts +387 -0
  46. package/src/client/tests/vue-adapter.test.ts +407 -0
  47. package/src/client/types/framework-runtime.d.ts +68 -0
  48. package/src/client/types/vite-hmr.d.ts +46 -0
  49. package/src/client/types/vite-virtual-modules.d.ts +60 -0
  50. package/src/components/Image.tsx +123 -0
  51. package/src/components/IslandErrorBoundary.tsx +145 -0
  52. package/src/components/LayoutDataErrorBoundary.tsx +141 -0
  53. package/src/components/LayoutErrorBoundary.tsx +127 -0
  54. package/src/components/PersistentIsland.tsx +52 -0
  55. package/src/components/StreamingErrorBoundary.tsx +233 -0
  56. package/src/components/StreamingLayout.tsx +538 -0
  57. package/src/components/tests/component-analyzer.test.ts +96 -0
  58. package/src/components/tests/component-detection.test.ts +347 -0
  59. package/src/components/tests/persistent-islands.test.ts +398 -0
  60. package/src/core/components/component-analyzer.ts +192 -0
  61. package/src/core/components/component-detection.ts +508 -0
  62. package/src/core/components/enhanced-framework-detector.ts +500 -0
  63. package/src/core/components/framework-registry.ts +563 -0
  64. package/src/core/components/tests/enhanced-framework-detector.test.ts +577 -0
  65. package/src/core/components/tests/framework-registry.test.ts +465 -0
  66. package/src/core/content/mdx-processor.ts +46 -0
  67. package/src/core/integrations/README.md +282 -0
  68. package/src/core/integrations/index.ts +19 -0
  69. package/src/core/integrations/loader.ts +125 -0
  70. package/src/core/integrations/registry.ts +195 -0
  71. package/src/core/islands/island-persistence.ts +325 -0
  72. package/src/core/islands/island-state-serializer.ts +258 -0
  73. package/src/core/islands/persistent-island-context.tsx +80 -0
  74. package/src/core/islands/use-persistent-state.ts +68 -0
  75. package/src/core/layout/enhanced-layout-resolver.ts +322 -0
  76. package/src/core/layout/layout-cache-manager.ts +485 -0
  77. package/src/core/layout/layout-composer.ts +357 -0
  78. package/src/core/layout/layout-data-loader.ts +516 -0
  79. package/src/core/layout/layout-discovery.ts +243 -0
  80. package/src/core/layout/layout-matcher.ts +299 -0
  81. package/src/core/layout/layout-types.ts +110 -0
  82. package/src/core/layout/tests/enhanced-layout-resolver.test.ts +477 -0
  83. package/src/core/layout/tests/layout-cache-optimization.test.ts +149 -0
  84. package/src/core/layout/tests/layout-composer.test.ts +486 -0
  85. package/src/core/layout/tests/layout-data-loader.test.ts +443 -0
  86. package/src/core/layout/tests/layout-discovery.test.ts +253 -0
  87. package/src/core/layout/tests/layout-matcher.test.ts +480 -0
  88. package/src/core/modules/framework-module-resolver.ts +273 -0
  89. package/src/core/modules/tests/framework-module-resolver.test.ts +263 -0
  90. package/src/core/modules/tests/module-resolution-integration.test.ts +117 -0
  91. package/src/islands/component-analysis.ts +213 -0
  92. package/src/islands/css-utils.ts +565 -0
  93. package/src/islands/discovery/index.ts +80 -0
  94. package/src/islands/discovery/registry.ts +340 -0
  95. package/src/islands/discovery/resolver.ts +477 -0
  96. package/src/islands/discovery/scanner.ts +386 -0
  97. package/src/islands/discovery/tests/island-discovery.test.ts +881 -0
  98. package/src/islands/discovery/types.ts +117 -0
  99. package/src/islands/discovery/validator.ts +544 -0
  100. package/src/islands/discovery/watcher.ts +368 -0
  101. package/src/islands/framework-detection.ts +428 -0
  102. package/src/islands/integration-loader.ts +490 -0
  103. package/src/islands/island.tsx +565 -0
  104. package/src/islands/render-cache.ts +550 -0
  105. package/src/islands/types.ts +80 -0
  106. package/src/islands/universal-css-collector.ts +157 -0
  107. package/src/islands/universal-head-collector.ts +137 -0
  108. package/src/layout-system.d.ts +592 -0
  109. package/src/layout-system.ts +218 -0
  110. package/src/middleware/__tests__/discovery.test.ts +107 -0
  111. package/src/middleware/discovery.ts +268 -0
  112. package/src/middleware/executor.ts +315 -0
  113. package/src/middleware/index.ts +76 -0
  114. package/src/middleware/types.ts +99 -0
  115. package/src/nitro/build-config.ts +576 -0
  116. package/src/nitro/config.ts +483 -0
  117. package/src/nitro/error-handler.ts +636 -0
  118. package/src/nitro/index.ts +173 -0
  119. package/src/nitro/island-manifest.ts +584 -0
  120. package/src/nitro/middleware-adapter.ts +260 -0
  121. package/src/nitro/renderer.ts +1458 -0
  122. package/src/nitro/route-discovery.ts +439 -0
  123. package/src/nitro/types.ts +321 -0
  124. package/src/render/collect-css.ts +198 -0
  125. package/src/render/error-pages.ts +79 -0
  126. package/src/render/isolated-ssr-renderer.ts +654 -0
  127. package/src/render/ssr.ts +1030 -0
  128. package/src/schemas/api.ts +30 -0
  129. package/src/schemas/core.ts +64 -0
  130. package/src/schemas/index.ts +212 -0
  131. package/src/schemas/layout.ts +279 -0
  132. package/src/schemas/routing/index.ts +38 -0
  133. package/src/schemas/routing.ts +376 -0
  134. package/src/types/as-island.ts +20 -0
  135. package/src/types/image.d.ts +106 -0
  136. package/src/types/index.d.ts +22 -0
  137. package/src/types/island-jsx.d.ts +33 -0
  138. package/src/types/island-prop.d.ts +20 -0
  139. package/src/types/layout.ts +285 -0
  140. package/src/types/mdx.d.ts +6 -0
  141. package/src/types/routing.ts +555 -0
  142. package/src/types/tests/layout-types.test.ts +197 -0
  143. package/src/types/types.ts +5 -0
  144. package/src/types/urlpattern.d.ts +49 -0
  145. package/src/types/vite-env.d.ts +11 -0
  146. package/src/utils/dev-logger.ts +299 -0
  147. package/src/utils/fs.ts +151 -0
  148. package/src/vite-plugin/auto-discover.ts +551 -0
  149. package/src/vite-plugin/config.ts +266 -0
  150. package/src/vite-plugin/errors.ts +127 -0
  151. package/src/vite-plugin/image-optimization.ts +151 -0
  152. package/src/vite-plugin/integration-activator.ts +126 -0
  153. package/src/vite-plugin/island-sidecar-plugin.ts +176 -0
  154. package/src/vite-plugin/module-discovery.ts +189 -0
  155. package/src/vite-plugin/nitro-integration.ts +1334 -0
  156. package/src/vite-plugin/plugin.ts +329 -0
  157. package/src/vite-plugin/tests/image-optimization.test.ts +54 -0
  158. package/src/vite-plugin/types.ts +327 -0
  159. package/src/vite-plugin/validation.ts +228 -0
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Framework-aware module resolver for handling framework-specific path transformations
3
+ * and MIME type resolution during hydration and module serving.
4
+ */
5
+
6
+ export interface ModuleResolutionConfig {
7
+ framework: string;
8
+ baseUrl: string;
9
+ extensions: string[];
10
+ transformPath: (path: string) => string;
11
+ mimeType: string;
12
+ }
13
+
14
+ export interface ResolvedModule {
15
+ originalPath: string;
16
+ resolvedPath: string;
17
+ framework: string;
18
+ shouldTransform: boolean;
19
+ mimeType: string;
20
+ url: string;
21
+ }
22
+
23
+ export interface FrameworkModuleConfig {
24
+ extensions: string[];
25
+ hydrationExtension: string;
26
+ mimeType: string;
27
+ transformPath: (path: string, mode: 'development' | 'production') => string;
28
+ }
29
+
30
+ /**
31
+ * Framework-specific module configurations
32
+ */
33
+ const FRAMEWORK_MODULE_CONFIGS: Record<string, FrameworkModuleConfig> = {
34
+ solid: {
35
+ extensions: ['.tsx', '.jsx'],
36
+ hydrationExtension: '.js',
37
+ mimeType: 'application/javascript',
38
+ transformPath: (path: string, mode: 'development' | 'production') => {
39
+ // For Solid, transform .tsx to .js for hydration
40
+ if (path.endsWith('.tsx')) {
41
+ return path.replace(/\.tsx$/, '.js');
42
+ }
43
+ if (path.endsWith('.jsx')) {
44
+ return path.replace(/\.jsx$/, '.js');
45
+ }
46
+ return path;
47
+ },
48
+ },
49
+ preact: {
50
+ extensions: ['.tsx', '.jsx'],
51
+ hydrationExtension: '.js',
52
+ mimeType: 'application/javascript',
53
+ transformPath: (path: string, mode: 'development' | 'production') => {
54
+ // For Preact, transform .tsx/.jsx to .js for hydration
55
+ if (path.endsWith('.tsx')) {
56
+ return path.replace(/\.tsx$/, '.js');
57
+ }
58
+ if (path.endsWith('.jsx')) {
59
+ return path.replace(/\.jsx$/, '.js');
60
+ }
61
+ return path;
62
+ },
63
+ },
64
+ vue: {
65
+ extensions: ['.vue'],
66
+ hydrationExtension: '.js',
67
+ mimeType: 'application/javascript',
68
+ transformPath: (path: string, mode: 'development' | 'production') => {
69
+ // Vue components are typically compiled to .js
70
+ if (path.endsWith('.vue')) {
71
+ return path.replace(/\.vue$/, '.js');
72
+ }
73
+ return path;
74
+ },
75
+ },
76
+ svelte: {
77
+ extensions: ['.svelte'],
78
+ hydrationExtension: '.js',
79
+ mimeType: 'application/javascript',
80
+ transformPath: (path: string, mode: 'development' | 'production') => {
81
+ // Svelte components are compiled to .js
82
+ if (path.endsWith('.svelte')) {
83
+ return path.replace(/\.svelte$/, '.js');
84
+ }
85
+ return path;
86
+ },
87
+ },
88
+ qwik: {
89
+ extensions: ['.tsx', '.jsx'],
90
+ hydrationExtension: '.js',
91
+ mimeType: 'application/javascript',
92
+ transformPath: (path: string, mode: 'development' | 'production') => {
93
+ // Qwik components are compiled to .js via the Qwik optimizer
94
+ if (path.endsWith('.tsx')) {
95
+ return path.replace(/\.tsx$/, '.js');
96
+ }
97
+ if (path.endsWith('.jsx')) {
98
+ return path.replace(/\.jsx$/, '.js');
99
+ }
100
+ return path;
101
+ },
102
+ },
103
+ };
104
+
105
+ export class FrameworkModuleResolver {
106
+ private mode: 'development' | 'production';
107
+ private baseUrl: string;
108
+
109
+ constructor(mode: 'development' | 'production' = 'development', baseUrl = '') {
110
+ this.mode = mode;
111
+ this.baseUrl = baseUrl;
112
+ }
113
+
114
+ /**
115
+ * Resolve a module path for a specific framework
116
+ */
117
+ resolveModule(
118
+ originalPath: string,
119
+ framework: string,
120
+ options: {
121
+ forHydration?: boolean;
122
+ baseUrl?: string;
123
+ } = {}
124
+ ): ResolvedModule {
125
+ const config = FRAMEWORK_MODULE_CONFIGS[framework];
126
+ if (!config) {
127
+ throw new Error(`Unknown framework: ${framework}`);
128
+ }
129
+
130
+ const { forHydration = false, baseUrl = this.baseUrl } = options;
131
+
132
+ let resolvedPath = originalPath;
133
+ let shouldTransform = false;
134
+ let mimeType = this.getMimeType(originalPath);
135
+
136
+ // Apply framework-specific path transformation for hydration
137
+ if (forHydration) {
138
+ const transformedPath = config.transformPath(originalPath, this.mode);
139
+ if (transformedPath !== originalPath) {
140
+ resolvedPath = transformedPath;
141
+ shouldTransform = true;
142
+ mimeType = config.mimeType;
143
+ }
144
+ }
145
+
146
+ // Generate the full URL
147
+ const url = this.generateModuleUrl(resolvedPath, baseUrl);
148
+
149
+ return {
150
+ originalPath,
151
+ resolvedPath,
152
+ framework,
153
+ shouldTransform,
154
+ mimeType,
155
+ url,
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Check if a path needs transformation for the given framework
161
+ */
162
+ needsTransformation(path: string, framework: string): boolean {
163
+ const config = FRAMEWORK_MODULE_CONFIGS[framework];
164
+ if (!config) {
165
+ return false;
166
+ }
167
+
168
+ const transformedPath = config.transformPath(path, this.mode);
169
+ return transformedPath !== path;
170
+ }
171
+
172
+ /**
173
+ * Get the appropriate MIME type for a file path
174
+ */
175
+ getMimeType(path: string): string {
176
+ const ext = this.getFileExtension(path);
177
+
178
+ switch (ext) {
179
+ case '.js':
180
+ case '.mjs':
181
+ return 'application/javascript';
182
+ case '.ts':
183
+ case '.tsx':
184
+ case '.jsx':
185
+ return 'application/javascript'; // These are typically compiled to JS
186
+ case '.css':
187
+ return 'text/css';
188
+ case '.json':
189
+ return 'application/json';
190
+ case '.vue':
191
+ case '.svelte':
192
+ return 'application/javascript'; // Compiled components
193
+ default:
194
+ return 'text/plain';
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Generate a module URL for the given path
200
+ */
201
+ generateModuleUrl(path: string, baseUrl = this.baseUrl): string {
202
+ // Ensure path starts with /
203
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
204
+
205
+ if (baseUrl) {
206
+ return `${baseUrl.replace(/\/$/, '')}${normalizedPath}`;
207
+ }
208
+
209
+ return normalizedPath;
210
+ }
211
+
212
+ /**
213
+ * Get supported frameworks
214
+ */
215
+ getSupportedFrameworks(): string[] {
216
+ return Object.keys(FRAMEWORK_MODULE_CONFIGS);
217
+ }
218
+
219
+ /**
220
+ * Get framework configuration
221
+ */
222
+ getFrameworkConfig(framework: string): FrameworkModuleConfig | undefined {
223
+ return FRAMEWORK_MODULE_CONFIGS[framework];
224
+ }
225
+
226
+ /**
227
+ * Check if a framework is supported
228
+ */
229
+ isFrameworkSupported(framework: string): boolean {
230
+ return framework in FRAMEWORK_MODULE_CONFIGS;
231
+ }
232
+
233
+ /**
234
+ * Get file extension from path
235
+ */
236
+ private getFileExtension(path: string): string {
237
+ const lastDot = path.lastIndexOf('.');
238
+ return lastDot === -1 ? '' : path.substring(lastDot);
239
+ }
240
+
241
+ /**
242
+ * Set the resolver mode (development/production)
243
+ */
244
+ setMode(mode: 'development' | 'production'): void {
245
+ this.mode = mode;
246
+ }
247
+
248
+ /**
249
+ * Get current mode
250
+ */
251
+ getMode(): 'development' | 'production' {
252
+ return this.mode;
253
+ }
254
+
255
+ /**
256
+ * Set base URL for module resolution
257
+ */
258
+ setBaseUrl(baseUrl: string): void {
259
+ this.baseUrl = baseUrl;
260
+ }
261
+
262
+ /**
263
+ * Get current base URL
264
+ */
265
+ getBaseUrl(): string {
266
+ return this.baseUrl;
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Default instance for common usage
272
+ */
273
+ export const frameworkModuleResolver = new FrameworkModuleResolver();
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Tests for FrameworkModuleResolver
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { FrameworkModuleResolver } from '../framework-module-resolver.ts';
7
+
8
+ describe('FrameworkModuleResolver - Basic functionality', () => {
9
+ it('should create resolver with default settings', () => {
10
+ const resolver = new FrameworkModuleResolver();
11
+ expect(resolver.getMode()).toEqual('development');
12
+ expect(resolver.getBaseUrl()).toEqual('');
13
+ });
14
+
15
+ it('should create resolver with custom settings', () => {
16
+ const resolver = new FrameworkModuleResolver('production', 'https://example.com');
17
+ expect(resolver.getMode()).toEqual('production');
18
+ expect(resolver.getBaseUrl()).toEqual('https://example.com');
19
+ });
20
+
21
+ it('should update mode and base URL', () => {
22
+ const resolver = new FrameworkModuleResolver();
23
+ resolver.setMode('production');
24
+ resolver.setBaseUrl('https://test.com');
25
+ expect(resolver.getMode()).toEqual('production');
26
+ expect(resolver.getBaseUrl()).toEqual('https://test.com');
27
+ });
28
+ });
29
+
30
+ describe('FrameworkModuleResolver - Framework support', () => {
31
+ const resolver = new FrameworkModuleResolver();
32
+
33
+ it('should support known frameworks', () => {
34
+ expect(resolver.isFrameworkSupported('solid')).toEqual(true);
35
+ expect(resolver.isFrameworkSupported('preact')).toEqual(true);
36
+ expect(resolver.isFrameworkSupported('vue')).toEqual(true);
37
+ expect(resolver.isFrameworkSupported('svelte')).toEqual(true);
38
+ });
39
+
40
+ it('should not support unknown frameworks', () => {
41
+ expect(resolver.isFrameworkSupported('unknown')).toEqual(false);
42
+ expect(resolver.isFrameworkSupported('react')).toEqual(false);
43
+ });
44
+
45
+ it('should return supported frameworks list', () => {
46
+ const frameworks = resolver.getSupportedFrameworks();
47
+ expect(frameworks.includes('solid')).toEqual(true);
48
+ expect(frameworks.includes('preact')).toEqual(true);
49
+ expect(frameworks.includes('vue')).toEqual(true);
50
+ expect(frameworks.includes('svelte')).toEqual(true);
51
+ });
52
+
53
+ it('should get framework configuration', () => {
54
+ const solidConfig = resolver.getFrameworkConfig('solid');
55
+ expect(solidConfig?.extensions.includes('.tsx')).toEqual(true);
56
+ expect(solidConfig?.hydrationExtension).toEqual('.js');
57
+ expect(solidConfig?.mimeType).toEqual('application/javascript');
58
+
59
+ const unknownConfig = resolver.getFrameworkConfig('unknown');
60
+ expect(unknownConfig).toEqual(undefined);
61
+ });
62
+ });
63
+
64
+ describe('FrameworkModuleResolver - Solid path transformation', () => {
65
+ const resolver = new FrameworkModuleResolver();
66
+
67
+ it('should transform .tsx to .js for Solid hydration', () => {
68
+ const result = resolver.resolveModule('/src/islands/Counter.tsx', 'solid', {
69
+ forHydration: true,
70
+ });
71
+
72
+ expect(result.originalPath).toEqual('/src/islands/Counter.tsx');
73
+ expect(result.resolvedPath).toEqual('/src/islands/Counter.js');
74
+ expect(result.framework).toEqual('solid');
75
+ expect(result.shouldTransform).toEqual(true);
76
+ expect(result.mimeType).toEqual('application/javascript');
77
+ expect(result.url).toEqual('/src/islands/Counter.js');
78
+ });
79
+
80
+ it('should transform .jsx to .js for Solid hydration', () => {
81
+ const result = resolver.resolveModule('/src/islands/Counter.jsx', 'solid', {
82
+ forHydration: true,
83
+ });
84
+
85
+ expect(result.originalPath).toEqual('/src/islands/Counter.jsx');
86
+ expect(result.resolvedPath).toEqual('/src/islands/Counter.js');
87
+ expect(result.shouldTransform).toEqual(true);
88
+ });
89
+
90
+ it('should not transform non-hydration requests', () => {
91
+ const result = resolver.resolveModule('/src/islands/Counter.tsx', 'solid', {
92
+ forHydration: false,
93
+ });
94
+
95
+ expect(result.originalPath).toEqual('/src/islands/Counter.tsx');
96
+ expect(result.resolvedPath).toEqual('/src/islands/Counter.tsx');
97
+ expect(result.shouldTransform).toEqual(false);
98
+ });
99
+
100
+ it('should not transform .js files', () => {
101
+ const result = resolver.resolveModule('/src/islands/Counter.js', 'solid', {
102
+ forHydration: true,
103
+ });
104
+
105
+ expect(result.originalPath).toEqual('/src/islands/Counter.js');
106
+ expect(result.resolvedPath).toEqual('/src/islands/Counter.js');
107
+ expect(result.shouldTransform).toEqual(false);
108
+ });
109
+ });
110
+
111
+ describe('FrameworkModuleResolver - Preact path transformation', () => {
112
+ const resolver = new FrameworkModuleResolver();
113
+
114
+ it('should transform .tsx to .js for Preact hydration', () => {
115
+ const result = resolver.resolveModule('/src/islands/Counter.tsx', 'preact', {
116
+ forHydration: true,
117
+ });
118
+
119
+ expect(result.originalPath).toEqual('/src/islands/Counter.tsx');
120
+ expect(result.resolvedPath).toEqual('/src/islands/Counter.js');
121
+ expect(result.framework).toEqual('preact');
122
+ expect(result.shouldTransform).toEqual(true);
123
+ expect(result.mimeType).toEqual('application/javascript');
124
+ });
125
+
126
+ it('should transform .jsx to .js for Preact hydration', () => {
127
+ const result = resolver.resolveModule('/src/islands/Counter.jsx', 'preact', {
128
+ forHydration: true,
129
+ });
130
+
131
+ expect(result.resolvedPath).toEqual('/src/islands/Counter.js');
132
+ expect(result.shouldTransform).toEqual(true);
133
+ });
134
+ });
135
+
136
+ describe('FrameworkModuleResolver - Vue path transformation', () => {
137
+ const resolver = new FrameworkModuleResolver();
138
+
139
+ it('should transform .vue to .js for Vue hydration', () => {
140
+ const result = resolver.resolveModule('/src/islands/Counter.vue', 'vue', {
141
+ forHydration: true,
142
+ });
143
+
144
+ expect(result.originalPath).toEqual('/src/islands/Counter.vue');
145
+ expect(result.resolvedPath).toEqual('/src/islands/Counter.js');
146
+ expect(result.framework).toEqual('vue');
147
+ expect(result.shouldTransform).toEqual(true);
148
+ expect(result.mimeType).toEqual('application/javascript');
149
+ });
150
+ });
151
+
152
+ describe('FrameworkModuleResolver - Svelte path transformation', () => {
153
+ const resolver = new FrameworkModuleResolver();
154
+
155
+ it('should transform .svelte to .js for Svelte hydration', () => {
156
+ const result = resolver.resolveModule('/src/islands/Counter.svelte', 'svelte', {
157
+ forHydration: true,
158
+ });
159
+
160
+ expect(result.originalPath).toEqual('/src/islands/Counter.svelte');
161
+ expect(result.resolvedPath).toEqual('/src/islands/Counter.js');
162
+ expect(result.framework).toEqual('svelte');
163
+ expect(result.shouldTransform).toEqual(true);
164
+ expect(result.mimeType).toEqual('application/javascript');
165
+ });
166
+ });
167
+
168
+ describe('FrameworkModuleResolver - MIME type detection', () => {
169
+ const resolver = new FrameworkModuleResolver();
170
+
171
+ it('should detect JavaScript MIME types', () => {
172
+ expect(resolver.getMimeType('/test.js')).toEqual('application/javascript');
173
+ expect(resolver.getMimeType('/test.mjs')).toEqual('application/javascript');
174
+ expect(resolver.getMimeType('/test.ts')).toEqual('application/javascript');
175
+ expect(resolver.getMimeType('/test.tsx')).toEqual('application/javascript');
176
+ expect(resolver.getMimeType('/test.jsx')).toEqual('application/javascript');
177
+ });
178
+
179
+ it('should detect component MIME types', () => {
180
+ expect(resolver.getMimeType('/test.vue')).toEqual('application/javascript');
181
+ expect(resolver.getMimeType('/test.svelte')).toEqual('application/javascript');
182
+ });
183
+
184
+ it('should detect other MIME types', () => {
185
+ expect(resolver.getMimeType('/test.css')).toEqual('text/css');
186
+ expect(resolver.getMimeType('/test.json')).toEqual('application/json');
187
+ expect(resolver.getMimeType('/test.txt')).toEqual('text/plain');
188
+ expect(resolver.getMimeType('/test.unknown')).toEqual('text/plain');
189
+ });
190
+ });
191
+
192
+ describe('FrameworkModuleResolver - URL generation', () => {
193
+ it('should generate URLs without base URL', () => {
194
+ const resolver = new FrameworkModuleResolver();
195
+ expect(resolver.generateModuleUrl('/src/test.js')).toEqual('/src/test.js');
196
+ expect(resolver.generateModuleUrl('src/test.js')).toEqual('/src/test.js');
197
+ });
198
+
199
+ it('should generate URLs with base URL', () => {
200
+ const resolver = new FrameworkModuleResolver('development', 'https://example.com');
201
+ expect(resolver.generateModuleUrl('/src/test.js')).toEqual('https://example.com/src/test.js');
202
+ expect(resolver.generateModuleUrl('src/test.js')).toEqual('https://example.com/src/test.js');
203
+ });
204
+
205
+ it('should handle base URL with trailing slash', () => {
206
+ const resolver = new FrameworkModuleResolver('development', 'https://example.com/');
207
+ expect(resolver.generateModuleUrl('/src/test.js')).toEqual('https://example.com/src/test.js');
208
+ });
209
+
210
+ it('should generate URLs with custom base URL option', () => {
211
+ const resolver = new FrameworkModuleResolver();
212
+ const result = resolver.resolveModule('/src/test.tsx', 'solid', {
213
+ forHydration: true,
214
+ baseUrl: 'https://custom.com',
215
+ });
216
+ expect(result.url).toEqual('https://custom.com/src/test.js');
217
+ });
218
+ });
219
+
220
+ describe('FrameworkModuleResolver - Transformation detection', () => {
221
+ const resolver = new FrameworkModuleResolver();
222
+
223
+ it('should detect when transformation is needed', () => {
224
+ expect(resolver.needsTransformation('/test.tsx', 'solid')).toEqual(true);
225
+ expect(resolver.needsTransformation('/test.jsx', 'solid')).toEqual(true);
226
+ expect(resolver.needsTransformation('/test.vue', 'vue')).toEqual(true);
227
+ expect(resolver.needsTransformation('/test.svelte', 'svelte')).toEqual(true);
228
+ });
229
+
230
+ it('should detect when transformation is not needed', () => {
231
+ expect(resolver.needsTransformation('/test.js', 'solid')).toEqual(false);
232
+ expect(resolver.needsTransformation('/test.css', 'solid')).toEqual(false);
233
+ expect(resolver.needsTransformation('/test.tsx', 'unknown')).toEqual(false);
234
+ });
235
+ });
236
+
237
+ describe('FrameworkModuleResolver - Error handling', () => {
238
+ const resolver = new FrameworkModuleResolver();
239
+
240
+ it('should throw error for unknown framework', () => {
241
+ expect(() => {
242
+ resolver.resolveModule('/test.tsx', 'unknown');
243
+ }).toThrow('Unknown framework: unknown');
244
+ });
245
+ });
246
+
247
+ describe('FrameworkModuleResolver - Development vs Production mode', () => {
248
+ it('should handle development mode', () => {
249
+ const resolver = new FrameworkModuleResolver('development');
250
+ const result = resolver.resolveModule('/src/test.tsx', 'solid', {
251
+ forHydration: true,
252
+ });
253
+ expect(result.resolvedPath).toEqual('/src/test.js');
254
+ });
255
+
256
+ it('should handle production mode', () => {
257
+ const resolver = new FrameworkModuleResolver('production');
258
+ const result = resolver.resolveModule('/src/test.tsx', 'solid', {
259
+ forHydration: true,
260
+ });
261
+ expect(result.resolvedPath).toEqual('/src/test.js');
262
+ });
263
+ });
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Integration tests for module resolution system
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { FrameworkModuleResolver } from '../framework-module-resolver.ts';
7
+
8
+ describe('Module Resolution Integration - End-to-end flow', () => {
9
+ it('should resolve Solid .tsx to .js for hydration', () => {
10
+ const resolver = new FrameworkModuleResolver('development');
11
+
12
+ const result = resolver.resolveModule('/src/islands/Counter.tsx', 'solid', {
13
+ forHydration: true,
14
+ });
15
+
16
+ expect(result.originalPath).toEqual('/src/islands/Counter.tsx');
17
+ expect(result.resolvedPath).toEqual('/src/islands/Counter.js');
18
+ expect(result.framework).toEqual('solid');
19
+ expect(result.shouldTransform).toEqual(true);
20
+ expect(result.mimeType).toEqual('application/javascript');
21
+ expect(result.url).toEqual('/src/islands/Counter.js');
22
+ });
23
+
24
+ it('should handle different frameworks correctly', () => {
25
+ const resolver = new FrameworkModuleResolver('production', 'https://cdn.example.com');
26
+
27
+ // Test Preact
28
+ const preactResult = resolver.resolveModule('/components/Button.jsx', 'preact', {
29
+ forHydration: true,
30
+ });
31
+ expect(preactResult.resolvedPath).toEqual('/components/Button.js');
32
+ expect(preactResult.url).toEqual('https://cdn.example.com/components/Button.js');
33
+
34
+ // Test Vue
35
+ const vueResult = resolver.resolveModule('/components/Modal.vue', 'vue', {
36
+ forHydration: true,
37
+ });
38
+ expect(vueResult.resolvedPath).toEqual('/components/Modal.js');
39
+ expect(vueResult.mimeType).toEqual('application/javascript');
40
+
41
+ // Test Svelte
42
+ const svelteResult = resolver.resolveModule('/components/Card.svelte', 'svelte', {
43
+ forHydration: true,
44
+ });
45
+ expect(svelteResult.resolvedPath).toEqual('/components/Card.js');
46
+ });
47
+
48
+ it('should handle development vs production modes', () => {
49
+ const devResolver = new FrameworkModuleResolver('development');
50
+ const prodResolver = new FrameworkModuleResolver('production');
51
+
52
+ const testPath = '/src/islands/Counter.tsx';
53
+
54
+ const devResult = devResolver.resolveModule(testPath, 'solid', { forHydration: true });
55
+ const prodResult = prodResolver.resolveModule(testPath, 'solid', { forHydration: true });
56
+
57
+ expect(devResult.resolvedPath).toEqual(prodResult.resolvedPath);
58
+ expect(devResult.resolvedPath).toEqual('/src/islands/Counter.js');
59
+
60
+ expect(devResolver.getMode()).toEqual('development');
61
+ expect(prodResolver.getMode()).toEqual('production');
62
+ });
63
+ });
64
+
65
+ describe('Module Resolution Integration - Error scenarios', () => {
66
+ it('should handle unsupported frameworks gracefully', () => {
67
+ const resolver = new FrameworkModuleResolver();
68
+
69
+ expect(() => {
70
+ resolver.resolveModule('/test.tsx', 'react');
71
+ }).toThrow('Unknown framework: react');
72
+ });
73
+
74
+ it('should handle invalid paths gracefully', () => {
75
+ const resolver = new FrameworkModuleResolver();
76
+
77
+ const result = resolver.resolveModule('', 'solid', { forHydration: true });
78
+ expect(result.originalPath).toEqual('');
79
+ expect(result.resolvedPath).toEqual('');
80
+ });
81
+
82
+ it('should handle MIME type detection for unknown extensions', () => {
83
+ const resolver = new FrameworkModuleResolver();
84
+
85
+ expect(resolver.getMimeType('/test.unknown')).toEqual('text/plain');
86
+ expect(resolver.getMimeType('/test')).toEqual('text/plain');
87
+ expect(resolver.getMimeType('')).toEqual('text/plain');
88
+ });
89
+ });
90
+
91
+ describe('Module Resolution Integration - Performance considerations', () => {
92
+ it('should handle multiple resolutions efficiently', () => {
93
+ const resolver = new FrameworkModuleResolver();
94
+ const startTime = performance.now();
95
+
96
+ for (let i = 0; i < 100; i++) {
97
+ resolver.resolveModule(`/src/islands/Counter${i}.tsx`, 'solid', {
98
+ forHydration: true,
99
+ });
100
+ }
101
+
102
+ const endTime = performance.now();
103
+ const duration = endTime - startTime;
104
+
105
+ expect(duration < 100).toEqual(true);
106
+ });
107
+
108
+ it('should cache framework configurations', () => {
109
+ const resolver = new FrameworkModuleResolver();
110
+
111
+ const config1 = resolver.getFrameworkConfig('solid');
112
+ const config2 = resolver.getFrameworkConfig('solid');
113
+
114
+ expect(config1).toEqual(config2);
115
+ expect(config1?.extensions.includes('.tsx')).toEqual(true);
116
+ });
117
+ });