@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.
- package/README.md +54 -0
- package/mod.ts +301 -0
- package/package.json +85 -0
- package/src/build/README.md +310 -0
- package/src/build/integration-bundler-plugin.ts +116 -0
- package/src/build/integration-config.ts +168 -0
- package/src/build/integration-detection-plugin.ts +117 -0
- package/src/build/integration-resolver-plugin.ts +90 -0
- package/src/build/island-manifest.ts +269 -0
- package/src/build/island-types-generator.ts +476 -0
- package/src/build/mdx-island-transform.ts +464 -0
- package/src/build/mdx-plugin.ts +98 -0
- package/src/build/page-island-transform.ts +598 -0
- package/src/build/prop-extractors/index.ts +21 -0
- package/src/build/prop-extractors/lit.ts +140 -0
- package/src/build/prop-extractors/qwik.ts +16 -0
- package/src/build/prop-extractors/solid.ts +125 -0
- package/src/build/prop-extractors/svelte.ts +194 -0
- package/src/build/prop-extractors/vue.ts +111 -0
- package/src/build/sidecar-file-manager.ts +104 -0
- package/src/build/sidecar-renderer.ts +30 -0
- package/src/client/adapters/index.ts +13 -0
- package/src/client/adapters/lit-adapter.ts +654 -0
- package/src/client/adapters/preact-adapter.ts +331 -0
- package/src/client/adapters/qwik-adapter.ts +345 -0
- package/src/client/adapters/react-adapter.ts +353 -0
- package/src/client/adapters/solid-adapter.ts +451 -0
- package/src/client/adapters/svelte-adapter.ts +524 -0
- package/src/client/adapters/vue-adapter.ts +467 -0
- package/src/client/components.ts +35 -0
- package/src/client/css-hmr-handler.ts +344 -0
- package/src/client/framework-adapter.ts +462 -0
- package/src/client/hmr-coordinator.ts +396 -0
- package/src/client/hmr-error-overlay.js +533 -0
- package/src/client/main.js +816 -0
- package/src/client/tests/css-hmr-handler.test.ts +360 -0
- package/src/client/tests/framework-adapter.test.ts +519 -0
- package/src/client/tests/hmr-coordinator.test.ts +176 -0
- package/src/client/tests/hydration-option-parsing.test.ts +107 -0
- package/src/client/tests/lit-adapter.test.ts +427 -0
- package/src/client/tests/preact-adapter.test.ts +353 -0
- package/src/client/tests/qwik-adapter.test.ts +343 -0
- package/src/client/tests/react-adapter.test.ts +317 -0
- package/src/client/tests/solid-adapter.test.ts +396 -0
- package/src/client/tests/svelte-adapter.test.ts +387 -0
- package/src/client/tests/vue-adapter.test.ts +407 -0
- package/src/client/types/framework-runtime.d.ts +68 -0
- package/src/client/types/vite-hmr.d.ts +46 -0
- package/src/client/types/vite-virtual-modules.d.ts +60 -0
- package/src/components/Image.tsx +123 -0
- package/src/components/IslandErrorBoundary.tsx +145 -0
- package/src/components/LayoutDataErrorBoundary.tsx +141 -0
- package/src/components/LayoutErrorBoundary.tsx +127 -0
- package/src/components/PersistentIsland.tsx +52 -0
- package/src/components/StreamingErrorBoundary.tsx +233 -0
- package/src/components/StreamingLayout.tsx +538 -0
- package/src/components/tests/component-analyzer.test.ts +96 -0
- package/src/components/tests/component-detection.test.ts +347 -0
- package/src/components/tests/persistent-islands.test.ts +398 -0
- package/src/core/components/component-analyzer.ts +192 -0
- package/src/core/components/component-detection.ts +508 -0
- package/src/core/components/enhanced-framework-detector.ts +500 -0
- package/src/core/components/framework-registry.ts +563 -0
- package/src/core/components/tests/enhanced-framework-detector.test.ts +577 -0
- package/src/core/components/tests/framework-registry.test.ts +465 -0
- package/src/core/content/mdx-processor.ts +46 -0
- package/src/core/integrations/README.md +282 -0
- package/src/core/integrations/index.ts +19 -0
- package/src/core/integrations/loader.ts +125 -0
- package/src/core/integrations/registry.ts +195 -0
- package/src/core/islands/island-persistence.ts +325 -0
- package/src/core/islands/island-state-serializer.ts +258 -0
- package/src/core/islands/persistent-island-context.tsx +80 -0
- package/src/core/islands/use-persistent-state.ts +68 -0
- package/src/core/layout/enhanced-layout-resolver.ts +322 -0
- package/src/core/layout/layout-cache-manager.ts +485 -0
- package/src/core/layout/layout-composer.ts +357 -0
- package/src/core/layout/layout-data-loader.ts +516 -0
- package/src/core/layout/layout-discovery.ts +243 -0
- package/src/core/layout/layout-matcher.ts +299 -0
- package/src/core/layout/layout-types.ts +110 -0
- package/src/core/layout/tests/enhanced-layout-resolver.test.ts +477 -0
- package/src/core/layout/tests/layout-cache-optimization.test.ts +149 -0
- package/src/core/layout/tests/layout-composer.test.ts +486 -0
- package/src/core/layout/tests/layout-data-loader.test.ts +443 -0
- package/src/core/layout/tests/layout-discovery.test.ts +253 -0
- package/src/core/layout/tests/layout-matcher.test.ts +480 -0
- package/src/core/modules/framework-module-resolver.ts +273 -0
- package/src/core/modules/tests/framework-module-resolver.test.ts +263 -0
- package/src/core/modules/tests/module-resolution-integration.test.ts +117 -0
- package/src/islands/component-analysis.ts +213 -0
- package/src/islands/css-utils.ts +565 -0
- package/src/islands/discovery/index.ts +80 -0
- package/src/islands/discovery/registry.ts +340 -0
- package/src/islands/discovery/resolver.ts +477 -0
- package/src/islands/discovery/scanner.ts +386 -0
- package/src/islands/discovery/tests/island-discovery.test.ts +881 -0
- package/src/islands/discovery/types.ts +117 -0
- package/src/islands/discovery/validator.ts +544 -0
- package/src/islands/discovery/watcher.ts +368 -0
- package/src/islands/framework-detection.ts +428 -0
- package/src/islands/integration-loader.ts +490 -0
- package/src/islands/island.tsx +565 -0
- package/src/islands/render-cache.ts +550 -0
- package/src/islands/types.ts +80 -0
- package/src/islands/universal-css-collector.ts +157 -0
- package/src/islands/universal-head-collector.ts +137 -0
- package/src/layout-system.d.ts +592 -0
- package/src/layout-system.ts +218 -0
- package/src/middleware/__tests__/discovery.test.ts +107 -0
- package/src/middleware/discovery.ts +268 -0
- package/src/middleware/executor.ts +315 -0
- package/src/middleware/index.ts +76 -0
- package/src/middleware/types.ts +99 -0
- package/src/nitro/build-config.ts +576 -0
- package/src/nitro/config.ts +483 -0
- package/src/nitro/error-handler.ts +636 -0
- package/src/nitro/index.ts +173 -0
- package/src/nitro/island-manifest.ts +584 -0
- package/src/nitro/middleware-adapter.ts +260 -0
- package/src/nitro/renderer.ts +1458 -0
- package/src/nitro/route-discovery.ts +439 -0
- package/src/nitro/types.ts +321 -0
- package/src/render/collect-css.ts +198 -0
- package/src/render/error-pages.ts +79 -0
- package/src/render/isolated-ssr-renderer.ts +654 -0
- package/src/render/ssr.ts +1030 -0
- package/src/schemas/api.ts +30 -0
- package/src/schemas/core.ts +64 -0
- package/src/schemas/index.ts +212 -0
- package/src/schemas/layout.ts +279 -0
- package/src/schemas/routing/index.ts +38 -0
- package/src/schemas/routing.ts +376 -0
- package/src/types/as-island.ts +20 -0
- package/src/types/image.d.ts +106 -0
- package/src/types/index.d.ts +22 -0
- package/src/types/island-jsx.d.ts +33 -0
- package/src/types/island-prop.d.ts +20 -0
- package/src/types/layout.ts +285 -0
- package/src/types/mdx.d.ts +6 -0
- package/src/types/routing.ts +555 -0
- package/src/types/tests/layout-types.test.ts +197 -0
- package/src/types/types.ts +5 -0
- package/src/types/urlpattern.d.ts +49 -0
- package/src/types/vite-env.d.ts +11 -0
- package/src/utils/dev-logger.ts +299 -0
- package/src/utils/fs.ts +151 -0
- package/src/vite-plugin/auto-discover.ts +551 -0
- package/src/vite-plugin/config.ts +266 -0
- package/src/vite-plugin/errors.ts +127 -0
- package/src/vite-plugin/image-optimization.ts +151 -0
- package/src/vite-plugin/integration-activator.ts +126 -0
- package/src/vite-plugin/island-sidecar-plugin.ts +176 -0
- package/src/vite-plugin/module-discovery.ts +189 -0
- package/src/vite-plugin/nitro-integration.ts +1334 -0
- package/src/vite-plugin/plugin.ts +329 -0
- package/src/vite-plugin/tests/image-optimization.test.ts +54 -0
- package/src/vite-plugin/types.ts +327 -0
- 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
|
+
});
|