@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,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avalon Vite Plugin
|
|
3
|
+
*
|
|
4
|
+
* This module provides the main `avalon()` function that creates a unified Vite plugin
|
|
5
|
+
* for the Avalon framework. It handles configuration resolution, integration activation,
|
|
6
|
+
* Nitro server integration, and wires up all the necessary Vite hooks.
|
|
7
|
+
*
|
|
8
|
+
* ISLAND DETECTION:
|
|
9
|
+
* Islands are detected by usage - any component used with an `island` prop in pages
|
|
10
|
+
* or layouts is automatically treated as an island. No fixed islands directory required.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Plugin, PluginOption, ResolvedConfig, ViteDevServer } from "vite";
|
|
14
|
+
import type {
|
|
15
|
+
AvalonPluginConfig,
|
|
16
|
+
IntegrationName,
|
|
17
|
+
ResolvedAvalonConfig,
|
|
18
|
+
} from "./types.ts";
|
|
19
|
+
import { resolveConfig, checkDirectoriesExist } from "./config.ts";
|
|
20
|
+
import { activateIntegrations, activateSingleIntegration } from "./integration-activator.ts";
|
|
21
|
+
import { discoverIntegrationsFromIslandUsage } from "./auto-discover.ts";
|
|
22
|
+
import { validateActiveIntegrations, formatValidationResults } from "./validation.ts";
|
|
23
|
+
import { createMDXPlugin } from "../build/mdx-plugin.ts";
|
|
24
|
+
import { mdxIslandTransform } from "../build/mdx-island-transform.ts";
|
|
25
|
+
import { pageIslandTransform } from "../build/page-island-transform.ts";
|
|
26
|
+
import { registry } from "../core/integrations/registry.ts";
|
|
27
|
+
import { createNitroIntegration } from "./nitro-integration.ts";
|
|
28
|
+
import { islandSidecarPlugin } from "./island-sidecar-plugin.ts";
|
|
29
|
+
import { createImagePlugin } from "./image-optimization.ts";
|
|
30
|
+
import type { NitroConfigOutput } from "../nitro/config.ts";
|
|
31
|
+
declare global {
|
|
32
|
+
var __avalonConfig: ResolvedAvalonConfig | undefined;
|
|
33
|
+
var __viteDevServer: ViteDevServer | undefined;
|
|
34
|
+
var __nitroConfig: NitroConfigOutput | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Collects Vite plugins from all activated integrations.
|
|
39
|
+
*
|
|
40
|
+
* This function iterates through the activated integrations and calls their
|
|
41
|
+
* vitePlugin() method if implemented. The returned plugins are collected and
|
|
42
|
+
* flattened into a single array.
|
|
43
|
+
*
|
|
44
|
+
* Plugin ordering is handled to ensure correct application:
|
|
45
|
+
* - Lit plugins come first (DOM shim requirement)
|
|
46
|
+
* - Other framework plugins follow
|
|
47
|
+
*
|
|
48
|
+
* @param activeIntegrations - Set of activated integration names
|
|
49
|
+
* @param verbose - Whether to log detailed information
|
|
50
|
+
* @returns Promise resolving to an array of Vite plugins from integrations
|
|
51
|
+
*/
|
|
52
|
+
export async function collectIntegrationPlugins(
|
|
53
|
+
activeIntegrations: Set<IntegrationName>,
|
|
54
|
+
verbose: boolean = false
|
|
55
|
+
): Promise<Plugin[]> {
|
|
56
|
+
const plugins: Plugin[] = [];
|
|
57
|
+
const litPlugins: Plugin[] = [];
|
|
58
|
+
|
|
59
|
+
for (const name of activeIntegrations) {
|
|
60
|
+
const validPlugins = await loadPluginsForIntegration(name, verbose);
|
|
61
|
+
if (name === "lit") {
|
|
62
|
+
litPlugins.push(...validPlugins);
|
|
63
|
+
} else {
|
|
64
|
+
plugins.push(...validPlugins);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return [...litPlugins, ...plugins];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function loadPluginsForIntegration(name: IntegrationName, _verbose: boolean): Promise<Plugin[]> {
|
|
72
|
+
const integration = registry.get(name);
|
|
73
|
+
if (!integration) return [];
|
|
74
|
+
if (typeof integration.vitePlugin !== "function") return [];
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const result = await integration.vitePlugin();
|
|
78
|
+
const pluginArray = Array.isArray(result) ? result : [result];
|
|
79
|
+
return pluginArray.filter((p): p is Plugin => p != null);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn(`[avalon] Failed to load vite plugin for ${name}:`, error instanceof Error ? error.message : error);
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Discovers which integrations are actually needed by scanning pages/layouts for island prop usage.
|
|
88
|
+
* This enables lazy loading - only load Vite plugins for frameworks that are actually used.
|
|
89
|
+
*
|
|
90
|
+
* @param config - The resolved Avalon configuration
|
|
91
|
+
* @param projectRoot - The project root directory (defaults to cwd)
|
|
92
|
+
* @returns Set of integration names that are actually needed
|
|
93
|
+
*/
|
|
94
|
+
async function discoverNeededIntegrations(
|
|
95
|
+
config: ResolvedAvalonConfig,
|
|
96
|
+
projectRoot?: string
|
|
97
|
+
): Promise<Set<IntegrationName>> {
|
|
98
|
+
const needed = new Set<IntegrationName>();
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Scan pages, layouts, and modules for components used with island prop
|
|
102
|
+
const discovered = await discoverIntegrationsFromIslandUsage(
|
|
103
|
+
config.pagesDir,
|
|
104
|
+
config.layoutsDir,
|
|
105
|
+
projectRoot,
|
|
106
|
+
config.modules?.dir
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Only include integrations that are both discovered AND configured
|
|
110
|
+
for (const integration of discovered) {
|
|
111
|
+
if (config.integrations.includes(integration)) {
|
|
112
|
+
needed.add(integration);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch {
|
|
116
|
+
// If discovery fails, fall back to all configured integrations
|
|
117
|
+
for (const integration of config.integrations) {
|
|
118
|
+
needed.add(integration);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return needed;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function resolveIntegrationsToLoad(
|
|
126
|
+
preResolvedConfig: ResolvedAvalonConfig
|
|
127
|
+
): Promise<IntegrationName[]> {
|
|
128
|
+
if (!preResolvedConfig.lazyIntegrations || preResolvedConfig.integrations.length === 0) {
|
|
129
|
+
return [...preResolvedConfig.integrations];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const needed = await discoverNeededIntegrations(preResolvedConfig);
|
|
133
|
+
if (needed.size === 0) {
|
|
134
|
+
return [...preResolvedConfig.integrations];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return Array.from(needed);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function setupMDXPlugins(preResolvedConfig: ResolvedAvalonConfig): Promise<Plugin[]> {
|
|
141
|
+
try {
|
|
142
|
+
const mdxPlugins = await createMDXPlugin({
|
|
143
|
+
jsxImportSource: preResolvedConfig.mdx.jsxImportSource,
|
|
144
|
+
syntaxHighlighting: preResolvedConfig.mdx.syntaxHighlighting,
|
|
145
|
+
remarkPlugins: preResolvedConfig.mdx.remarkPlugins as import("unified").Pluggable[],
|
|
146
|
+
rehypePlugins: preResolvedConfig.mdx.rehypePlugins as import("unified").Pluggable[],
|
|
147
|
+
development: true,
|
|
148
|
+
});
|
|
149
|
+
mdxPlugins.push(mdxIslandTransform({ verbose: preResolvedConfig.verbose }));
|
|
150
|
+
return mdxPlugins;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (preResolvedConfig.showWarnings) {
|
|
153
|
+
console.warn("⚠️ Could not configure MDX plugin:", error);
|
|
154
|
+
}
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function setupNitroPlugins(
|
|
160
|
+
preResolvedConfig: ResolvedAvalonConfig,
|
|
161
|
+
nitroConfig: NonNullable<AvalonPluginConfig["nitro"]>,
|
|
162
|
+
_verbose?: boolean
|
|
163
|
+
): { plugins: Plugin[]; options: NitroConfigOutput } {
|
|
164
|
+
const { plugins, nitroOptions } = createNitroIntegration(preResolvedConfig, nitroConfig);
|
|
165
|
+
globalThis.__nitroConfig = nitroOptions;
|
|
166
|
+
return { plugins, options: nitroOptions };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function runAutoDiscovery(
|
|
170
|
+
resolvedConfig: ResolvedAvalonConfig,
|
|
171
|
+
viteRoot: string,
|
|
172
|
+
activeIntegrations: Set<IntegrationName>
|
|
173
|
+
): Promise<void> {
|
|
174
|
+
if (!resolvedConfig.autoDiscoverIntegrations) return;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const discovered = await discoverIntegrationsFromIslandUsage(
|
|
178
|
+
resolvedConfig.pagesDir,
|
|
179
|
+
resolvedConfig.layoutsDir,
|
|
180
|
+
viteRoot,
|
|
181
|
+
resolvedConfig.modules?.dir
|
|
182
|
+
);
|
|
183
|
+
for (const name of discovered) {
|
|
184
|
+
if (activeIntegrations.has(name)) continue;
|
|
185
|
+
try {
|
|
186
|
+
await activateSingleIntegration(name, activeIntegrations, resolvedConfig.verbose);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (resolvedConfig.showWarnings) console.warn(` ⚠️ Could not auto-load integration: ${name}`, error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (resolvedConfig.showWarnings) console.warn(" ⚠️ Auto-discovery failed:", error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function runValidation(
|
|
197
|
+
resolvedConfig: ResolvedAvalonConfig,
|
|
198
|
+
activeIntegrations: Set<IntegrationName>
|
|
199
|
+
): void {
|
|
200
|
+
if (!resolvedConfig.validateIntegrations || activeIntegrations.size === 0) return;
|
|
201
|
+
|
|
202
|
+
const validationSummary = validateActiveIntegrations(activeIntegrations, resolvedConfig.showWarnings);
|
|
203
|
+
if (!validationSummary.allValid) {
|
|
204
|
+
console.error(formatValidationResults(validationSummary));
|
|
205
|
+
if (resolvedConfig.showWarnings) console.warn(" ⚠️ Some integrations have validation issues.");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Creates the Avalon Vite plugin array
|
|
211
|
+
*
|
|
212
|
+
* @param config - Avalon configuration options
|
|
213
|
+
* @returns A promise that resolves to an array of Vite plugins that handle all Avalon functionality.
|
|
214
|
+
* Returns PluginOption[] to avoid TypeScript's excessive stack depth issues
|
|
215
|
+
* when comparing Plugin<any> arrays in Vite 8's complex type system.
|
|
216
|
+
*/
|
|
217
|
+
export async function avalon(config?: AvalonPluginConfig): Promise<PluginOption[]> {
|
|
218
|
+
// Resolved configuration with defaults applied
|
|
219
|
+
let resolvedConfig: ResolvedAvalonConfig;
|
|
220
|
+
|
|
221
|
+
// Reference to Vite's resolved config
|
|
222
|
+
let viteConfig: ResolvedConfig;
|
|
223
|
+
|
|
224
|
+
// Track which integrations are activated
|
|
225
|
+
const activeIntegrations = new Set<IntegrationName>();
|
|
226
|
+
|
|
227
|
+
// Pre-resolve config to get MDX settings and integration list
|
|
228
|
+
// We use isDev=true as a default; the actual value will be set in configResolved
|
|
229
|
+
const preResolvedConfig = resolveConfig(config, true);
|
|
230
|
+
|
|
231
|
+
const integrationsToLoad = await resolveIntegrationsToLoad(preResolvedConfig);
|
|
232
|
+
|
|
233
|
+
if (integrationsToLoad.length > 0) {
|
|
234
|
+
await activateIntegrations({ ...preResolvedConfig, integrations: integrationsToLoad }, activeIntegrations);
|
|
235
|
+
}
|
|
236
|
+
const mdxPlugins = await setupMDXPlugins(preResolvedConfig);
|
|
237
|
+
|
|
238
|
+
// Image optimization plugins (vite-imagetools wrapper)
|
|
239
|
+
const imagePlugins = await createImagePlugin(preResolvedConfig.image, preResolvedConfig.verbose);
|
|
240
|
+
|
|
241
|
+
let integrationPlugins: Plugin[] = [];
|
|
242
|
+
if (activeIntegrations.size > 0) {
|
|
243
|
+
integrationPlugins = await collectIntegrationPlugins(activeIntegrations, preResolvedConfig.verbose);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let nitroPlugins: Plugin[] = [];
|
|
247
|
+
if (config?.nitro) {
|
|
248
|
+
const { plugins } = setupNitroPlugins(preResolvedConfig, config.nitro, preResolvedConfig.verbose);
|
|
249
|
+
nitroPlugins = plugins;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Sidecar plugin for Vue/Svelte/Solid type declarations
|
|
253
|
+
const sidecarPlugin = islandSidecarPlugin({
|
|
254
|
+
verbose: preResolvedConfig.verbose,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// The main Avalon plugin
|
|
258
|
+
const avalonPlugin: Plugin = {
|
|
259
|
+
name: "avalon",
|
|
260
|
+
enforce: "pre",
|
|
261
|
+
|
|
262
|
+
configResolved(resolvedViteConfig: ResolvedConfig) {
|
|
263
|
+
viteConfig = resolvedViteConfig;
|
|
264
|
+
const isDev = resolvedViteConfig.command === "serve";
|
|
265
|
+
resolvedConfig = resolveConfig(config, isDev);
|
|
266
|
+
|
|
267
|
+
globalThis.__avalonConfig = resolvedConfig;
|
|
268
|
+
|
|
269
|
+
checkDirectoriesExist(resolvedConfig, resolvedViteConfig.root);
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
async buildStart() {
|
|
273
|
+
await runAutoDiscovery(resolvedConfig, viteConfig?.root, activeIntegrations);
|
|
274
|
+
runValidation(resolvedConfig, activeIntegrations);
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
configureServer(server: ViteDevServer) {
|
|
278
|
+
|
|
279
|
+
(globalThis as any).__viteDevServer = server;
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Extract Lit plugins for proper ordering
|
|
284
|
+
const litPlugins = integrationPlugins.filter(p => p.name?.includes("lit"));
|
|
285
|
+
const otherIntegrationPlugins = integrationPlugins.filter(p => !p.name?.includes("lit"));
|
|
286
|
+
|
|
287
|
+
// Page island transform: auto-wraps components with `island` prop
|
|
288
|
+
const pageTransformPlugin = pageIslandTransform({
|
|
289
|
+
pagesDir: preResolvedConfig.pagesDir,
|
|
290
|
+
layoutsDir: preResolvedConfig.layoutsDir,
|
|
291
|
+
modules: preResolvedConfig.modules,
|
|
292
|
+
verbose: preResolvedConfig.verbose,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return [
|
|
296
|
+
pageTransformPlugin,
|
|
297
|
+
...imagePlugins,
|
|
298
|
+
...litPlugins,
|
|
299
|
+
...mdxPlugins,
|
|
300
|
+
avalonPlugin,
|
|
301
|
+
sidecarPlugin,
|
|
302
|
+
...nitroPlugins,
|
|
303
|
+
...otherIntegrationPlugins,
|
|
304
|
+
] as PluginOption[];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function getResolvedConfig(): ResolvedAvalonConfig | undefined {
|
|
308
|
+
return globalThis.__avalonConfig;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function getPagesDir(): string {
|
|
312
|
+
return globalThis.__avalonConfig?.pagesDir ?? "src/pages";
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function getLayoutsDir(): string {
|
|
316
|
+
return globalThis.__avalonConfig?.layoutsDir ?? "src/layouts";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
export function getNitroConfig(): NitroConfigOutput | undefined {
|
|
321
|
+
return globalThis.__nitroConfig;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function isNitroEnabled(): boolean {
|
|
325
|
+
return globalThis.__nitroConfig !== undefined;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export type { AvalonPluginConfig, IntegrationName, ResolvedAvalonConfig, ImageConfig, ResolvedImageConfig } from "./types.ts";
|
|
329
|
+
export type { AvalonNitroConfig, NitroConfigOutput } from "../nitro/config.ts";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { createImagePlugin } from "../image-optimization.ts";
|
|
3
|
+
import type { ResolvedImageConfig } from "../types.ts";
|
|
4
|
+
|
|
5
|
+
describe("createImagePlugin", () => {
|
|
6
|
+
const defaultConfig: ResolvedImageConfig = {
|
|
7
|
+
enabled: true,
|
|
8
|
+
defaultFormat: "webp",
|
|
9
|
+
quality: 80,
|
|
10
|
+
widths: [200, 400, 600, 800, 1200],
|
|
11
|
+
removeMetadata: true,
|
|
12
|
+
include: /^[^?]+\.(heif|avif|jpeg|jpg|png|tiff|webp|gif)(\?.*)?$/,
|
|
13
|
+
exclude: "public/**/*",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns empty array when disabled", async () => {
|
|
21
|
+
const plugins = await createImagePlugin({ ...defaultConfig, enabled: false }, false);
|
|
22
|
+
expect(plugins).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns imagetools plugin when enabled", async () => {
|
|
26
|
+
const plugins = await createImagePlugin(defaultConfig, false);
|
|
27
|
+
expect(plugins.length).toBe(1);
|
|
28
|
+
expect(plugins[0].name).toBe("imagetools");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("logs info when verbose is true", async () => {
|
|
32
|
+
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
33
|
+
|
|
34
|
+
await createImagePlugin(defaultConfig, true);
|
|
35
|
+
|
|
36
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Image optimization enabled"));
|
|
37
|
+
consoleSpy.mockRestore();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("respects custom format setting", async () => {
|
|
41
|
+
const plugins = await createImagePlugin({ ...defaultConfig, defaultFormat: "avif" }, true);
|
|
42
|
+
expect(plugins.length).toBe(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("respects custom quality setting", async () => {
|
|
46
|
+
const plugins = await createImagePlugin({ ...defaultConfig, quality: 90 }, false);
|
|
47
|
+
expect(plugins.length).toBe(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("respects custom widths setting", async () => {
|
|
51
|
+
const plugins = await createImagePlugin({ ...defaultConfig, widths: [320, 640, 1280] }, false);
|
|
52
|
+
expect(plugins.length).toBe(1);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite Plugin Types for Avalon
|
|
3
|
+
*
|
|
4
|
+
* CONFIG PATH: Vite plugin runtime
|
|
5
|
+
* These types define the inline configuration passed to `avalon()` in
|
|
6
|
+
* `vite.config.ts`. The `resolveConfig()` function in `vite-plugin/config.ts`
|
|
7
|
+
* merges user options against `DEFAULT_CONFIG` to produce a `ResolvedAvalonConfig`.
|
|
8
|
+
*
|
|
9
|
+
* This path uses `IntegrationName[]` (simple string array) for integrations.
|
|
10
|
+
*
|
|
11
|
+
* There is a separate CLI config path (`schemas/integration-config.ts` →
|
|
12
|
+
* `config-loader.ts` → `startup.ts` → `cli.ts`) that reads `avalon.config.ts`
|
|
13
|
+
* from disk and uses `IntegrationConfigEntry[]` (objects with name/enabled/options).
|
|
14
|
+
* That path is NOT used during Vite plugin startup.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { AvalonNitroConfig } from "../nitro/config.ts";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Image optimization configuration
|
|
21
|
+
*/
|
|
22
|
+
export interface ImageConfig {
|
|
23
|
+
/**
|
|
24
|
+
* Enable image optimization via vite-imagetools
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default image format for optimized images
|
|
31
|
+
* @default "webp"
|
|
32
|
+
*/
|
|
33
|
+
defaultFormat?: "webp" | "avif" | "jpg" | "png";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Default image quality (1-100)
|
|
37
|
+
* @default 80
|
|
38
|
+
*/
|
|
39
|
+
quality?: number;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Breakpoint widths for srcset generation
|
|
43
|
+
* @default [200, 400, 600, 800, 1200]
|
|
44
|
+
*/
|
|
45
|
+
widths?: number[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether to strip EXIF and other metadata from images
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
removeMetadata?: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* File patterns to include for image processing
|
|
55
|
+
* @default /^[^?]+\.(heif|avif|jpeg|jpg|png|tiff|webp|gif)(\?.*)?$/
|
|
56
|
+
*/
|
|
57
|
+
include?: string | RegExp | (string | RegExp)[];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* File patterns to exclude from image processing
|
|
61
|
+
* @default "public/**\/*"
|
|
62
|
+
*/
|
|
63
|
+
exclude?: string | RegExp | (string | RegExp)[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolved image optimization configuration
|
|
68
|
+
*/
|
|
69
|
+
export interface ResolvedImageConfig {
|
|
70
|
+
enabled: boolean;
|
|
71
|
+
defaultFormat: "webp" | "avif" | "jpg" | "png";
|
|
72
|
+
quality: number;
|
|
73
|
+
widths: number[];
|
|
74
|
+
removeMetadata: boolean;
|
|
75
|
+
include: string | RegExp | (string | RegExp)[];
|
|
76
|
+
exclude: string | RegExp | (string | RegExp)[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Supported integration names
|
|
81
|
+
* These correspond to the @useavalon/* packages
|
|
82
|
+
*/
|
|
83
|
+
export type IntegrationName =
|
|
84
|
+
| "react"
|
|
85
|
+
| "preact"
|
|
86
|
+
| "vue"
|
|
87
|
+
| "svelte"
|
|
88
|
+
| "solid"
|
|
89
|
+
| "lit"
|
|
90
|
+
| "qwik";
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* MDX configuration options
|
|
94
|
+
*/
|
|
95
|
+
export interface MDXConfig {
|
|
96
|
+
/**
|
|
97
|
+
* JSX import source for MDX files
|
|
98
|
+
* @default "preact"
|
|
99
|
+
*/
|
|
100
|
+
jsxImportSource?: string;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Enable syntax highlighting for code blocks
|
|
104
|
+
* @default true
|
|
105
|
+
*/
|
|
106
|
+
syntaxHighlighting?: boolean;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Custom remark plugins
|
|
110
|
+
*/
|
|
111
|
+
remarkPlugins?: unknown[];
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Custom rehype plugins
|
|
115
|
+
*/
|
|
116
|
+
rehypePlugins?: unknown[];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Modular architecture configuration
|
|
121
|
+
* Enables co-located pages/layouts within feature modules
|
|
122
|
+
*/
|
|
123
|
+
export interface ModulesConfig {
|
|
124
|
+
/**
|
|
125
|
+
* Directory containing feature modules
|
|
126
|
+
* @example "app/modules"
|
|
127
|
+
*/
|
|
128
|
+
dir: string;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Name of the pages directory within each module
|
|
132
|
+
* @default "pages"
|
|
133
|
+
*/
|
|
134
|
+
pagesDirName?: string;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Name of the layouts directory within each module
|
|
138
|
+
* @default "layouts"
|
|
139
|
+
*/
|
|
140
|
+
layoutsDirName?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Configuration options for the Avalon Vite plugin
|
|
145
|
+
*/
|
|
146
|
+
export interface AvalonPluginConfig {
|
|
147
|
+
/**
|
|
148
|
+
* Directory containing page components for file-system routing
|
|
149
|
+
* @default "src/pages"
|
|
150
|
+
*/
|
|
151
|
+
pagesDir?: string;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Directory containing layout components
|
|
155
|
+
* @default "src/layouts"
|
|
156
|
+
*/
|
|
157
|
+
layoutsDir?: string;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Modular architecture configuration
|
|
161
|
+
* When set, discovers pages and layouts within feature modules
|
|
162
|
+
* Can be a string (just the dir) or full config object
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* // Simple - uses default 'pages' and 'layouts' folder names
|
|
167
|
+
* modules: 'app/modules'
|
|
168
|
+
*
|
|
169
|
+
* // Full config - customize folder names
|
|
170
|
+
* modules: {
|
|
171
|
+
* dir: 'app/modules',
|
|
172
|
+
* pagesDirName: 'views',
|
|
173
|
+
* layoutsDirName: 'layouts',
|
|
174
|
+
* }
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
modules?: string | ModulesConfig;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Framework integrations to activate
|
|
181
|
+
* Simply list the framework names - the integration packages handle the rest
|
|
182
|
+
* @example ["react", "svelte", "lit"]
|
|
183
|
+
*/
|
|
184
|
+
integrations?: IntegrationName[];
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* MDX processing configuration
|
|
188
|
+
*/
|
|
189
|
+
mdx?: MDXConfig;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Image optimization configuration
|
|
193
|
+
* When enabled, Avalon auto-injects vite-imagetools with sensible defaults.
|
|
194
|
+
* Set to `false` to disable, or pass an object to customize.
|
|
195
|
+
*
|
|
196
|
+
* @default { enabled: true }
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* // Use defaults (webp, quality 80, standard breakpoints)
|
|
201
|
+
* image: true
|
|
202
|
+
*
|
|
203
|
+
* // Customize
|
|
204
|
+
* image: {
|
|
205
|
+
* defaultFormat: 'avif',
|
|
206
|
+
* quality: 90,
|
|
207
|
+
* widths: [320, 640, 1024, 1920],
|
|
208
|
+
* }
|
|
209
|
+
*
|
|
210
|
+
* // Disable
|
|
211
|
+
* image: false
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
image?: boolean | ImageConfig;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Nitro server runtime configuration
|
|
218
|
+
* When provided, enables Nitro integration for universal deployment
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* nitro: {
|
|
223
|
+
* preset: 'vercel',
|
|
224
|
+
* streaming: true,
|
|
225
|
+
* routeRules: {
|
|
226
|
+
* '/api/**': { cors: true },
|
|
227
|
+
* '/static/**': { cache: { maxAge: 86400 } },
|
|
228
|
+
* },
|
|
229
|
+
* }
|
|
230
|
+
* ```
|
|
231
|
+
*/
|
|
232
|
+
nitro?: AvalonNitroConfig;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Enable verbose logging during development
|
|
236
|
+
* @default false
|
|
237
|
+
*/
|
|
238
|
+
verbose?: boolean;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Auto-discover integrations based on component file extensions
|
|
242
|
+
* When true, Avalon will automatically activate integrations
|
|
243
|
+
* based on the components you use, even if not listed in integrations
|
|
244
|
+
* @default true
|
|
245
|
+
*/
|
|
246
|
+
autoDiscoverIntegrations?: boolean;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Validate integrations on startup
|
|
250
|
+
* When true, Avalon will check that all integrations
|
|
251
|
+
* implement the required interface correctly
|
|
252
|
+
* @default true
|
|
253
|
+
*/
|
|
254
|
+
validateIntegrations?: boolean;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Show warnings for integration issues
|
|
258
|
+
* When true, Avalon will log warnings for non-critical
|
|
259
|
+
* integration problems
|
|
260
|
+
* @default true
|
|
261
|
+
*/
|
|
262
|
+
showWarnings?: boolean;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Enable lazy loading of integration Vite plugins
|
|
266
|
+
* When true (default), Avalon will only load Vite plugins for integrations
|
|
267
|
+
* that are actually used in your project, significantly improving cold start time.
|
|
268
|
+
*
|
|
269
|
+
* The lazy loading works by:
|
|
270
|
+
* 1. Scanning the islands directory to discover which frameworks are used
|
|
271
|
+
* 2. Only loading Vite plugins for those frameworks at startup
|
|
272
|
+
* 3. Loading additional plugins on-demand if new frameworks are encountered
|
|
273
|
+
*
|
|
274
|
+
* Set to false to load all configured integrations at startup (slower but predictable).
|
|
275
|
+
* @default true
|
|
276
|
+
*/
|
|
277
|
+
lazyIntegrations?: boolean;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Fully resolved MDX configuration with defaults applied
|
|
282
|
+
*/
|
|
283
|
+
export interface ResolvedMDXConfig {
|
|
284
|
+
jsxImportSource: string;
|
|
285
|
+
syntaxHighlighting: boolean;
|
|
286
|
+
remarkPlugins: unknown[];
|
|
287
|
+
rehypePlugins: unknown[];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Resolved modular architecture configuration
|
|
292
|
+
*/
|
|
293
|
+
export interface ResolvedModulesConfig {
|
|
294
|
+
dir: string;
|
|
295
|
+
pagesDirName: string;
|
|
296
|
+
layoutsDirName: string;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Fully resolved configuration with defaults applied
|
|
301
|
+
*/
|
|
302
|
+
export interface ResolvedAvalonConfig {
|
|
303
|
+
pagesDir: string;
|
|
304
|
+
layoutsDir: string;
|
|
305
|
+
modules: ResolvedModulesConfig | null;
|
|
306
|
+
integrations: IntegrationName[];
|
|
307
|
+
mdx: ResolvedMDXConfig;
|
|
308
|
+
image: ResolvedImageConfig;
|
|
309
|
+
verbose: boolean;
|
|
310
|
+
autoDiscoverIntegrations: boolean;
|
|
311
|
+
validateIntegrations: boolean;
|
|
312
|
+
showWarnings: boolean;
|
|
313
|
+
lazyIntegrations: boolean;
|
|
314
|
+
isDev: boolean;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Re-export Nitro configuration types for convenience
|
|
319
|
+
*/
|
|
320
|
+
export type { AvalonNitroConfig } from "../nitro/config.ts";
|
|
321
|
+
export type {
|
|
322
|
+
CacheOptions,
|
|
323
|
+
RouteRule,
|
|
324
|
+
NitroConfigOutput,
|
|
325
|
+
AvalonRuntimeConfig,
|
|
326
|
+
StaticAssetsConfig,
|
|
327
|
+
} from "../nitro/config.ts";
|