@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,282 @@
|
|
|
1
|
+
# Integration System
|
|
2
|
+
|
|
3
|
+
The Avalon integration system provides a modular, extensible architecture for framework integrations. Each framework (Preact, Vue, Solid, Svelte) is an independent package with its own versioning and dependencies.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/core/integrations/
|
|
9
|
+
├── registry.ts # Integration registry for managing loaded integrations
|
|
10
|
+
├── loader.ts # Dynamic integration loading with caching
|
|
11
|
+
├── validator.ts # Integration validation and compliance checking
|
|
12
|
+
├── config-loader.ts # Configuration file loading and parsing
|
|
13
|
+
├── startup.ts # System initialization and validation
|
|
14
|
+
├── cli.ts # CLI commands for managing integrations
|
|
15
|
+
└── index.ts # Public API exports
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Key Components
|
|
19
|
+
|
|
20
|
+
### Registry (`registry.ts`)
|
|
21
|
+
|
|
22
|
+
The `IntegrationRegistry` manages loaded framework integrations:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { registry } from "./src/core/integrations/registry.ts";
|
|
26
|
+
|
|
27
|
+
// Load an integration
|
|
28
|
+
const integration = await registry.load("preact");
|
|
29
|
+
|
|
30
|
+
// Check if loaded
|
|
31
|
+
if (registry.has("preact")) {
|
|
32
|
+
const preact = registry.get("preact");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Get all loaded integrations
|
|
36
|
+
const all = registry.getAll();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Loader (`loader.ts`)
|
|
40
|
+
|
|
41
|
+
Dynamic integration loading with caching:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { loadIntegration } from "./src/core/integrations/loader.ts";
|
|
45
|
+
|
|
46
|
+
// Load integration (cached)
|
|
47
|
+
const integration = await loadIntegration("vue");
|
|
48
|
+
|
|
49
|
+
// Preload multiple integrations
|
|
50
|
+
await preloadIntegrations(["preact", "vue", "solid"]);
|
|
51
|
+
|
|
52
|
+
// Check if loaded
|
|
53
|
+
if (isIntegrationLoaded("svelte")) {
|
|
54
|
+
// ...
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Validator (`validator.ts`)
|
|
59
|
+
|
|
60
|
+
Validate integrations against the required interface:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { validateIntegration } from "./src/core/integrations/validator.ts";
|
|
64
|
+
|
|
65
|
+
const result = validateIntegration(integration);
|
|
66
|
+
|
|
67
|
+
if (result.valid) {
|
|
68
|
+
console.log("Integration is valid");
|
|
69
|
+
} else {
|
|
70
|
+
console.error("Errors:", result.errors);
|
|
71
|
+
console.warn("Warnings:", result.warnings);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Configuration (`config-loader.ts`)
|
|
76
|
+
|
|
77
|
+
Load and parse `avalon.config.ts`:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { loadConfig } from "./src/core/integrations/config-loader.ts";
|
|
81
|
+
|
|
82
|
+
const result = await loadConfig();
|
|
83
|
+
|
|
84
|
+
if (result.found) {
|
|
85
|
+
console.log("Config:", result.config);
|
|
86
|
+
console.log("Path:", result.configPath);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Startup (`startup.ts`)
|
|
91
|
+
|
|
92
|
+
Initialize the integration system:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { initializeIntegrations } from "./src/core/integrations/startup.ts";
|
|
96
|
+
|
|
97
|
+
const result = await initializeIntegrations();
|
|
98
|
+
|
|
99
|
+
if (result.success) {
|
|
100
|
+
console.log("Loaded:", result.loadedIntegrations);
|
|
101
|
+
} else {
|
|
102
|
+
console.error("Errors:", result.errors);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
Create an `avalon.config.ts` file in your project root:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
export default {
|
|
112
|
+
integrations: [
|
|
113
|
+
{ name: "preact", enabled: true },
|
|
114
|
+
{ name: "vue", enabled: true },
|
|
115
|
+
{ name: "solid", enabled: true },
|
|
116
|
+
{ name: "svelte", enabled: true },
|
|
117
|
+
],
|
|
118
|
+
autoDiscoverIntegrations: true,
|
|
119
|
+
validateIntegrations: true,
|
|
120
|
+
showWarnings: true,
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
See [Integration Configuration](../../../docs/integration-configuration.md) for full documentation.
|
|
125
|
+
|
|
126
|
+
## CLI Commands
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# List integrations
|
|
130
|
+
deno run --allow-read --allow-env src/core/integrations/cli.ts list
|
|
131
|
+
|
|
132
|
+
# Initialize system
|
|
133
|
+
deno run --allow-read --allow-env src/core/integrations/cli.ts init
|
|
134
|
+
|
|
135
|
+
# Validate integrations
|
|
136
|
+
deno run --allow-read --allow-env src/core/integrations/cli.ts validate
|
|
137
|
+
|
|
138
|
+
# Show integration info
|
|
139
|
+
deno run --allow-read --allow-env src/core/integrations/cli.ts info preact
|
|
140
|
+
|
|
141
|
+
# Generate config file
|
|
142
|
+
deno run --allow-read --allow-write src/core/integrations/cli.ts generate-config
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Error Handling
|
|
146
|
+
|
|
147
|
+
The system provides helpful error messages:
|
|
148
|
+
|
|
149
|
+
### Missing Integration
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
try {
|
|
153
|
+
await loadIntegration("vue");
|
|
154
|
+
} catch (error) {
|
|
155
|
+
// Error includes installation instructions
|
|
156
|
+
console.error(error.message);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Validation Errors
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const result = validateIntegration(integration);
|
|
164
|
+
|
|
165
|
+
if (!result.valid) {
|
|
166
|
+
// Detailed error messages
|
|
167
|
+
result.errors.forEach(error => console.error(error));
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Configuration Errors
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const config = await loadConfig();
|
|
175
|
+
|
|
176
|
+
if (config.errors.length > 0) {
|
|
177
|
+
// Configuration validation errors
|
|
178
|
+
config.errors.forEach(error => console.error(error));
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Integration Discovery
|
|
183
|
+
|
|
184
|
+
The system supports multiple discovery methods:
|
|
185
|
+
|
|
186
|
+
### 1. Explicit Configuration
|
|
187
|
+
|
|
188
|
+
List integrations in `avalon.config.ts`:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
export default {
|
|
192
|
+
integrations: [
|
|
193
|
+
{ name: "preact", enabled: true },
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 2. Auto-Discovery
|
|
199
|
+
|
|
200
|
+
Enable auto-discovery to load integrations based on usage:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
export default {
|
|
204
|
+
autoDiscoverIntegrations: true,
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 3. Manual Loading
|
|
209
|
+
|
|
210
|
+
Load integrations programmatically:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { loadIntegration } from "./src/core/integrations/loader.ts";
|
|
214
|
+
|
|
215
|
+
const integration = await loadIntegration("solid");
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Validation
|
|
219
|
+
|
|
220
|
+
Integrations are validated to ensure they implement the required interface:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
interface Integration {
|
|
224
|
+
name: string;
|
|
225
|
+
version: string;
|
|
226
|
+
render(params: RenderParams): Promise<RenderResult>;
|
|
227
|
+
getHydrationScript(): string;
|
|
228
|
+
config(): IntegrationConfig;
|
|
229
|
+
vitePlugin?(): any | any[];
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Validation checks:
|
|
234
|
+
- Required properties exist and have correct types
|
|
235
|
+
- Required methods are functions
|
|
236
|
+
- Configuration is valid
|
|
237
|
+
- Detection patterns are RegExp objects
|
|
238
|
+
|
|
239
|
+
## Best Practices
|
|
240
|
+
|
|
241
|
+
### Development
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
export default {
|
|
245
|
+
autoDiscoverIntegrations: true, // Flexible
|
|
246
|
+
validateIntegrations: true, // Catch issues early
|
|
247
|
+
showWarnings: true, // See all issues
|
|
248
|
+
};
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Production
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
export default {
|
|
255
|
+
integrations: [
|
|
256
|
+
// Only list what you use
|
|
257
|
+
{ name: "preact", enabled: true },
|
|
258
|
+
],
|
|
259
|
+
autoDiscoverIntegrations: false, // Explicit control
|
|
260
|
+
validateIntegrations: true, // Ensure compatibility
|
|
261
|
+
showWarnings: false, // Clean logs
|
|
262
|
+
};
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Testing
|
|
266
|
+
|
|
267
|
+
Test the configuration system:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
deno run --allow-read --allow-env src/core/integrations/test-config.ts
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## API Reference
|
|
274
|
+
|
|
275
|
+
See [Integration Configuration](../../../docs/integration-configuration.md) for complete API documentation.
|
|
276
|
+
|
|
277
|
+
## Related Documentation
|
|
278
|
+
|
|
279
|
+
- [Integration System Design](../../../.kiro/specs/framework-integrations/design.md)
|
|
280
|
+
- [Integration Requirements](../../../.kiro/specs/framework-integrations/requirements.md)
|
|
281
|
+
- [Integration Configuration](../../../docs/integration-configuration.md)
|
|
282
|
+
- [Creating Custom Integrations](../../../docs/custom-integrations.md)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration system exports
|
|
3
|
+
* Central export point for all integration-related functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Registry
|
|
7
|
+
export { IntegrationRegistry, registry } from "./registry.ts";
|
|
8
|
+
|
|
9
|
+
// Loader
|
|
10
|
+
export {
|
|
11
|
+
loadIntegration,
|
|
12
|
+
detectAndLoadIntegration,
|
|
13
|
+
detectFrameworkFromPath,
|
|
14
|
+
detectFrameworkFromContent,
|
|
15
|
+
preloadIntegrations,
|
|
16
|
+
getLoadedIntegrations,
|
|
17
|
+
clearIntegrationCache,
|
|
18
|
+
isIntegrationLoaded,
|
|
19
|
+
} from "./loader.ts";
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { registry } from "./registry.ts";
|
|
2
|
+
import type { Integration } from "@useavalon/core";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cache for loaded integrations to avoid repeated dynamic imports
|
|
6
|
+
*/
|
|
7
|
+
const integrationCache = new Map<string, Integration>();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Load an integration by name, using cache if available
|
|
11
|
+
*/
|
|
12
|
+
export async function loadIntegration(framework: string): Promise<Integration> {
|
|
13
|
+
// Check cache first
|
|
14
|
+
if (integrationCache.has(framework)) {
|
|
15
|
+
return integrationCache.get(framework)!;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Load from registry (which handles dynamic imports)
|
|
19
|
+
const integration = await registry.load(framework);
|
|
20
|
+
|
|
21
|
+
// Cache the loaded integration
|
|
22
|
+
integrationCache.set(framework, integration);
|
|
23
|
+
|
|
24
|
+
return integration;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Detect framework from file path and load the appropriate integration
|
|
29
|
+
*/
|
|
30
|
+
export async function detectAndLoadIntegration(src: string): Promise<Integration> {
|
|
31
|
+
const framework = detectFrameworkFromPath(src);
|
|
32
|
+
return await loadIntegration(framework);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Detect framework from file path based on extension and naming conventions
|
|
37
|
+
*/
|
|
38
|
+
export function detectFrameworkFromPath(src: string): string {
|
|
39
|
+
// Vue files
|
|
40
|
+
if (src.endsWith(".vue")) {
|
|
41
|
+
return "vue";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Svelte files
|
|
45
|
+
if (src.endsWith(".svelte")) {
|
|
46
|
+
return "svelte";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Solid files (convention: .solid.tsx or .solid.jsx)
|
|
50
|
+
if (src.includes(".solid.")) {
|
|
51
|
+
return "solid";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Qwik files (convention: .qwik.tsx or .qwik.jsx)
|
|
55
|
+
if (src.includes(".qwik.")) {
|
|
56
|
+
return "qwik";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Default to Preact for .tsx and .jsx files
|
|
60
|
+
return "preact";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Detect framework from file content by analyzing imports and patterns
|
|
65
|
+
*/
|
|
66
|
+
export function detectFrameworkFromContent(
|
|
67
|
+
src: string,
|
|
68
|
+
content?: string
|
|
69
|
+
): string {
|
|
70
|
+
// First try path-based detection
|
|
71
|
+
const pathFramework = detectFrameworkFromPath(src);
|
|
72
|
+
|
|
73
|
+
// If we have a definitive answer from path, use it
|
|
74
|
+
if (pathFramework !== "preact" || !content) {
|
|
75
|
+
return pathFramework;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// For .tsx/.jsx files, analyze content to distinguish between Preact and Solid
|
|
79
|
+
if (content.includes("solid-js")) {
|
|
80
|
+
return "solid";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (content.includes("@builder.io/qwik")) {
|
|
84
|
+
return "qwik";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (content.includes("preact")) {
|
|
88
|
+
return "preact";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Default to Preact
|
|
92
|
+
return "preact";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Preload integrations for the given frameworks
|
|
97
|
+
* Useful for warming up the cache during build or startup
|
|
98
|
+
*/
|
|
99
|
+
export async function preloadIntegrations(frameworks: string[]): Promise<void> {
|
|
100
|
+
await Promise.all(
|
|
101
|
+
frameworks.map(framework => loadIntegration(framework))
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get all currently loaded integrations
|
|
107
|
+
*/
|
|
108
|
+
export function getLoadedIntegrations(): Integration[] {
|
|
109
|
+
return Array.from(integrationCache.values());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Clear the integration cache
|
|
114
|
+
* Useful for testing or hot module replacement
|
|
115
|
+
*/
|
|
116
|
+
export function clearIntegrationCache(): void {
|
|
117
|
+
integrationCache.clear();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if an integration is loaded in cache
|
|
122
|
+
*/
|
|
123
|
+
export function isIntegrationLoaded(framework: string): boolean {
|
|
124
|
+
return integrationCache.has(framework);
|
|
125
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { Integration } from "@useavalon/core";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { statSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Find the root of the Avalon monorepo by looking for packages/integrations
|
|
7
|
+
*/
|
|
8
|
+
function findMonorepoRoot(): string {
|
|
9
|
+
let currentDir = process.cwd();
|
|
10
|
+
|
|
11
|
+
// Walk up the directory tree looking for packages/integrations
|
|
12
|
+
for (let i = 0; i < 10; i++) {
|
|
13
|
+
try {
|
|
14
|
+
const integrationsPath = join(currentDir, "packages", "integrations");
|
|
15
|
+
const stat = statSync(integrationsPath);
|
|
16
|
+
if (stat.isDirectory()) {
|
|
17
|
+
return currentDir;
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
// Directory doesn't exist, try parent
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const parent = dirname(currentDir);
|
|
24
|
+
if (parent === currentDir) {
|
|
25
|
+
// Reached root, stop
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
currentDir = parent;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Fallback to cwd
|
|
32
|
+
return process.cwd();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* IntegrationRegistry manages loaded framework integrations.
|
|
37
|
+
* It provides registration, retrieval, and dynamic loading of integrations.
|
|
38
|
+
*/
|
|
39
|
+
export class IntegrationRegistry {
|
|
40
|
+
private readonly integrations = new Map<string, Integration>();
|
|
41
|
+
private readonly loadingPromises = new Map<string, Promise<Integration>>();
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Register an integration instance
|
|
45
|
+
*/
|
|
46
|
+
register(integration: Integration): void {
|
|
47
|
+
if (!integration.name) {
|
|
48
|
+
throw new Error("Integration must have a name");
|
|
49
|
+
}
|
|
50
|
+
this.integrations.set(integration.name, integration);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get a registered integration by name
|
|
55
|
+
*/
|
|
56
|
+
get(name: string): Integration | undefined {
|
|
57
|
+
return this.integrations.get(name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if an integration is registered
|
|
62
|
+
*/
|
|
63
|
+
has(name: string): boolean {
|
|
64
|
+
return this.integrations.has(name);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Dynamically load an integration by name
|
|
69
|
+
* Returns cached integration if already loaded
|
|
70
|
+
*/
|
|
71
|
+
async load(name: string): Promise<Integration> {
|
|
72
|
+
// Check if already loaded
|
|
73
|
+
const existing = this.integrations.get(name);
|
|
74
|
+
if (existing) {
|
|
75
|
+
return existing;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if currently loading (prevent duplicate loads)
|
|
79
|
+
const loadingPromise = this.loadingPromises.get(name);
|
|
80
|
+
if (loadingPromise) {
|
|
81
|
+
return loadingPromise;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Start loading
|
|
85
|
+
const promise = this.loadIntegration(name);
|
|
86
|
+
this.loadingPromises.set(name, promise);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const integration = await promise;
|
|
90
|
+
this.register(integration);
|
|
91
|
+
return integration;
|
|
92
|
+
} finally {
|
|
93
|
+
this.loadingPromises.delete(name);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Internal method to load integration module
|
|
99
|
+
* Note: In development mode, integrations should be pre-loaded via preloader.ts
|
|
100
|
+
* before Vite's SSR context starts. This method is a fallback for production
|
|
101
|
+
* or when integrations weren't pre-loaded.
|
|
102
|
+
*/
|
|
103
|
+
private async loadIntegration(name: string): Promise<Integration> {
|
|
104
|
+
try {
|
|
105
|
+
// Use absolute file:// URL to bypass Vite's module resolution
|
|
106
|
+
// This ensures we use Deno's native import which handles npm: specifiers correctly
|
|
107
|
+
const monorepoRoot = findMonorepoRoot();
|
|
108
|
+
const integrationPath = join(monorepoRoot, "packages", "integrations", name, "mod.ts");
|
|
109
|
+
const fileUrl = `file://${integrationPath}`;
|
|
110
|
+
|
|
111
|
+
// Dynamic import with file:// URL bypasses Vite's SSR module loader
|
|
112
|
+
const module = await import(/* @vite-ignore */ fileUrl);
|
|
113
|
+
|
|
114
|
+
// Look for the integration export (e.g., preactIntegration)
|
|
115
|
+
const integrationKey = `${name}Integration`;
|
|
116
|
+
const integration = module[integrationKey] || module.default;
|
|
117
|
+
|
|
118
|
+
if (!integration) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Integration module '${name}' does not export '${integrationKey}' or a default export`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return integration as Integration;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// Check if this is a Vite SSR context issue
|
|
127
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
128
|
+
const isViteIssue = errorMessage.includes('ERR_UNSUPPORTED_ESM_URL_SCHEME') ||
|
|
129
|
+
errorMessage.includes('Only file and data URLs are supported');
|
|
130
|
+
|
|
131
|
+
if (isViteIssue) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Integration '${name}' could not be loaded within Vite's SSR context. ` +
|
|
134
|
+
`This usually means the integration wasn't pre-loaded at server startup. ` +
|
|
135
|
+
`Make sure preloadIntegrationsNative() is called before Vite server starts.`,
|
|
136
|
+
{ cause: error }
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Failed to load integration for framework '${name}'. ` +
|
|
142
|
+
`Make sure @useavalon/${name} is installed.\n` +
|
|
143
|
+
`Install it with: bun add @useavalon/${name}`,
|
|
144
|
+
{ cause: error }
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get all registered integrations
|
|
151
|
+
*/
|
|
152
|
+
getAll(): Integration[] {
|
|
153
|
+
return Array.from(this.integrations.values());
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get all registered integration names
|
|
158
|
+
*/
|
|
159
|
+
getAllNames(): string[] {
|
|
160
|
+
return Array.from(this.integrations.keys());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Unregister an integration
|
|
165
|
+
*/
|
|
166
|
+
unregister(name: string): boolean {
|
|
167
|
+
return this.integrations.delete(name);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Clear all registered integrations
|
|
172
|
+
*/
|
|
173
|
+
clear(): void {
|
|
174
|
+
this.integrations.clear();
|
|
175
|
+
this.loadingPromises.clear();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the count of registered integrations
|
|
180
|
+
*/
|
|
181
|
+
get size(): number {
|
|
182
|
+
return this.integrations.size;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Global singleton registry instance
|
|
187
|
+
// Use globalThis to ensure the registry is shared across all module contexts
|
|
188
|
+
// This is important because Vite's ssrLoadModule creates new module contexts
|
|
189
|
+
declare global {
|
|
190
|
+
var __avalonIntegrationRegistry: IntegrationRegistry | undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
globalThis.__avalonIntegrationRegistry ??= new IntegrationRegistry();
|
|
194
|
+
|
|
195
|
+
export const registry = globalThis.__avalonIntegrationRegistry;
|