@useavalon/avalon 0.1.1 → 0.1.3
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/package.json
CHANGED
|
@@ -94,56 +94,36 @@ export class IntegrationRegistry {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
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
97
|
private async loadIntegration(name: string): Promise<Integration> {
|
|
98
|
+
const integrationKey = `${name}Integration`;
|
|
99
|
+
|
|
100
|
+
// 1. Try loading from the installed npm package first (@useavalon/<name>)
|
|
101
|
+
try {
|
|
102
|
+
const packageName = `@useavalon/${name}`;
|
|
103
|
+
const module = await import(/* @vite-ignore */ packageName);
|
|
104
|
+
const integration = module[integrationKey] || module.default;
|
|
105
|
+
if (integration) return integration as Integration;
|
|
106
|
+
} catch {
|
|
107
|
+
// Package not installed or import failed — try monorepo path
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 2. Monorepo fallback: resolve via packages/integrations/<name>/mod.ts
|
|
104
111
|
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
112
|
const monorepoRoot = findMonorepoRoot();
|
|
108
113
|
const integrationPath = join(monorepoRoot, "packages", "integrations", name, "mod.ts");
|
|
109
114
|
const fileUrl = `file://${integrationPath}`;
|
|
110
|
-
|
|
111
|
-
// Dynamic import with file:// URL bypasses Vite's SSR module loader
|
|
112
115
|
const module = await import(/* @vite-ignore */ fileUrl);
|
|
113
|
-
|
|
114
|
-
// Look for the integration export (e.g., preactIntegration)
|
|
115
|
-
const integrationKey = `${name}Integration`;
|
|
116
116
|
const integration = module[integrationKey] || module.default;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
);
|
|
117
|
+
if (integration) return integration as Integration;
|
|
118
|
+
} catch {
|
|
119
|
+
// Monorepo path also failed
|
|
146
120
|
}
|
|
121
|
+
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Failed to load integration for framework '${name}'. ` +
|
|
124
|
+
`Make sure @useavalon/${name} is installed.\n` +
|
|
125
|
+
`Install it with: bun add @useavalon/${name}`,
|
|
126
|
+
);
|
|
147
127
|
}
|
|
148
128
|
|
|
149
129
|
/**
|
package/src/nitro/renderer.ts
CHANGED
|
@@ -44,6 +44,8 @@ import {
|
|
|
44
44
|
discoverErrorPages,
|
|
45
45
|
type ErrorHandlerOptions,
|
|
46
46
|
} from './error-handler.ts';
|
|
47
|
+
import { h } from 'preact';
|
|
48
|
+
import preactRenderToString from 'preact-render-to-string';
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
51
|
* Resolved page route information
|
|
@@ -626,17 +628,27 @@ async function renderPageComponent(
|
|
|
626
628
|
context: NitroRenderContext,
|
|
627
629
|
_options: SSRRenderOptions,
|
|
628
630
|
): Promise<string> {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
+
const Component = pageModule.default as (props?: Record<string, unknown>) => unknown;
|
|
632
|
+
const metadata = pageModule.metadata || {};
|
|
631
633
|
|
|
632
|
-
//
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
634
|
+
// Call the page component (supports async components)
|
|
635
|
+
let vnode: unknown;
|
|
636
|
+
try {
|
|
637
|
+
const result = Component(pageProps);
|
|
638
|
+
vnode = result instanceof Promise ? await result : result;
|
|
639
|
+
} catch (err) {
|
|
640
|
+
console.error('[renderer] Error calling page component:', err);
|
|
641
|
+
vnode = h('div', null, 'Error rendering page');
|
|
642
|
+
}
|
|
637
643
|
|
|
638
|
-
|
|
639
|
-
|
|
644
|
+
// Render the vnode to HTML string using Preact SSR
|
|
645
|
+
let pageHtml: string;
|
|
646
|
+
try {
|
|
647
|
+
pageHtml = preactRenderToString(vnode as any);
|
|
648
|
+
} catch (err) {
|
|
649
|
+
console.error('[renderer] Error in preactRenderToString:', err);
|
|
650
|
+
pageHtml = '<div>Error rendering page</div>';
|
|
651
|
+
}
|
|
640
652
|
|
|
641
653
|
return `<!DOCTYPE html>
|
|
642
654
|
<html lang="en">
|
|
@@ -647,9 +659,10 @@ async function renderPageComponent(
|
|
|
647
659
|
${metadata.description ? `<meta name="description" content="${escapeHtml(String(metadata.description))}">` : ''}
|
|
648
660
|
</head>
|
|
649
661
|
<body>
|
|
650
|
-
<div id="app"
|
|
651
|
-
|
|
662
|
+
<div id="app">
|
|
663
|
+
${pageHtml}
|
|
652
664
|
</div>
|
|
665
|
+
<script type="module" src="/src/client/main.js"></script>
|
|
653
666
|
</body>
|
|
654
667
|
</html>`;
|
|
655
668
|
}
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import type { Plugin, ViteDevServer } from 'vite';
|
|
11
11
|
import { nitro as nitroVitePlugin } from 'nitro/vite';
|
|
12
12
|
import { stat as fsStat } from 'node:fs/promises';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
13
15
|
import type { ResolvedAvalonConfig } from './types.ts';
|
|
14
16
|
import { createNitroConfig, type AvalonNitroConfig, type NitroConfigOutput } from '../nitro/config.ts';
|
|
15
17
|
import type { PageModule } from '../nitro/types.ts';
|
|
@@ -27,6 +29,26 @@ import { collectCssFromModuleGraph, injectSsrCss } from '../render/collect-css.t
|
|
|
27
29
|
import { getUniversalCSSForHead } from '../islands/universal-css-collector.ts';
|
|
28
30
|
import { getUniversalHeadForInjection } from '../islands/universal-head-collector.ts';
|
|
29
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Resolves the absolute path to a file inside @useavalon/avalon's source tree.
|
|
34
|
+
*/
|
|
35
|
+
function resolveAvalonPackagePath(relativePath: string): string {
|
|
36
|
+
const require = createRequire(import.meta.url);
|
|
37
|
+
const modEntry = require.resolve('@useavalon/avalon');
|
|
38
|
+
const pkgRoot = dirname(modEntry);
|
|
39
|
+
return join(pkgRoot, relativePath);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolves the absolute path to a file inside an @useavalon/<name> integration package.
|
|
44
|
+
*/
|
|
45
|
+
function resolveIntegrationPackagePath(name: string, relativePath: string): string {
|
|
46
|
+
const require = createRequire(import.meta.url);
|
|
47
|
+
const modEntry = require.resolve(`@useavalon/${name}`);
|
|
48
|
+
const pkgRoot = dirname(modEntry);
|
|
49
|
+
return join(pkgRoot, relativePath);
|
|
50
|
+
}
|
|
51
|
+
|
|
30
52
|
export const VIRTUAL_MODULE_IDS = {
|
|
31
53
|
PAGE_ROUTES: 'virtual:avalon/page-routes',
|
|
32
54
|
ISLAND_MANIFEST: 'virtual:avalon/island-manifest',
|
|
@@ -342,18 +364,16 @@ async function handle404(
|
|
|
342
364
|
async function prewarmCoreModules(server: ViteDevServer, verbose?: boolean): Promise<void> {
|
|
343
365
|
const prewarmStart = performance.now();
|
|
344
366
|
|
|
367
|
+
const frameworkNames = ['react', 'vue', 'solid', 'svelte', 'lit', 'preact'] as const;
|
|
368
|
+
|
|
345
369
|
const coreModules = [
|
|
346
|
-
|
|
347
|
-
{ path: '
|
|
348
|
-
{ path: '
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
{ path: '../packages/integrations/solid/server/renderer.ts', assignTo: null },
|
|
354
|
-
{ path: '../packages/integrations/svelte/server/renderer.ts', assignTo: null },
|
|
355
|
-
{ path: '../packages/integrations/lit/server/renderer.ts', assignTo: null },
|
|
356
|
-
{ path: '../packages/integrations/preact/server/renderer.ts', assignTo: null },
|
|
370
|
+
{ path: resolveAvalonPackagePath('src/render/ssr.ts'), assignTo: 'ssr' as string | null },
|
|
371
|
+
{ path: resolveAvalonPackagePath('src/core/layout/enhanced-layout-resolver.ts'), assignTo: 'layout' as string | null },
|
|
372
|
+
{ path: resolveAvalonPackagePath('src/middleware/index.ts'), assignTo: null as string | null },
|
|
373
|
+
...frameworkNames.map(name => ({
|
|
374
|
+
path: resolveIntegrationPackagePath(name, 'server/renderer.ts'),
|
|
375
|
+
assignTo: null as string | null,
|
|
376
|
+
})),
|
|
357
377
|
];
|
|
358
378
|
|
|
359
379
|
const results = await Promise.allSettled(
|
|
@@ -1198,14 +1218,14 @@ async function renderPageToHtml(
|
|
|
1198
1218
|
|
|
1199
1219
|
try {
|
|
1200
1220
|
if (!cachedSSRModule) {
|
|
1201
|
-
cachedSSRModule = await server.ssrLoadModule('
|
|
1221
|
+
cachedSSRModule = await server.ssrLoadModule(resolveAvalonPackagePath('src/render/ssr.ts'));
|
|
1202
1222
|
}
|
|
1203
1223
|
// cachedSSRModule is the dynamically-loaded render/ssr.ts module;
|
|
1204
1224
|
// expected exports: renderToHtml, renderToHtmlWithLayouts
|
|
1205
1225
|
const ssrModule = cachedSSRModule as Record<string, unknown>;
|
|
1206
1226
|
|
|
1207
1227
|
if (!cachedLayoutModule) {
|
|
1208
|
-
cachedLayoutModule = await server.ssrLoadModule('
|
|
1228
|
+
cachedLayoutModule = await server.ssrLoadModule(resolveAvalonPackagePath('src/core/layout/enhanced-layout-resolver.ts'));
|
|
1209
1229
|
}
|
|
1210
1230
|
// cachedLayoutModule is the dynamically-loaded enhanced-layout-resolver.ts module;
|
|
1211
1231
|
// expected exports: EnhancedLayoutResolver, EnhancedLayoutResolverUtils
|
|
@@ -16,6 +16,8 @@ import type {
|
|
|
16
16
|
IntegrationName,
|
|
17
17
|
ResolvedAvalonConfig,
|
|
18
18
|
} from "./types.ts";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
19
21
|
import { resolveConfig, checkDirectoriesExist } from "./config.ts";
|
|
20
22
|
import { activateIntegrations, activateSingleIntegration } from "./integration-activator.ts";
|
|
21
23
|
import { discoverIntegrationsFromIslandUsage } from "./auto-discover.ts";
|
|
@@ -254,6 +256,18 @@ export async function avalon(config?: AvalonPluginConfig): Promise<PluginOption[
|
|
|
254
256
|
verbose: preResolvedConfig.verbose,
|
|
255
257
|
});
|
|
256
258
|
|
|
259
|
+
// Resolve the path to @useavalon/avalon's client entry for the /src/client/main.js alias.
|
|
260
|
+
// In the monorepo www/ project this is handled by a manual resolve.alias; for standalone
|
|
261
|
+
// projects the plugin must do it automatically.
|
|
262
|
+
let clientMainResolved: string | null = null;
|
|
263
|
+
try {
|
|
264
|
+
const require = createRequire(import.meta.url);
|
|
265
|
+
const clientEntry = require.resolve("@useavalon/avalon/client");
|
|
266
|
+
clientMainResolved = join(dirname(clientEntry), "main.js");
|
|
267
|
+
} catch {
|
|
268
|
+
// Inside the monorepo www/ project — it sets its own alias in vite.config.ts
|
|
269
|
+
}
|
|
270
|
+
|
|
257
271
|
// The main Avalon plugin
|
|
258
272
|
const avalonPlugin: Plugin = {
|
|
259
273
|
name: "avalon",
|
|
@@ -269,6 +283,16 @@ export async function avalon(config?: AvalonPluginConfig): Promise<PluginOption[
|
|
|
269
283
|
checkDirectoriesExist(resolvedConfig, resolvedViteConfig.root);
|
|
270
284
|
},
|
|
271
285
|
|
|
286
|
+
resolveId(id: string) {
|
|
287
|
+
// Resolve /src/client/main.js to the actual file inside @useavalon/avalon.
|
|
288
|
+
// The SSR renderer injects <script src="/src/client/main.js"> into HTML.
|
|
289
|
+
// Without this alias, standalone projects get a 404.
|
|
290
|
+
if (id === "/src/client/main.js" && clientMainResolved) {
|
|
291
|
+
return clientMainResolved;
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
},
|
|
295
|
+
|
|
272
296
|
async buildStart() {
|
|
273
297
|
await runAutoDiscovery(resolvedConfig, viteConfig?.root, activeIntegrations);
|
|
274
298
|
runValidation(resolvedConfig, activeIntegrations);
|