@useavalon/avalon 0.1.2 → 0.1.4
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
|
/**
|
|
@@ -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,11 +256,45 @@ export async function avalon(config?: AvalonPluginConfig): Promise<PluginOption[
|
|
|
254
256
|
verbose: preResolvedConfig.verbose,
|
|
255
257
|
});
|
|
256
258
|
|
|
259
|
+
// Pre-resolve paths for standalone projects.
|
|
260
|
+
// In the monorepo www/ project these are handled by manual resolve.alias.
|
|
261
|
+
const require = createRequire(import.meta.url);
|
|
262
|
+
|
|
263
|
+
let clientMainResolved: string | null = null;
|
|
264
|
+
try {
|
|
265
|
+
const clientEntry = require.resolve("@useavalon/avalon/client");
|
|
266
|
+
clientMainResolved = join(dirname(clientEntry), "main.js");
|
|
267
|
+
} catch {
|
|
268
|
+
// Monorepo — www/ sets its own alias
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Resolve /@useavalon/*/client virtual imports used by main.js
|
|
272
|
+
const integrationClientMap: Record<string, string | null> = {};
|
|
273
|
+
for (const name of ['preact', 'react', 'vue', 'svelte', 'solid', 'lit', 'qwik']) {
|
|
274
|
+
try {
|
|
275
|
+
integrationClientMap[`/@useavalon/${name}/client`] = require.resolve(`@useavalon/${name}/client`);
|
|
276
|
+
} catch {
|
|
277
|
+
integrationClientMap[`/@useavalon/${name}/client`] = null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
257
281
|
// The main Avalon plugin
|
|
258
282
|
const avalonPlugin: Plugin = {
|
|
259
283
|
name: "avalon",
|
|
260
284
|
enforce: "pre",
|
|
261
285
|
|
|
286
|
+
config() {
|
|
287
|
+
// Vite 8 uses OXC for TS/JSX transform. When the project tsconfig sets
|
|
288
|
+
// jsx: "react-jsx", Vite passes jsx options to OXC for ALL .ts files
|
|
289
|
+
// including those in node_modules/@useavalon. OXC rejects this for plain
|
|
290
|
+
// .ts files that don't contain JSX. Exclude @useavalon packages from OXC.
|
|
291
|
+
return {
|
|
292
|
+
oxc: {
|
|
293
|
+
exclude: [/node_modules\/@useavalon\//],
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
|
|
262
298
|
configResolved(resolvedViteConfig: ResolvedConfig) {
|
|
263
299
|
viteConfig = resolvedViteConfig;
|
|
264
300
|
const isDev = resolvedViteConfig.command === "serve";
|
|
@@ -269,6 +305,16 @@ export async function avalon(config?: AvalonPluginConfig): Promise<PluginOption[
|
|
|
269
305
|
checkDirectoriesExist(resolvedConfig, resolvedViteConfig.root);
|
|
270
306
|
},
|
|
271
307
|
|
|
308
|
+
resolveId(id: string) {
|
|
309
|
+
if (id === "/src/client/main.js" && clientMainResolved) {
|
|
310
|
+
return clientMainResolved;
|
|
311
|
+
}
|
|
312
|
+
if (id in integrationClientMap && integrationClientMap[id]) {
|
|
313
|
+
return integrationClientMap[id];
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
},
|
|
317
|
+
|
|
272
318
|
async buildStart() {
|
|
273
319
|
await runAutoDiscovery(resolvedConfig, viteConfig?.root, activeIntegrations);
|
|
274
320
|
runValidation(resolvedConfig, activeIntegrations);
|