@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useavalon/avalon",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Multi-framework islands architecture for the modern web",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
- 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
- );
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
- // SSR infrastructure
347
- { path: '../packages/avalon/src/render/ssr.ts', assignTo: 'ssr' },
348
- { path: '../packages/avalon/src/core/layout/enhanced-layout-resolver.ts', assignTo: 'layout' },
349
- { path: '../packages/avalon/src/middleware/index.ts', assignTo: null },
350
- // Framework renderers — prewarm so first island render is fast
351
- { path: '../packages/integrations/react/server/renderer.ts', assignTo: null },
352
- { path: '../packages/integrations/vue/server/renderer.ts', assignTo: null },
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('../packages/avalon/src/render/ssr.ts');
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('../packages/avalon/src/core/layout/enhanced-layout-resolver.ts');
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);