@useavalon/avalon 0.1.90 → 0.1.91-canary.20260531.55c9ced
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/bin/avalon.ts +17 -0
- package/dist/mod.d.ts +1 -0
- package/dist/src/build/page-island-transform.js +2 -2
- package/dist/src/client/server-island-hydrate.d.ts +12 -0
- package/dist/src/client/server-island-hydrate.js +1 -0
- package/dist/src/islands/framework-base-css.d.ts +2 -2
- package/dist/src/islands/framework-base-css.js +1 -1
- package/dist/src/islands/island.d.ts +7 -1
- package/dist/src/islands/island.js +1 -1
- package/dist/src/islands/types.d.ts +3 -0
- package/dist/src/nitro/renderer.js +1 -1
- package/dist/src/server-islands/encryption.d.ts +32 -0
- package/dist/src/server-islands/encryption.js +1 -0
- package/dist/src/server-islands/endpoint.d.ts +48 -0
- package/dist/src/server-islands/endpoint.js +4 -0
- package/dist/src/server-islands/manifest.d.ts +27 -0
- package/dist/src/server-islands/manifest.js +1 -0
- package/dist/src/server-islands/renderer.d.ts +18 -0
- package/dist/src/server-islands/renderer.js +17 -0
- package/dist/src/server-islands/route.d.ts +15 -0
- package/dist/src/server-islands/route.js +1 -0
- package/dist/src/server-islands/types.d.ts +17 -0
- package/dist/src/server-islands/types.js +1 -0
- package/dist/src/types/island-jsx.d.ts +4 -1
- package/dist/src/types/island-prop.d.ts +7 -0
- package/dist/src/types/virtual-modules.d.ts +12 -0
- package/dist/src/vite-plugin/nitro-integration.js +4 -1
- package/dist/src/vite-plugin/plugin.js +1 -1
- package/dist/src/vite-plugin/server-islands-plugin.d.ts +30 -0
- package/dist/src/vite-plugin/server-islands-plugin.js +3 -0
- package/package.json +5 -2
package/bin/avalon.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { generateKey } from "../src/server-islands/encryption.ts";
|
|
3
|
+
|
|
4
|
+
const command = process.argv[2];
|
|
5
|
+
|
|
6
|
+
if (command === "key") {
|
|
7
|
+
const key = generateKey();
|
|
8
|
+
console.log(`\nGenerated AVALON_KEY:\n`);
|
|
9
|
+
console.log(` ${key}\n`);
|
|
10
|
+
console.log(`Set this in your environment:\n`);
|
|
11
|
+
console.log(` export AVALON_KEY="${key}"\n`);
|
|
12
|
+
} else {
|
|
13
|
+
console.log("Usage: avalon <command>\n");
|
|
14
|
+
console.log("Commands:");
|
|
15
|
+
console.log(" key Generate a cryptographically random AES-256-GCM key");
|
|
16
|
+
process.exit(command === "--help" || command === "-h" ? 0 : 1);
|
|
17
|
+
}
|
package/dist/mod.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type { CacheConfig as IslandCacheConfig, CacheStats as IslandCacheStats,
|
|
|
23
23
|
export { clearCache, clearIslandCache, configureCache, getCacheConfig, getCacheStats, invalidateCacheForFile, invalidateCacheForPath, logCacheStats, } from "./src/islands/render-cache.ts";
|
|
24
24
|
export type { Framework, RenderParams, SvelteSSRCSSEntry } from "./src/islands/types.ts";
|
|
25
25
|
export { renderToHtml } from "./src/render/ssr.ts";
|
|
26
|
+
export type { ServerIslandProp } from "./src/server-islands/types.ts";
|
|
26
27
|
export type { IslandDirective } from "./src/types/island-prop.d.ts";
|
|
27
28
|
export type { NitroCoordinationPluginOptions, NitroIntegrationResult, } from "./src/vite-plugin/nitro-integration.ts";
|
|
28
29
|
export { createNitroCoordinationPlugin, createNitroIntegration, createVirtualModulesPlugin, getAvalonConfig, getViteDevServer, isDevelopmentMode, RESOLVED_VIRTUAL_IDS, VIRTUAL_MODULE_IDS, } from "./src/vite-plugin/nitro-integration.ts";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{dirname as e}from"node:path";function t(e){let t=[],n=/^[ \t]*import\s+([A-Z]\w*)\s+from\s+(['"][^'"]+['"])/gm,r=null;for(r=n.exec(e);r!==null;r=n.exec(e))t.push({localName:r[1],importPath:r[2].slice(1,-1),fullMatch:r[0].trimStart()});return t}function n(t,n,r=[]){if(t.startsWith(`/src/`)||t.startsWith(`/app/`)||t.startsWith(`/`))return t;for(let e of r){let n=e.find;if(typeof n==`string`){if(t===n||t.startsWith(`${n}/`)){let r=t.slice(n.length);return`${e.replacement.startsWith(`/`)?e.replacement:`/${e.replacement}`}${r}`}}else if(n instanceof RegExp&&n.test(t)){let r=e.replacement.startsWith(`/`)?e.replacement:`/${e.replacement}`;return t.replace(n,r)}}if(t.startsWith(`@/`))return`/app/${t.slice(2)}`;if(t.startsWith(`@shared/`))return`/app/shared/${t.slice(8)}`;if(t.startsWith(`@modules/`))return`/app/modules/${t.slice(9)}`;if(t.startsWith(`$components/`))return`/src/components/${t.slice(12)}`;if(t.startsWith(`$islands/`))return`/src/islands/${t.slice(9)}`;if(t.startsWith(`~/`))return`/src/${t.slice(2)}`;if(t.startsWith(`.`)){let r=n.replaceAll(`\\`,`/`),i=r.indexOf(`/app/`);if(i===-1&&(i=r.indexOf(`/src/`)),i!==-1){let n=e(r.slice(i)).split(`/`),a=t.split(`/`);for(let e of a)e===`..`?n.pop():e!==`.`&&n.push(e);return n.join(`/`)}}return`/src/${t.split(`/`).pop()}`}function r(e){if(e.endsWith(`.vue`))return`vue`;if(e.endsWith(`.svelte`))return`svelte`;if(e.includes(`.solid.`))return`solid`;if(e.includes(`.lit.`))return`lit`;if(e.includes(`.qwik.`))return`qwik`;if(e.includes(`.react.`))return`react`;if(e.endsWith(`.tsx`)||e.endsWith(`.jsx`))return`preact`}function i(e,t,n){let r=e.replaceAll(`\\`,`/`),i=t.replace(/^\//,``);if(r.includes(`/${i}/`)&&/\.(tsx|jsx)$/.test(r))return!0;if(n){let e=n.dir.replace(/^\//,``);if(RegExp(`/${e}/[^/]+/${n.pagesDirName}/`).test(r)&&/\.(tsx|jsx)$/.test(r))return!0}return!1}function a(e,t,n){let r=e.replaceAll(`\\`,`/`),i=t.replace(/^\//,``);if(r.includes(`/${i}/`)&&/\.(tsx|jsx)$/.test(r))return!0;if(n){let e=n.dir.replace(/^\//,``);if(RegExp(`/${e}/[^/]+/${n.layoutsDirName}/`).test(r)&&/\.(tsx|jsx)$/.test(r))return!0}return!1}const o=new Set([`qwik`]);function s(e){let t=r(e);return t!==void 0&&o.has(t)}function c(e,t){return t.some(t=>RegExp(`<${t}${String.raw`[\s][^>]*island[\s]*[={]`}`).test(e))}function l(e,t){return t.some(t=>s(t.importPath)?RegExp(`<${t.localName}${String.raw`[\s/>]`}`).test(e):!1)}function
|
|
2
|
-
`,s),n=t===-1?e.length:t+1;o+=e.slice(s,n),s=n;continue}if(e[s]===`/`&&e[s+1]===`*`){let t=e.indexOf(`*/`,s+2),n=t===-1?e.length:t+2;o+=e.slice(s,n),s=n;continue}if(!
|
|
1
|
+
import{dirname as e}from"node:path";function t(e){let t=[],n=/^[ \t]*import\s+([A-Z]\w*)\s+from\s+(['"][^'"]+['"])/gm,r=null;for(r=n.exec(e);r!==null;r=n.exec(e))t.push({localName:r[1],importPath:r[2].slice(1,-1),fullMatch:r[0].trimStart()});return t}function n(t,n,r=[]){if(t.startsWith(`/src/`)||t.startsWith(`/app/`)||t.startsWith(`/`))return t;for(let e of r){let n=e.find;if(typeof n==`string`){if(t===n||t.startsWith(`${n}/`)){let r=t.slice(n.length);return`${e.replacement.startsWith(`/`)?e.replacement:`/${e.replacement}`}${r}`}}else if(n instanceof RegExp&&n.test(t)){let r=e.replacement.startsWith(`/`)?e.replacement:`/${e.replacement}`;return t.replace(n,r)}}if(t.startsWith(`@/`))return`/app/${t.slice(2)}`;if(t.startsWith(`@shared/`))return`/app/shared/${t.slice(8)}`;if(t.startsWith(`@modules/`))return`/app/modules/${t.slice(9)}`;if(t.startsWith(`$components/`))return`/src/components/${t.slice(12)}`;if(t.startsWith(`$islands/`))return`/src/islands/${t.slice(9)}`;if(t.startsWith(`~/`))return`/src/${t.slice(2)}`;if(t.startsWith(`.`)){let r=n.replaceAll(`\\`,`/`),i=r.indexOf(`/app/`);if(i===-1&&(i=r.indexOf(`/src/`)),i!==-1){let n=e(r.slice(i)).split(`/`),a=t.split(`/`);for(let e of a)e===`..`?n.pop():e!==`.`&&n.push(e);return n.join(`/`)}}return`/src/${t.split(`/`).pop()}`}function r(e){if(e.endsWith(`.vue`))return`vue`;if(e.endsWith(`.svelte`))return`svelte`;if(e.includes(`.solid.`))return`solid`;if(e.includes(`.lit.`))return`lit`;if(e.includes(`.qwik.`))return`qwik`;if(e.includes(`.react.`))return`react`;if(e.endsWith(`.tsx`)||e.endsWith(`.jsx`))return`preact`}function i(e,t,n){let r=e.replaceAll(`\\`,`/`),i=t.replace(/^\//,``);if(r.includes(`/${i}/`)&&/\.(tsx|jsx)$/.test(r))return!0;if(n){let e=n.dir.replace(/^\//,``);if(RegExp(`/${e}/[^/]+/${n.pagesDirName}/`).test(r)&&/\.(tsx|jsx)$/.test(r))return!0}return!1}function a(e,t,n){let r=e.replaceAll(`\\`,`/`),i=t.replace(/^\//,``);if(r.includes(`/${i}/`)&&/\.(tsx|jsx)$/.test(r))return!0;if(n){let e=n.dir.replace(/^\//,``);if(RegExp(`/${e}/[^/]+/${n.layoutsDirName}/`).test(r)&&/\.(tsx|jsx)$/.test(r))return!0}return!1}const o=new Set([`qwik`]);function s(e){let t=r(e);return t!==void 0&&o.has(t)}function c(e,t){return t.some(t=>RegExp(`<${t}${String.raw`[\s][^>]*island[\s]*[={]`}`).test(e))}function l(e,t){return t.some(t=>RegExp(`<${t}${String.raw`[\s][^>]*server[\s]*[={]`}`).test(e))}function u(e,t){return t.some(t=>s(t.importPath)?RegExp(`<${t.localName}${String.raw`[\s/>]`}`).test(e):!1)}function d(e,t,i,a=[]){let s=new Map;for(let c of t){let t=n(c.importPath,i,a),l=r(t),u=RegExp(`<${c.localName}${String.raw`[\s][^>]*island[\s]*[={]`}`),d=RegExp(`<${c.localName}${String.raw`[\s][^>]*server[\s]*[={]`}`),f=u.test(e),p=d.test(e);if(f||p){s.set(c.localName,{srcPath:t,framework:l,importPath:c.importPath,autoIsland:!1,hasServerProp:p});continue}l&&o.has(l)&&RegExp(`<${c.localName}${String.raw`[\s/>]`}`).test(e)&&s.set(c.localName,{srcPath:t,framework:l,importPath:c.importPath,autoIsland:!0,hasServerProp:!1})}return s}function f(e,t){for(;t<e.length&&/\s/.test(e[t]);)t++;return t}function p(e,t){let n=e[t];for(t++;t<e.length&&e[t]!==n;)e[t]===`\\`&&t++,t++;return t<e.length?t+1:t}function m(e,t){for(t++;t<e.length&&e[t]!=="`";){if(e[t]===`\\`){t+=2;continue}if(e[t]===`$`&&e[t+1]===`{`){t=h(t+1,e);continue}t++}return t<e.length?t+1:t}function h(e,t){let n=e+1,r=1;for(;n<t.length&&r>0;){let e=t[n];e===`{`?(r++,n++):e===`}`?(r--,r>0&&n++):e===`'`||e===`"`||e==="`"?n=p(t,n):n++}return n<t.length?n+1:n}function g(e,t){let n=t+1,r=h(t,e);return{value:e.slice(n,r-1),endIdx:r}}function _(e,t){let n=e[t],r=t+1;for(;r<e.length&&e[r]!==n;)e[r]===`\\`&&r++,r++;return{value:`"${e.slice(t+1,r)}"`,endIdx:r+1}}function v(e,t){let n=t,r=t;for(;r<e.length&&/[a-zA-Z0-9_$]/.test(e[r]);)r++;let i=e.slice(n,r);if(!i)return null;if(r=f(e,r),e[r]!==`=`)return{name:i,value:null,endIdx:r};if(r=f(e,r+1),e[r]===`{`){let t=g(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}if(e[r]===`"`||e[r]===`'`){let t=_(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}return null}function y(e,t,n){if(e[t]===`/`&&e[t+1]===`>`)return{endIdx:t+2,selfClosing:!0};if(e[t]===`>`){let r=`</${n}>`,i=e.indexOf(r,t+1);return i===-1?null:{endIdx:i+r.length,selfClosing:!1}}return null}function b(e,t,n){let r=f(e,t+1+n.length),i=null,a=null,o=[];for(;r<e.length;){r=f(e,r);let t=y(e,r,n);if(t)return{endIdx:t.endIdx,islandProp:i,serverProp:a,otherProps:o};let s=v(e,r);if(!s)return null;if(r=s.endIdx,s.name===`island`)i=s.value??`{}`;else if(s.name===`server`)a=s.value??`{}`;else{let e=s.value===null?`${s.name}: true`:`${s.name}: ${s.value}`;o.push(e)}}return null}function x(e,t,n,r,i){let a=n?`, framework: "${n}"`:``,o=e.otherProps.length>0?`, props: { ${e.otherProps.join(`, `)} }`:``,s=`, component: ${i}`;if(r)return`{await __pageRenderIsland({ src: "`+t+`"`+a+s+o+`, ssr: true, ssrOnly: true })}`;if(e.serverProp){let n=`, server: (${e.serverProp})`,r=e.islandProp?`, island: (${e.islandProp})`:``;return`{await __pageRenderIsland({ src: "`+t+`"`+a+s+n+r+o+` })}`}let c=e.islandProp??``;return`{await __pageRenderIsland({ src: "`+t+`"`+a+s+`, ...(`+c+`)`+o+`, ssr: (`+c+`).ssr !== undefined ? (`+c+`).ssr : true })}`}function S(e,t,n){if(!e.startsWith(n,t))return!1;let r=t+n.length;return r>=e.length||!/[a-zA-Z0-9_$]/.test(e[r])}function C(e,t,n,r,i){let a=`<${t}`,o=``,s=0;for(;s<e.length;){if(e[s]==="`"){let t=s;s=m(e,s),o+=e.slice(t,s);continue}if(e[s]===`{`&&e[s+1]===`/`&&e[s+2]===`*`){let t=e.indexOf(`*/`,s+3);if(t!==-1){let n=t+2;for(;n<e.length&&/\s/.test(e[n]);)n++;if(n<e.length&&e[n]===`}`){o+=e.slice(s,n+1),s=n+1;continue}}}if(e[s]===`/`&&e[s+1]===`/`){let t=e.indexOf(`
|
|
2
|
+
`,s),n=t===-1?e.length:t+1;o+=e.slice(s,n),s=n;continue}if(e[s]===`/`&&e[s+1]===`*`){let t=e.indexOf(`*/`,s+2),n=t===-1?e.length:t+2;o+=e.slice(s,n),s=n;continue}if(!S(e,s,a)){o+=e[s],s++;continue}let c=b(e,s,t);if(!c||!c.islandProp&&!c.serverProp&&!i){let t=c?c.endIdx:s+1;o+=e.slice(s,t),s=t;continue}o+=x(c,n,r,i&&!c.islandProp,t),s=c.endIdx}return o}export function pageIslandTransform(e={}){let{pagesDir:n=`src/pages`,layoutsDir:r=`src/layouts`,modules:o=null}=e,s=[];return{name:`avalon:page-island-transform`,enforce:`pre`,configResolved(e){s=e.resolve?.alias??[]},transform(e,f){let p=a(f,r,o);if(!i(f,n,o)&&!p)return null;let m=t(e);if(m.length===0)return null;let h=m.map(e=>e.localName);if(!c(e,h)&&!l(e,h)&&!u(e,m))return null;let g=d(e,m,f,s);if(g.size===0)return null;let _=`import { renderIsland as __pageRenderIsland } from '@useavalon/avalon';\n${e}`;for(let[e,t]of g)_=C(_,e,t.srcPath,t.framework,t.autoIsland);return{code:_,map:null}}}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hydrate a server island element with a component, respecting the hydration strategy.
|
|
3
|
+
* Uses the framework integration system — same as normal island hydration.
|
|
4
|
+
*
|
|
5
|
+
* @param elementId - DOM id of the <avalon-server-island> wrapper
|
|
6
|
+
* @param componentPath - Module path to import the component from
|
|
7
|
+
* @param props - Component props
|
|
8
|
+
* @param condition - Hydration strategy (on:client, on:visible, on:interaction, on:idle)
|
|
9
|
+
* @param framework - Framework identifier (preact, react, solid, vue, svelte)
|
|
10
|
+
* @param renderId - Optional framework hydration key (Solid uses this to match data-hk markers)
|
|
11
|
+
*/
|
|
12
|
+
export declare function hydrateServerIsland(elementId: string, componentPath: string, props: Record<string, unknown>, condition?: string, framework?: string, renderId?: string): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{loadIntegrationModule as e}from"virtual:avalon/integration-loader";function t(e){if(e.default)return e.default;for(let t of Object.values(e))if(typeof t==`function`)return t;return e}const n=5e3;export async function hydrateServerIsland(r,i,a,o=`on:client`,s=`preact`,c){let l=document.getElementById(r);if(!l||l.dataset.hydrated)return;c&&(l.dataset.solidRenderId=c);let u=async()=>{if(l.dataset.hydrated)return;let n=t(await import(i)),r=await e(s);if(!r.hydrate||typeof r.hydrate!=`function`)throw Error(`Integration ${s} does not export a hydrate function`);await r.hydrate(l,n,a),l.dataset.hydrated=`true`};if(o===`on:client`)await u();else if(o===`on:visible`){let e=l.firstElementChild||l,t=new IntersectionObserver(e=>{e[0].isIntersecting&&(u(),t.disconnect())},{rootMargin:`50px`,threshold:0});t.observe(e)}else if(o===`on:interaction`){let e=()=>{u(),l.removeEventListener(`mouseenter`,e),l.removeEventListener(`focusin`,e),l.removeEventListener(`click`,e)};l.addEventListener(`mouseenter`,e),l.addEventListener(`focusin`,e),l.addEventListener(`click`,e)}else o===`on:idle`?`requestIdleCallback`in globalThis?requestIdleCallback(()=>u(),{timeout:n}):setTimeout(()=>u(),n):await u()}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Framework Baseline CSS
|
|
3
3
|
*
|
|
4
4
|
* Provides default styling for Avalon's framework-emitted custom elements
|
|
5
|
-
* (`<avalon-island>`, `<avalon-page>`, `<avalon-page-content>`).
|
|
5
|
+
* (`<avalon-island>`, `<avalon-page>`, `<avalon-page-content>`, `<avalon-server-island>`).
|
|
6
6
|
*
|
|
7
7
|
* These elements are used as DOM anchors for hydration and content
|
|
8
8
|
* placement. They must be transparent to layout — otherwise an island
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function injectFrameworkBaseCSS(html: string): string;
|
|
35
35
|
/** Exposed for tests */
|
|
36
|
-
export declare const __FRAMEWORK_BASE_CSS = "avalon-island,avalon-page,avalon-page-content{display:contents}";
|
|
36
|
+
export declare const __FRAMEWORK_BASE_CSS = "avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=`avalon-island,avalon-page,avalon-page-content{display:contents}`,t=`<style data-avalon-base="true">${e}</style>`;export function injectFrameworkBaseCSS(e){return e.includes(`data-avalon-base="true"`)||!e.includes(`<head>`)&&!e.includes(`<head `)?e:e.replace(/<head(\s[^>]*)?>/i,e=>`${e}${t}`)}export const __FRAMEWORK_BASE_CSS=e;
|
|
1
|
+
const e=`avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}`,t=`<style data-avalon-base="true">${e}</style>`;export function injectFrameworkBaseCSS(e){return e.includes(`data-avalon-base="true"`)||!e.includes(`<head>`)&&!e.includes(`<head `)?e:e.replace(/<head(\s[^>]*)?>/i,e=>`${e}${t}`)}export const __FRAMEWORK_BASE_CSS=e;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { JSX } from "preact";
|
|
2
2
|
import type { ViteDevServer } from "vite";
|
|
3
3
|
import type { AnalyzerOptions } from "../core/components/component-analyzer.ts";
|
|
4
|
+
import type { ServerIslandProp } from "../server-islands/types.ts";
|
|
5
|
+
import type { IslandDirective } from "../types/island-prop.d.ts";
|
|
4
6
|
import type { Framework } from "./types.ts";
|
|
5
7
|
declare global {
|
|
6
8
|
var __viteDevServer: ViteDevServer | undefined;
|
|
@@ -36,6 +38,10 @@ export interface IslandProps {
|
|
|
36
38
|
hydrationData?: Record<string, unknown>;
|
|
37
39
|
/** Pre-imported component reference (avoids dynamic import in bundled SSR) */
|
|
38
40
|
component?: unknown;
|
|
41
|
+
/** Server island configuration — defers rendering to a server endpoint after page load */
|
|
42
|
+
server?: ServerIslandProp;
|
|
43
|
+
/** Island directive for combined server + client islands */
|
|
44
|
+
island?: IslandDirective;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* Universal Island component – renders `<avalon-island>` custom elements for better DOM structure.
|
|
@@ -60,4 +66,4 @@ export default function Island({ src, condition, conditionArg, props, children,
|
|
|
60
66
|
* Error isolation: If SSR fails, returns an error placeholder instead of throwing,
|
|
61
67
|
* allowing the page to continue rendering other islands.
|
|
62
68
|
*/
|
|
63
|
-
export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, }: IslandProps): Promise<JSX.Element>;
|
|
69
|
+
export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, server, island, }: IslandProps): Promise<JSX.Element>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Fragment as e,h as t}from"preact";import{getIslandBundlePath as n}from"../build/island-manifest.js";import{
|
|
1
|
+
import{Fragment as e,h as t}from"preact";import{getIslandBundlePath as n}from"../build/island-manifest.js";import{addToManifest as r,generateComponentId as i}from"../server-islands/manifest.js";import{renderServerIsland as a}from"../server-islands/renderer.js";import{devError as o,devLog as s,devWarn as c,isDev as l,logRenderTiming as u}from"../utils/dev-logger.js";import{analyzeComponentFile as d,renderComponentSSROnly as f}from"./component-analysis.js";import{detectFramework as p}from"./framework-detection.js";import{isCustomDirective as m,serializeDirectiveScript as h}from"./hydration-directives.js";import{detectFrameworkFromPath as g,loadIntegration as _}from"./integration-loader.js";import{addModulepreload as v}from"./modulepreload-collector.js";import{generatePerIslandScript as y}from"./per-island-script.js";import{addUniversalCSS as b}from"./universal-css-collector.js";import{addUniversalHead as x}from"./universal-head-collector.js";function S(e){return`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}`}function C(){return globalThis.__avalonHydrationMode===void 0?globalThis.__viteDevServer?!1:typeof __AVALON_PER_ISLAND__<`u`?__AVALON_PER_ISLAND__:!l():globalThis.__avalonHydrationMode===`per-island`}function w(r,i){if(!C()||i.shouldSkipHydration)return r;let a=n(i.src);i.condition===`on:client`&&v(a);let o=m(i.condition),s=o?h(i.condition):void 0;return t(e,null,r,t(`div`,{dangerouslySetInnerHTML:{__html:y({islandId:i.islandId,componentSrc:a,framework:i.framework,condition:i.condition,conditionArg:i.conditionArg,propsJson:JSON.stringify(i.props),isCustomDirective:o,directiveScript:s??void 0})},"data-island-script":``}))}function T(e){let t={};e.renderId&&(t[`data-solid-render-id`]=e.renderId);let n=e.metadata;return n?.tagName&&(t[`data-tag-name`]=n.tagName),t}function E(e,t,r,i,a){let o={"data-condition":t,"data-src":n(e),"data-props":JSON.stringify(r),"data-render-strategy":`hydrate`,...T(i)};if(m(t)){o[`data-custom-directive`]=t;let e=h(t);e&&(o[`data-directive-script`]=e)}return a&&(o[`data-condition-arg`]=a),o}function D(e){return e.startsWith(`<script`)?`script`:e.startsWith(`<style`)?`style`:e.startsWith(`<meta`)?`meta`:e.startsWith(`<link`)?`link`:e.includes(`window._$HY`)||e.includes(`_$HY=`)?`script`:`other`}function O(e){let t=e.match(/<style[^>]*>([\s\S]*?)<\/style>/i);return t?t[1].trim():null}function k(e,t,n,r){if(e.css&&b(e.css,t,n,e.scopeId),e.head){let i=e.head.trim(),a=D(i);if(a===`style`){let a=O(i);a&&(s(`${r} Extracting CSS from head <style> tag`),b(a,t,n,e.scopeId));return}x(e.head,t,n,a)}}function A(e){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,conditionArg:c,props:l,hydrationData:u,children:d}=e,f={id:n,"data-framework":r},p=i?{"data-render-strategy":`ssr-only`}:E(a,o,l,u,c);r===`lit`&&s(`🔍 [Island Component] ${a} - Lit hydration data:`,{hydrationDataKeys:Object.keys(u),metadata:u.metadata});let m={...f,...p},h;return h=typeof d==`string`?t(`avalon-island`,{...m,dangerouslySetInnerHTML:{__html:d}}):t(`avalon-island`,m,d),w(h,{islandId:n,src:a,condition:o,conditionArg:c,props:l,framework:r,shouldSkipHydration:i})}function j(e){let{islandId:r,detectedFramework:i,shouldSkipHydration:a,src:o,condition:s,props:c,hydrationData:l,conditionArg:u}=e;if(a)return t(`avalon-island`,{id:r,"data-render-strategy":`ssr-only`,"data-framework":i});let d={id:r,"data-condition":s,"data-src":n(o),"data-props":JSON.stringify(c),"data-render-strategy":`hydrate`,"data-framework":i,...T(l)};if(m(s)){d[`data-custom-directive`]=s;let e=h(s);e&&(d[`data-directive-script`]=e)}return u&&(d[`data-condition-arg`]=u),w(t(`avalon-island`,d),{islandId:r,src:o,condition:s,conditionArg:u,props:c,framework:i,shouldSkipHydration:a})}export default function M({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:a=t!==`on:client`,framework:o,ssrOnly:l=!1,renderOptions:u={},hydrationData:d={}}){let f=S(e),p=l||!!u.forceSSROnly,m=o||g(e),h=i!=null&&i!==``;return s(`🔍 [Island Component] ${e}`,{ssr:a,ssrOnly:l,hasChildren:h,framework:o,condition:t}),a&&h?A({islandId:f,detectedFramework:m,shouldSkipHydration:p,src:e,condition:t,conditionArg:n,props:r,hydrationData:d,children:i}):(a&&!h&&p&&c(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),j({islandId:f,detectedFramework:m,shouldSkipHydration:p,src:e,condition:t,props:r,hydrationData:d,conditionArg:n}))}function N(e,r){let i=r instanceof Error?r.message:String(r);return o(`🚨 Island SSR failed for ${e}:`,r),r instanceof Error&&r.stack&&o(`Stack trace:`,r.stack),t(`avalon-island`,{id:S(e),"data-src":n(e),"data-ssr-error":i,"data-render-strategy":`client-only`})}async function P({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u,component:d}){let f=`🏝️ [${e}]`;if(!a||i)return M({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u});let p;try{p=await _(s)}catch(i){return o(`${f} Failed to load ${s} integration:`,i),M({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}try{let i=await p.render({component:d??null,props:r,src:e,condition:t,ssrOnly:c,viteServer:globalThis.__viteDevServer,isDev:l()});return k(i,e,s,f),M({src:e,condition:t,conditionArg:n,props:r,children:i.html,ssr:!0,framework:s,ssrOnly:c,renderOptions:u,hydrationData:c?void 0:i.hydrationData})}catch(i){return o(`${f} Fast path SSR failed:`,i),M({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}}async function F(e,t,n,r){if(t||n.detectScripts===!1)return t;try{let t=await d(e,n);if(t.decision.warnings?.length)for(let e of t.decision.warnings)c(`${r} Analysis warning: ${e}`);return!t.decision.shouldHydrate}catch(e){return c(`${r} Component analysis failed:`,e),t}}async function I(e){return e.endsWith(`.vue`)?`vue`:e.endsWith(`.svelte`)?`svelte`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)||e.endsWith(`.ts`)||e.endsWith(`.js`)?p(e):`unknown`}async function L(e,t,n,r,i,a,o){let s=await I(e),c=s,u=await(await R(s,a)).render({component:o??null,props:n,src:e,condition:t,ssrOnly:r,viteServer:globalThis.__viteDevServer,isDev:l()});return k(u,e,s,a),M({src:e,condition:t,props:n,children:u.html,ssr:!0,framework:c,ssrOnly:r,renderOptions:i,hydrationData:r?void 0:u.hydrationData})}async function R(e,t){try{s(`${t} Loading integration for framework: ${e}`);let n=await _(e);return s(`${t} ✅ Integration loaded successfully`),n}catch(n){throw o(`${t} Failed to load ${e} integration:`,n),Error(`Failed to load integration for framework '${e}'. Make sure @useavalon/${e} is installed.\nInstall it with: deno add @useavalon/${e}`,{cause:n})}}export async function renderIsland({src:o,condition:s=`on:client`,conditionArg:c,props:d={},children:f,ssr:p=s!==`on:client`,framework:m,ssrOnly:h=!1,renderOptions:_={},component:v,server:y,island:b}){let x=l()?performance.now():0,S=`🏝️ [${o}]`;try{if(y){let s=i(o);r(s,o);let c=m||g(o);return t(e,null,t(`div`,{dangerouslySetInnerHTML:{__html:a(s,d,y,b,b?n(o):void 0,o,c)},"data-server-island-wrapper":``}))}return h&&!p&&(p=!0),m?await P({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:p,framework:m,ssrOnly:h,renderOptions:_,component:v}):await z({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:p,ssrOnly:h,renderOptions:_,logPrefix:S,component:v})}catch(e){return N(o,e)}finally{l()&&u(o,performance.now()-x)}}async function z(e){let{src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,ssrOnly:l,renderOptions:u,logPrefix:d,component:f}=e;if(s(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:c,ssrOnly:l,hasChildren:!!a,condition:n}),await F(t,l,u,d))return B(t,n,i,a,c,u,d);if(!c||a)return M({src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,renderOptions:u});try{return await L(t,n,i,l,u,d,f)}catch(e){let a=await I(t);return o(`${d} Framework rendering failed:`,e),M({src:t,condition:n,conditionArg:r,props:i,ssr:!1,framework:a,renderOptions:u})}}function B(e,t,n,r,i,a,s){return i&&!r?f({src:e,condition:t,props:n,renderOptions:a}).catch(r=>(o(`${s} SSR failed for SSR-only component:`,r),M({src:e,condition:t,props:n,ssr:!1,ssrOnly:!0,renderOptions:a}))):M({src:e,condition:t,props:n,children:r,ssr:i,ssrOnly:!0,renderOptions:a})}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSX } from "preact";
|
|
2
2
|
import type { AnalyzerOptions } from "../core/components/component-analyzer.ts";
|
|
3
|
+
import type { ServerIslandProp } from "../server-islands/types.ts";
|
|
3
4
|
/**
|
|
4
5
|
* Framework type for island components
|
|
5
6
|
* Represents the supported UI frameworks for island architecture
|
|
@@ -28,6 +29,8 @@ export interface IslandProps {
|
|
|
28
29
|
ssrOnly?: boolean;
|
|
29
30
|
/** Component render options for intelligent detection */
|
|
30
31
|
renderOptions?: AnalyzerOptions;
|
|
32
|
+
/** Server island configuration — defers rendering to a server endpoint after page load */
|
|
33
|
+
server?: ServerIslandProp;
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
36
|
* Parameters for framework-specific render functions
|
|
@@ -181,4 +181,4 @@ import{getRequestURL as e}from"h3";import{h as t}from"preact";import n from"prea
|
|
|
181
181
|
</details>
|
|
182
182
|
`:``}
|
|
183
183
|
</div>
|
|
184
|
-
`}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function D(e,t,n){return async()=>(e.value??=await a({baseDir:t,devMode:n}),e.value)}function O(e,t,n){return async(r,i)=>e?c(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:t,isDev:n=!1,enableCustomErrorPages:a=!0}=e,c={isDev:n,avalonConfig:t,loadPageModule:e.loadPageModule,pagesDir:t.pagesDir};a&&s(c).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let u=D({value:null},t.srcDir||`src`,n),m=O(a,c,n);async function h(a){let s=getRequestURL(a).pathname;try{let c=await o(a,await u(),{devMode:n});if(c)return n&&console.log(`[renderer] Middleware terminated request for ${s}`),c;let f=a.context.route,p=null;if(p=f||(e.resolvePageRoute?await e.resolvePageRoute(s,t.pagesDir):await k(s,t.pagesDir)),!p)return m(l(`Page not found: ${s}`),a);let h=e.loadPageModule?await e.loadPageModule(p.filePath):await A(p.filePath),g=createRenderContext(a,a.context.params||p.params);if(e.resolveLayouts&&(g.layoutContext={layouts:await e.resolveLayouts(s,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(h,g,{onShellReady:()=>{setResponseHeader(a,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(h,g,{},e.wrapWithLayouts),a=injectHydrationScript(t.html,n);return a=r(a),a=i(a),new Response(a,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Renderer Error]`,e),m(e instanceof Error?e:Error(String(e)),a)}}return Object.assign(h,{async fetch(a){let o=new URL(a.url,`http://localhost`).pathname;try{let s=null;if(s=e.resolvePageRoute?await e.resolvePageRoute(o,t.pagesDir):await k(o,t.pagesDir),!s)return createErrorResponse(l(`Page not found: ${o}`),n);let c=await renderPage(e.loadPageModule?await e.loadPageModule(s.filePath):await A(s.filePath),createRenderContextFromRequest(a,s.params),{},e.wrapWithLayouts),u=injectHydrationScript(c.html,n);return u=r(u),u=i(u),new Response(u,{status:c.statusCode,headers:c.headers})}catch(e){return console.error(`[Nitro Renderer .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}async function k(e,t){return e===`/`||e===``?{filePath:`src/pages/index.tsx`,pattern:`/`,params:{}}:{filePath:`src/pages/${e.replace(/^\//,``).replace(/\/$/,``)}.tsx`,pattern:e,params:{}}}async function A(e){return{default:()=>null,metadata:{title:`Avalon Page`}}}export function createNitroCatchAllRenderer(e){let{avalonConfig:t,isDev:n=!1,loadPageModule:a,resolveLayouts:c,enableCustomErrorPages:u=!0}=e,m={isDev:n,avalonConfig:t,loadPageModule:a,pagesDir:t.pagesDir};u&&s(m).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let h=D({value:null},t.srcDir||`src`,n),_=O(u,m,n);async function v(s){let u=getRequestURL(s).pathname;try{let f=await o(s,await h(),{devMode:n});if(f)return n&&console.log(`[renderer] Middleware terminated request for ${u}`),f;let p=s.context.params||{},m=p.slug||u.replace(/^\//,``)||`index`,g=`${t.pagesDir}/${m}.tsx`,v;try{v=await a(g)}catch(e){try{v=await a(`${t.pagesDir}/${m}/index.tsx`)}catch(r){return n&&(console.debug(`[renderer] Page not found: ${g}`,e),console.debug(`[renderer] Index fallback not found: ${t.pagesDir}/${m}/index.tsx`,r)),_(l(`Page not found: ${u}`),s)}}let y=createRenderContext(s,p);if(c&&(y.layoutContext={layouts:await c(u,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(v,y,{onShellReady:()=>{setResponseHeader(s,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(v,y,{},e.wrapWithLayouts),a=injectHydrationScript(t.html,n);return a=r(a),a=i(a),new Response(a,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Catch-All Renderer Error]`,e),_(e instanceof Error?e:Error(String(e)),s)}}return Object.assign(v,{async fetch(o){let s=new URL(o.url,`http://localhost`).pathname;try{let c=s.replace(/^\//,``)||`index`,u=`${t.pagesDir}/${c}.tsx`,d;try{d=await a(u)}catch{try{d=await a(`${t.pagesDir}/${c}/index.tsx`)}catch{return createErrorResponse(l(`Page not found: ${s}`),n)}}let p=createRenderContextFromRequest(o),m=await renderPage(d,p,{},e.wrapWithLayouts),h=injectHydrationScript(m.html,n);return h=r(h),h=i(h),new Response(h,{status:m.statusCode,headers:m.headers})}catch(e){return console.error(`[Nitro CatchAll .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}export{clearMiddlewareCache as clearRendererMiddlewareCache}from"../middleware/index.js";
|
|
184
|
+
`}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function D(e,t,n){return async()=>(e.value??=await a({baseDir:t,devMode:n}),e.value)}function O(e,t,n){return async(r,i)=>e?c(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:t,isDev:n=!1,enableCustomErrorPages:a=!0}=e,c={isDev:n,avalonConfig:t,loadPageModule:e.loadPageModule,pagesDir:t.pagesDir};a&&s(c).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let u=D({value:null},t.srcDir||`src`,n),m=O(a,c,n);async function h(a){let s=getRequestURL(a).pathname;if(s.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let c=await o(a,await u(),{devMode:n});if(c)return n&&console.log(`[renderer] Middleware terminated request for ${s}`),c;let f=a.context.route,p=null;if(p=f||(e.resolvePageRoute?await e.resolvePageRoute(s,t.pagesDir):await k(s,t.pagesDir)),!p)return m(l(`Page not found: ${s}`),a);let h=e.loadPageModule?await e.loadPageModule(p.filePath):await A(p.filePath),g=createRenderContext(a,a.context.params||p.params);if(e.resolveLayouts&&(g.layoutContext={layouts:await e.resolveLayouts(s,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(h,g,{onShellReady:()=>{setResponseHeader(a,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(h,g,{},e.wrapWithLayouts),a=injectHydrationScript(t.html,n);return a=r(a),a=i(a),new Response(a,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Renderer Error]`,e),m(e instanceof Error?e:Error(String(e)),a)}}return Object.assign(h,{async fetch(a){let o=new URL(a.url,`http://localhost`).pathname;if(o.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let s=null;if(s=e.resolvePageRoute?await e.resolvePageRoute(o,t.pagesDir):await k(o,t.pagesDir),!s)return createErrorResponse(l(`Page not found: ${o}`),n);let c=await renderPage(e.loadPageModule?await e.loadPageModule(s.filePath):await A(s.filePath),createRenderContextFromRequest(a,s.params),{},e.wrapWithLayouts),u=injectHydrationScript(c.html,n);return u=r(u),u=i(u),new Response(u,{status:c.statusCode,headers:c.headers})}catch(e){return console.error(`[Nitro Renderer .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}async function k(e,t){return e===`/`||e===``?{filePath:`src/pages/index.tsx`,pattern:`/`,params:{}}:{filePath:`src/pages/${e.replace(/^\//,``).replace(/\/$/,``)}.tsx`,pattern:e,params:{}}}async function A(e){return{default:()=>null,metadata:{title:`Avalon Page`}}}export function createNitroCatchAllRenderer(e){let{avalonConfig:t,isDev:n=!1,loadPageModule:a,resolveLayouts:c,enableCustomErrorPages:u=!0}=e,m={isDev:n,avalonConfig:t,loadPageModule:a,pagesDir:t.pagesDir};u&&s(m).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let h=D({value:null},t.srcDir||`src`,n),_=O(u,m,n);async function v(s){let u=getRequestURL(s).pathname;if(u.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let f=await o(s,await h(),{devMode:n});if(f)return n&&console.log(`[renderer] Middleware terminated request for ${u}`),f;let p=s.context.params||{},m=p.slug||u.replace(/^\//,``)||`index`,g=`${t.pagesDir}/${m}.tsx`,v;try{v=await a(g)}catch(e){try{v=await a(`${t.pagesDir}/${m}/index.tsx`)}catch(r){return n&&(console.debug(`[renderer] Page not found: ${g}`,e),console.debug(`[renderer] Index fallback not found: ${t.pagesDir}/${m}/index.tsx`,r)),_(l(`Page not found: ${u}`),s)}}let y=createRenderContext(s,p);if(c&&(y.layoutContext={layouts:await c(u,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(v,y,{onShellReady:()=>{setResponseHeader(s,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(v,y,{},e.wrapWithLayouts),a=injectHydrationScript(t.html,n);return a=r(a),a=i(a),new Response(a,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Catch-All Renderer Error]`,e),_(e instanceof Error?e:Error(String(e)),s)}}return Object.assign(v,{async fetch(o){let s=new URL(o.url,`http://localhost`).pathname;if(s.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let c=s.replace(/^\//,``)||`index`,u=`${t.pagesDir}/${c}.tsx`,d;try{d=await a(u)}catch{try{d=await a(`${t.pagesDir}/${c}/index.tsx`)}catch{return createErrorResponse(l(`Page not found: ${s}`),n)}}let p=createRenderContextFromRequest(o),m=await renderPage(d,p,{},e.wrapWithLayouts),h=injectHydrationScript(m.html,n);return h=r(h),h=i(h),new Response(h,{status:m.statusCode,headers:m.headers})}catch(e){return console.error(`[Nitro CatchAll .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}export{clearMiddlewareCache as clearRendererMiddlewareCache}from"../middleware/index.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a new random AES-256 key as a base64 string.
|
|
3
|
+
*/
|
|
4
|
+
export declare function generateKey(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Gets the encryption key from the AVALON_KEY env var, or falls back to a
|
|
7
|
+
* per-process build-time key in development.
|
|
8
|
+
*
|
|
9
|
+
* In production, AVALON_KEY is REQUIRED: a per-process key would cause
|
|
10
|
+
* cross-process decryption failures (one instance encrypts, another can't
|
|
11
|
+
* decrypt). We throw to surface the misconfiguration instead of failing
|
|
12
|
+
* silently at request time.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getKey(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Encrypts data using AES-256-GCM.
|
|
17
|
+
* Output format: base64url(iv + ciphertext + authTag)
|
|
18
|
+
*
|
|
19
|
+
* @param data - The plaintext string to encrypt
|
|
20
|
+
* @param key - Optional base64-encoded key. Defaults to getKey().
|
|
21
|
+
* @returns base64url-encoded encrypted string
|
|
22
|
+
*/
|
|
23
|
+
export declare function encrypt(data: string, key?: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Decrypts a base64url-encoded AES-256-GCM encrypted string.
|
|
26
|
+
*
|
|
27
|
+
* @param data - The base64url-encoded encrypted string
|
|
28
|
+
* @param key - Optional base64-encoded key. Defaults to getKey().
|
|
29
|
+
* @returns The decrypted plaintext string
|
|
30
|
+
* @throws Error if decryption fails (tampered data, wrong key, etc.)
|
|
31
|
+
*/
|
|
32
|
+
export declare function decrypt(data: string, key?: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createCipheriv as e,createDecipheriv as t,randomBytes as n}from"node:crypto";const r=`aes-256-gcm`;let i,a=!1;export function generateKey(){return n(32).toString(`base64`)}export function getKey(){let e=process.env.AVALON_KEY;if(e){let t=Buffer.from(e,`base64`);if(t.length!==32)throw Error(`AVALON_KEY must be a base64-encoded 256-bit (32-byte) key. Got ${t.length} bytes.`);return e}if(process.env.NODE_ENV===`production`)throw Error("AVALON_KEY is required in production for server islands. Generate one with `npx avalon key` and set it as an environment variable so all server instances share the same encryption secret.");return a||(a=!0,console.warn(`[avalon] AVALON_KEY not set — using a per-process key for server islands. This only works for single-process development. Set AVALON_KEY for production.`)),i||=generateKey(),i}function s(e){return e.replaceAll(`+`,`-`).replaceAll(`/`,`_`).replace(/=+$/,``)}function c(e){let t=e.replaceAll(`-`,`+`).replaceAll(`_`,`/`),n=t.length%4;return n===2?t+=`==`:n===3&&(t+=`=`),t}export function encrypt(t,i){let a=Buffer.from(i??getKey(),`base64`);if(a.length!==32)throw Error(`Encryption key must be 32 bytes. Got ${a.length} bytes.`);let o=n(12),c=e(r,a,o),l=Buffer.concat([c.update(t,`utf8`),c.final()]),u=c.getAuthTag();return s(Buffer.concat([o,l,u]).toString(`base64`))}export function decrypt(e,n){let i=Buffer.from(n??getKey(),`base64`);if(i.length!==32)throw Error(`Encryption key must be 32 bytes. Got ${i.length} bytes.`);let a=Buffer.from(c(e),`base64`);if(a.length<28)throw Error(`Invalid encrypted data: too short`);let o=a.subarray(0,12),s=a.subarray(a.length-16),l=a.subarray(12,a.length-16),u=t(r,i,o);return u.setAuthTag(s),Buffer.concat([u.update(l),u.final()]).toString(`utf8`)}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Islands Endpoint
|
|
3
|
+
*
|
|
4
|
+
* Nitro route handler for `/_server-islands/:componentId`.
|
|
5
|
+
* Handles decrypting props, importing the component from the manifest,
|
|
6
|
+
* rendering it with Preact SSR, and returning HTML with Cache-Control headers.
|
|
7
|
+
*
|
|
8
|
+
* Supports both GET (props in query param `p`) and POST (props in body) requests.
|
|
9
|
+
*
|
|
10
|
+
* @module server-islands/endpoint
|
|
11
|
+
*/
|
|
12
|
+
import type { H3Event } from "h3";
|
|
13
|
+
/**
|
|
14
|
+
* Options for configuring the server island endpoint handler.
|
|
15
|
+
*/
|
|
16
|
+
export interface ServerIslandEndpointOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Whether running in development mode.
|
|
19
|
+
* In dev mode, error messages are included in 500 responses.
|
|
20
|
+
*/
|
|
21
|
+
isDev?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Default Cache-Control header value.
|
|
24
|
+
* @default "private, no-store"
|
|
25
|
+
*/
|
|
26
|
+
defaultCacheControl?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a Nitro-compatible event handler for the server islands endpoint.
|
|
30
|
+
*
|
|
31
|
+
* The handler:
|
|
32
|
+
* 1. Extracts the componentId from the URL path
|
|
33
|
+
* 2. Extracts encrypted props from query param (GET) or body (POST)
|
|
34
|
+
* 3. Decrypts the props using AES-256-GCM
|
|
35
|
+
* 4. Looks up the component module path from the manifest
|
|
36
|
+
* 5. Dynamically imports the component
|
|
37
|
+
* 6. Renders it with Preact's renderToString
|
|
38
|
+
* 7. Returns HTML with appropriate headers
|
|
39
|
+
*
|
|
40
|
+
* Error responses:
|
|
41
|
+
* - 400 Bad Request: decryption failure or missing props
|
|
42
|
+
* - 404 Not Found: component not in manifest
|
|
43
|
+
* - 500 Internal Server Error: render failure
|
|
44
|
+
*
|
|
45
|
+
* @param options - Endpoint configuration options
|
|
46
|
+
* @returns An async handler function compatible with H3/Nitro
|
|
47
|
+
*/
|
|
48
|
+
export declare function defineServerIslandHandler(options?: ServerIslandEndpointOptions): (event: H3Event) => Promise<Response>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{h as e}from"preact";import t from"preact-render-to-string";import{loadIntegration as n}from"../islands/integration-loader.js";import{decrypt as r}from"./encryption.js";import{lookupComponent as i}from"./manifest.js";let a=!1,o={};async function s(){if(a)return o;a=!0;try{o=(await import(`virtual:server-island-manifest`)).serverIslandLoaders??{}}catch{}return o}export function defineServerIslandHandler(a={}){let{isDev:o=process.env.NODE_ENV!==`production`,defaultCacheControl:c=`private, no-store`}=a;return async a=>{let u=l(a);if(!u)return new Response(`Missing component ID`,{status:400,headers:{"Content-Type":`text/plain`}});let d;try{d=await f(a)}catch{return new Response(`Failed to read request body`,{status:400,headers:{"Content-Type":`text/plain`}})}if(!d)return new Response(`Missing encrypted props`,{status:400,headers:{"Content-Type":`text/plain`}});let p,m,h;try{let e;if(d.startsWith(`dev.`)){let t=d.slice(4);e=Buffer.from(t,`base64url`).toString(`utf8`)}else e=r(d);let t=JSON.parse(e);t.__island&&(m=t.__island,delete t.__island),t.__src&&(h=t.__src,delete t.__src),p=t}catch{return new Response(`Bad Request: decryption failed`,{status:400,headers:{"Content-Type":`text/plain`}})}let g=i(u)??h;if(!g)return new Response(`Component not found`,{status:404,headers:{"Content-Type":`text/plain`}});let _;try{let e=(await s())[u];if(e?_=await e():(o&&console.log(`[server-islands] Loading component from:`,g),_=await import(g)),!_.default)throw TypeError(`Module "${g}" does not have a default export`)}catch(e){if(m&&o)console.warn(`[server-islands] Component import failed (will render client-side):`,g,e instanceof Error?e.message:e),_={default:null};else{let t=o&&e instanceof Error?e.message:`Component import failed`;return new Response(t,{status:500,headers:{"Content-Type":`text/plain`}})}}let v,y=!1,b,x=m?.framework??`preact`;if(_.default)try{if(x===`preact`||x===`react`){let n=_.default;v=t(e(n,p))}else{let e=await(await n(x)).render({component:_.default,props:p,src:g,condition:m?.condition??`on:client`,ssrOnly:!1,viteServer:void 0,isDev:o});v=e.html;let t=e.hydrationData;t?.renderId&&(b=t.renderId),e.css&&(v=`${v}<style>${e.css}</style>`)}}catch(e){if(m&&o)v=``,y=!0;else{let t=o&&e instanceof Error?`${e.message}\n${e.stack}`:``;return console.error(`[server-islands] Render error for`,g,e),new Response(t||`Internal Server Error`,{status:500,headers:{"Content-Type":`text/plain`}})}}else v=``,y=!0;if(m){let e=m.elementId??`si-${u}`,t=m.componentSrc??g,n;try{n=JSON.stringify(p)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Failed to serialize props for server island (componentId=${u}, module=${g}, elementId=${e}): ${n}`)}let r=y?`on:client`:m.condition??`on:client`;if(o){let i=new URL(`../client/server-island-hydrate.ts`,import.meta.url).pathname,a=m.framework??`preact`,o=b?JSON.stringify(b):`undefined`,s=`<script type="module">
|
|
2
|
+
import{hydrateServerIsland}from"/@fs${i}";
|
|
3
|
+
hydrateServerIsland(${JSON.stringify(e)},${JSON.stringify(t)},${n},${JSON.stringify(r)},${JSON.stringify(a)},${o});
|
|
4
|
+
<\/script>`;v+=s}else{let{generatePerIslandScript:r}=await import(`../islands/per-island-script.ts`),i=r({islandId:e,componentSrc:t,framework:m.framework,condition:m.condition,conditionArg:m.conditionArg,propsJson:n});v+=i}}return new Response(v,{status:200,headers:{"Content-Type":`text/html`,"Cache-Control":c}})}}function l(e){let t=e.context?.params;if(t?.componentId)return t.componentId;let n=u(e);return/\/_server-islands\/([^/?]+)/.exec(n)?.[1]}function u(e){if(e.url)return e.url.pathname;let t=e.req?.url;return t?new URL(t,`http://localhost`).pathname:`/`}function d(e){return e.req?.method??`GET`}async function f(e){let t=d(e).toUpperCase();if(t===`GET`)return(e.url?e.url.searchParams:new URLSearchParams(``)).get(`p`)||void 0;if(t===`POST`){let t=e.web?.request;if(t)return await t.text();if(e.node?.req)return await p(e.node.req);if(e._body!==void 0)return String(e._body)}}function p(e){return new Promise((t,n)=>{let r=[];e.on(`data`,e=>r.push(e)),e.on(`end`,()=>t(Buffer.concat(r).toString(`utf8`))),e.on(`error`,n)})}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a stable 12-character component ID from a module's relative path.
|
|
3
|
+
* Uses base64url(sha256(relativePath)).slice(0, 12) for brevity and URL safety.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateComponentId(relativePath: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Registers a component in the manifest at build time.
|
|
8
|
+
*
|
|
9
|
+
* @param componentId - The hashed component identifier
|
|
10
|
+
* @param modulePath - The relative module path to the component
|
|
11
|
+
*/
|
|
12
|
+
export declare function addToManifest(componentId: string, modulePath: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Looks up a component's module path by its ID at runtime.
|
|
15
|
+
*
|
|
16
|
+
* @param componentId - The hashed component identifier
|
|
17
|
+
* @returns The module path if found, or undefined
|
|
18
|
+
*/
|
|
19
|
+
export declare function lookupComponent(componentId: string): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the full manifest as a plain object for serialization into the server bundle.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getManifest(): Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Clears the manifest. Useful for testing or rebuilds.
|
|
26
|
+
*/
|
|
27
|
+
export declare function clearManifest(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as e}from"node:crypto";export function generateComponentId(t){return e(`sha256`).update(t).digest(`base64url`).slice(0,12)}const n=new Map;export function addToManifest(e,t){n.set(e,t)}export function lookupComponent(e){return n.get(e)}export function getManifest(){return Object.fromEntries(n)}export function clearManifest(){n.clear()}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IslandDirective } from "../types/island-prop.d.ts";
|
|
2
|
+
import type { ServerIslandProp } from "./types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Renders a server island placeholder with fallback content and an inline
|
|
5
|
+
* fetch script that will request the real component HTML after page load.
|
|
6
|
+
*
|
|
7
|
+
* @param componentId - The hashed component ID (from manifest)
|
|
8
|
+
* @param props - The component's props (excluding `server` and `island`)
|
|
9
|
+
* @param serverProp - The server island configuration
|
|
10
|
+
* @param islandProp - Optional island directive for combined server+client islands
|
|
11
|
+
* @param componentSrc - Optional component source path (used for combined island hydration)
|
|
12
|
+
* @returns Complete HTML string with wrapper element and fetch script
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderServerIsland(componentId: string, props: Record<string, unknown>, serverProp: ServerIslandProp, islandProp?: IslandDirective, componentSrc?: string,
|
|
15
|
+
/** The original source path of the component (used for dev-mode loading) */
|
|
16
|
+
srcPath?: string,
|
|
17
|
+
/** Framework identifier (preact, solid, vue, etc.) */
|
|
18
|
+
framework?: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import e from"preact-render-to-string";import{encrypt as t}from"./encryption.js";const n=process.env.NODE_ENV!==`production`;function r(e){return n?`dev.${Buffer.from(e,`utf8`).toString(`base64url`)}`:t(e)}let i=0;function a(e,t){try{return JSON.stringify(e)}catch(e){let n=e instanceof Error?e.message:String(e);throw Error(`Failed to serialize server island props (elementId=${t.elementId}, componentSrc=${t.componentSrc??`unknown`}, combinedIsland=${t.hasIsland}). Props must be JSON-serializable (no functions, circular refs, or BigInt): ${n}`)}}export function renderServerIsland(t,n,o,l,u,d,f){let p=`si-${t}-${i++}`,m={...n};d&&(m.__src=d),l&&(m.__island={condition:l.condition??`on:client`,conditionArg:l.conditionArg,framework:f??`preact`,componentSrc:u,elementId:p});let h=r(a(m,{componentSrc:u,elementId:p,hasIsland:!!l})),g=o.fallback?e(o.fallback):``,_=o.timeout??1e4,v=s({elementId:p,endpointPath:`/_server-islands/${t}`,timeout:_,islandProp:l});return`${`<avalon-server-island id="${p}" data-p="${c(h)}">`}${g}</avalon-server-island>\n${v}`}function s(e){let{elementId:t,endpointPath:n,timeout:r,islandProp:i}=e,a=i?`el.querySelectorAll('script[type="module"]').forEach(s=>{const n=document.createElement("script");n.type="module";n.textContent=s.textContent;s.replaceWith(n);});`:``;return`<script type="module">
|
|
2
|
+
(async()=>{
|
|
3
|
+
const el=document.getElementById("${t}");
|
|
4
|
+
if(!el)return;
|
|
5
|
+
const p=el.dataset.p;
|
|
6
|
+
const url="${n}?p="+encodeURIComponent(p);
|
|
7
|
+
const ctrl=new AbortController();
|
|
8
|
+
const t=setTimeout(()=>ctrl.abort(),${r});
|
|
9
|
+
try{
|
|
10
|
+
const r=await fetch(...(url.length>2048?["${n}",{method:"POST",body:p,headers:{"content-type":"text/plain"},signal:ctrl.signal}]:[url,{signal:ctrl.signal}]));
|
|
11
|
+
clearTimeout(t);
|
|
12
|
+
if(!r.ok)return;
|
|
13
|
+
const html=await r.text();
|
|
14
|
+
el.innerHTML=html;${a?`\n${a}`:``}
|
|
15
|
+
}catch(e){clearTimeout(t);}
|
|
16
|
+
})()
|
|
17
|
+
<\/script>`}function c(e){return e.replaceAll(`&`,`&`).replaceAll(`"`,`"`).replaceAll(`<`,`<`).replaceAll(`>`,`>`)}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Islands Nitro Route Handler
|
|
3
|
+
*
|
|
4
|
+
* This file is registered as a Nitro handler by the Avalon build integration.
|
|
5
|
+
* It handles requests to `/_server-islands/:componentId` by delegating to
|
|
6
|
+
* the `defineServerIslandHandler` factory.
|
|
7
|
+
*
|
|
8
|
+
* Supports both GET and POST methods:
|
|
9
|
+
* - GET: encrypted props in the `p` query parameter
|
|
10
|
+
* - POST: encrypted props in the request body
|
|
11
|
+
*
|
|
12
|
+
* @module server-islands/route
|
|
13
|
+
*/
|
|
14
|
+
declare const handler: (event: import("h3").H3Event) => Promise<Response>;
|
|
15
|
+
export default handler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineServerIslandHandler as e}from"./endpoint.js";const t=e();export default t;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { JSX } from "preact";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for a server island component.
|
|
4
|
+
*
|
|
5
|
+
* When a component receives the `server` prop, it is excluded from the initial
|
|
6
|
+
* page render and instead fetched on-demand from a dedicated server endpoint
|
|
7
|
+
* after the page loads. This enables prerendered/cached pages to contain
|
|
8
|
+
* personalized or dynamic content without sacrificing cacheability.
|
|
9
|
+
*/
|
|
10
|
+
export interface ServerIslandProp {
|
|
11
|
+
/** JSX to render as placeholder until the server response arrives */
|
|
12
|
+
fallback?: JSX.Element;
|
|
13
|
+
/** Cache-Control header for the island endpoint response */
|
|
14
|
+
cache?: string;
|
|
15
|
+
/** Fetch timeout in milliseconds (default: 10000) */
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Automatically included via tsconfig.json `compilerOptions.types`.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { IslandDirective } from "./island-prop.d.ts";
|
|
7
|
+
import type { IslandDirective, ServerIslandProp } from "./island-prop.d.ts";
|
|
8
8
|
|
|
9
9
|
/** Force TypeScript to expand the type inline on hover instead of showing the alias name */
|
|
10
10
|
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
|
@@ -13,6 +13,7 @@ declare module "preact" {
|
|
|
13
13
|
namespace JSX {
|
|
14
14
|
interface IntrinsicAttributes {
|
|
15
15
|
island?: Expand<IslandDirective>;
|
|
16
|
+
server?: Expand<ServerIslandProp>;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
}
|
|
@@ -21,6 +22,7 @@ declare global {
|
|
|
21
22
|
namespace JSX {
|
|
22
23
|
interface IntrinsicAttributes {
|
|
23
24
|
island?: Expand<IslandDirective>;
|
|
25
|
+
server?: Expand<ServerIslandProp>;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
}
|
|
@@ -28,5 +30,6 @@ declare global {
|
|
|
28
30
|
declare module "@vue/runtime-core" {
|
|
29
31
|
interface ComponentCustomProps {
|
|
30
32
|
island?: Expand<IslandDirective>;
|
|
33
|
+
server?: Expand<ServerIslandProp>;
|
|
31
34
|
}
|
|
32
35
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ServerIslandProp } from "../server-islands/types.ts";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Type augmentation for the `island` prop on island components.
|
|
3
5
|
*
|
|
@@ -11,8 +13,13 @@
|
|
|
11
13
|
*
|
|
12
14
|
* Custom directives:
|
|
13
15
|
* <Counter island={{ condition: 'on:delay' }} />
|
|
16
|
+
*
|
|
17
|
+
* Server islands:
|
|
18
|
+
* <UserAvatar server={{ fallback: <AvatarSkeleton /> }} userId={session.id} />
|
|
14
19
|
*/
|
|
15
20
|
|
|
21
|
+
export type { ServerIslandProp } from "../server-islands/types.ts";
|
|
22
|
+
|
|
16
23
|
export type IslandDirective = {
|
|
17
24
|
/**
|
|
18
25
|
* Hydration condition — controls when the island's JavaScript loads and executes.
|
|
@@ -60,3 +60,15 @@ declare module "virtual:avalon/integration-loader" {
|
|
|
60
60
|
export function preLitHydration(): Promise<void>;
|
|
61
61
|
export function loadHMRAdapter(framework: string): Promise<unknown>;
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
declare module "virtual:server-island-manifest" {
|
|
65
|
+
/** Mapping of componentId → module path for all registered server islands */
|
|
66
|
+
export const serverIslandManifest: Record<string, string>;
|
|
67
|
+
/** Lazy loaders that dynamically import each server island component, preventing tree-shaking */
|
|
68
|
+
export const serverIslandLoaders: Record<string, () => Promise<{ default: unknown }>>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
declare module "virtual:server-island-key" {
|
|
72
|
+
/** The AES-256-GCM encryption key (base64-encoded) embedded at build time */
|
|
73
|
+
export const serverIslandKey: string;
|
|
74
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import{existsSync as e,readdirSync as t,rmSync as n}from"node:fs";import{stat as r}from"node:fs/promises";import{createRequire as i}from"node:module";import{dirname as a,join as o,relative as s,resolve as c}from"node:path";import{nitro as l}from"nitro/vite";import{isRunnableDevEnvironment as u}from"vite";import{getUniversalCSSForHead as d}from"../islands/universal-css-collector.js";import{getUniversalHeadForInjection as f,injectSolidHydrationScriptIfNeeded as p}from"../islands/universal-head-collector.js";import{clearMiddlewareCache as m,discoverScopedMiddleware as h,executeScopedMiddleware as g}from"../middleware/index.js";import{createNitroConfig as _}from"../nitro/config.js";import{createIslandManifestPlugin as v,createNitroBuildPlugin as y,createSourceMapConfig as b,createSourceMapPlugin as x}from"../nitro/index.js";import{collectCssFromModuleGraph as S,injectSsrCss as C}from"../render/collect-css.js";import{generateErrorPage as w,generateFallback404 as T}from"../render/error-pages.js";function E(t){let n=o(a(i(import.meta.url).resolve(`@useavalon/avalon`)),t);if(t.endsWith(`.ts`)&&!e(n)){let t=n.replace(/\.ts$/,`.js`);if(e(t))return t}return n}function D(t,n){let r=o(a(i(o(process.cwd(),`package.json`)).resolve(`@useavalon/${t}`)),n);if(n.endsWith(`.ts`)&&!e(r)){let t=r.replace(/\.ts$/,`.js`);if(e(t))return t}return r}export const VIRTUAL_MODULE_IDS={PAGE_ROUTES:`virtual:avalon/page-routes`,PAGE_LOADER:`virtual:avalon/page-loader`,ISLAND_MANIFEST:`virtual:avalon/island-manifest`,RUNTIME_CONFIG:`virtual:avalon/runtime-config`,CONFIG:`virtual:avalon/config`,LAYOUTS:`virtual:avalon/layouts`,ASSETS:`virtual:avalon/assets`,RENDERER:`virtual:avalon/renderer`,CLIENT_ENTRY:`virtual:avalon/client-entry`,INTEGRATION_LOADER:`virtual:avalon/integration-loader`};export const RESOLVED_VIRTUAL_IDS={PAGE_ROUTES:`\0${VIRTUAL_MODULE_IDS.PAGE_ROUTES}`,PAGE_LOADER:`\0${VIRTUAL_MODULE_IDS.PAGE_LOADER}`,ISLAND_MANIFEST:`\0${VIRTUAL_MODULE_IDS.ISLAND_MANIFEST}`,RUNTIME_CONFIG:`\0${VIRTUAL_MODULE_IDS.RUNTIME_CONFIG}`,CONFIG:`\0${VIRTUAL_MODULE_IDS.CONFIG}`,LAYOUTS:`\0${VIRTUAL_MODULE_IDS.LAYOUTS}`,ASSETS:`\0${VIRTUAL_MODULE_IDS.ASSETS}`,RENDERER:`\0${VIRTUAL_MODULE_IDS.RENDERER}`,CLIENT_ENTRY:`\0${VIRTUAL_MODULE_IDS.CLIENT_ENTRY}`,INTEGRATION_LOADER:`\0${VIRTUAL_MODULE_IDS.INTEGRATION_LOADER}`};export function createNitroIntegration(e,t={}){let n=_(t,e),r={preset:n.preset,serverDir:t.serverDir??n.serverDir??`./server`,routeRules:n.routeRules,runtimeConfig:n.runtimeConfig,compatibilityDate:n.compatibilityDate,scanDirs:[`.`],noExternals:[`estree-walker`,/^@useavalon\//,/^estree-util/]};t.renderer===!1?r.renderer=!1:n.renderer&&(r.renderer=n.renderer),n.publicRuntimeConfig&&(r.publicRuntimeConfig=n.publicRuntimeConfig),n.publicAssets&&(r.publicAssets=n.publicAssets),n.compressPublicAssets&&(r.compressPublicAssets=n.compressPublicAssets),n.serverEntry&&(r.serverEntry=n.serverEntry);let i=n.traceDeps??[];r.traceDeps=[...new Set([`undici`,...i])],n.prerender&&(r.prerender={routes:[],crawlLinks:!1});let a=l(r),o=createNitroCoordinationPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),s=createVirtualModulesPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),c=y(e,t),u=v(e,{verbose:e.verbose,generatePreloadHints:!0}),d=x(b(t.preset??`node_server`,e.isDev));return{nitroOptions:n,plugins:[...Array.isArray(a)?a:[a],o,s,c,u,d]}}export function createNitroCoordinationPlugin(e){let{avalonConfig:t,verbose:r}=e;return{name:`avalon:nitro-coordination`,enforce:`pre`,config(e,{command:t}){if(t===`serve`)return{server:{watch:{ignored:[`**/.output/**`,`**/dist/**`,`**/.netlify/**`,`**/.vercel/**`,`**/.cloudflare/**`,`**/.wrangler/**`,`**/.firebase/**`,`**/.amplify-hosting/**`]},fs:{deny:[`.output`,`dist`,`.netlify`,`.vercel`,`.cloudflare`,`.wrangler`,`.firebase`,`.amplify-hosting`]}}}},configResolved(e){globalThis.__avalonHydrationMode=e.command===`serve`?`entry-client`:`per-island`},configureServer(e){globalThis.__viteDevServer=e;let i=e.config.root||process.cwd(),a=e.config.build?.outDir?o(i,e.config.build.outDir):o(i,`dist`);try{n(a,{recursive:!0,force:!0})}catch{}try{n(o(i,`.output`),{recursive:!0,force:!0})}catch{}let s=!1,c=new Promise(t=>{let n=()=>{(e.environments?.nitro)?.devServer?.entry||s?(s=!0,t()):setTimeout(n,50)};setTimeout(n,100),setTimeout(()=>{s=!0,t()},15e3)});e.middlewares.use(async(e,t,n)=>{if(s)return n();let r=e.url||`/`;if(r.startsWith(`/@`)||r.startsWith(`/__`)||/\/[^/]+\.[a-z0-9]+(\?|$)/i.test(r))return n();await c,n()});let l=null;async function d(){return l||=await h({baseDir:`${e.config.root||process.cwd()}/src`,devMode:!1}),l}function f(){l=null}F(e,t,r,f),d().catch(e=>{console.warn(`[middleware] Failed to discover middleware:`,e)});let p=e.environments?.ssr,m=!!p&&u(p);m&&P(e,t.integrations,r).catch(e=>{console.error(`[prewarm] Core modules pre-warm failed:`,e)}),e.middlewares.use(async(n,i,a)=>{if(!m)return a();let o=n.url||`/`;if(o.endsWith(`.html`)&&(o=o.slice(0,-5)||`/`),o===`/index`&&(o=`/`),o.startsWith(`/@`)||o.startsWith(`/__`)||o.startsWith(`/node_modules/`)||o.startsWith(`/src/client/`)||o.startsWith(`/packages/`)||o.includes(`.`)&&!o.endsWith(`/`)||o.startsWith(`/api/`))return a();try{if(await M(e,o,n,i,d,r)||await q(e,o,t,i))return;let a=await J(e,o,t);if(a){i.statusCode=200,i.setHeader(`Content-Type`,`text/html`),i.end(a);return}await N(e,o,i,t)}catch(e){console.error(`[SSR Error]`,e),i.statusCode=500,i.setHeader(`Content-Type`,`text/html`),i.end(w(e))}})},buildStart(){}}}async function M(e,t,n,r,i,a){let o=performance.now(),s=await i();if(s.length===0)return!1;let c={};for(let[e,t]of Object.entries(n.headers))typeof t==`string`?c[e]=t:Array.isArray(t)&&(c[e]=t.join(`, `));let l=`http://${n.headers.host||`localhost`}${t}`,u=await g({url:l,method:n.method||`GET`,path:t,node:{req:n,res:r},req:new Request(l,{method:n.method||`GET`,headers:c}),context:{}},s,{devMode:!1}),d=performance.now()-o;return d>100&&console.warn(`⚠️ Slow middleware: ${d.toFixed(0)}ms for ${t}`),u?(r.statusCode=u.status,u.headers.forEach((e,t)=>{r.setHeader(t,e)}),r.end(await u.text()),!0):!1}async function N(e,t,n,r){try{let{discoverErrorPages:i,getErrorPageModule:a,generateDefaultErrorPage:o}=await import(`../nitro/error-handler.js`),s=a(404,await i({isDev:r.isDev,pagesDir:r.pagesDir,loadPageModule:async t=>await e.ssrLoadModule(t)}));if(s?.default&&typeof s.default==`function`){let{renderToHtml:e}=await import(`../render/ssr.js`),r=s.default,i=await e({component:()=>r({statusCode:404,message:`Page not found: ${t}`,url:t})},{});n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(i);return}let c=o(404,`Page not found: ${t}`,r.isDev);n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(c)}catch{n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(T(t))}}async function P(e,t,n){let r=performance.now(),i=[{path:E(`src/render/ssr.ts`),assignTo:`ssr`},{path:E(`src/core/layout/enhanced-layout-resolver.ts`),assignTo:`layout`},{path:E(`src/middleware/index.ts`),assignTo:null},...t.map(e=>({path:D(e,`server/renderer.ts`),assignTo:null}))],a=(await Promise.allSettled(i.map(async({path:t,assignTo:n})=>{let r=await e.ssrLoadModule(t);n===`ssr`&&(G=r),n===`layout`&&(K=r)}))).filter(e=>e.status===`fulfilled`).length,o=performance.now()-r;n&&a>0&&console.log(`🔥 SSR ready in ${o.toFixed(0)}ms (${a}/${i.length} core modules)`)}export function createVirtualModulesPlugin(e){let{avalonConfig:n,nitroConfig:r,verbose:i}=e,a=null,l=o,u=s,d=c,f=process.cwd(),p=[];function m(e){try{let n=t(e,{withFileTypes:!0});for(let t of n){let n=l(e,t.name);if(t.isDirectory()&&t.name!==`node_modules`&&!t.name.startsWith(`.`))m(n);else if(t.isFile()&&t.name.endsWith(`.css`)){let e=u(f,n).replaceAll(`\\`,`/`);p.push(e.startsWith(`/`)?e:`/${e}`)}}}catch{}}function h(){p.length=0;for(let e of r.globalCSS??[])p.push(e.startsWith(`/`)?e:`/${e}`);n.modules&&m(d(f,n.modules.dir)),m(d(f,n.layoutsDir));let e=d(f,n.layoutsDir,`..`);e!==f&&e!==d(f,n.layoutsDir)&&e.startsWith(f)&&m(e)}return n.isDev&&h(),{name:`avalon:nitro-virtual-modules`,enforce:`pre`,resolveId(e){return e===VIRTUAL_MODULE_IDS.PAGE_ROUTES?RESOLVED_VIRTUAL_IDS.PAGE_ROUTES:e===VIRTUAL_MODULE_IDS.PAGE_LOADER?RESOLVED_VIRTUAL_IDS.PAGE_LOADER:e===VIRTUAL_MODULE_IDS.ISLAND_MANIFEST?RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST:e===VIRTUAL_MODULE_IDS.RUNTIME_CONFIG?RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG:e===VIRTUAL_MODULE_IDS.CONFIG?RESOLVED_VIRTUAL_IDS.CONFIG:e===VIRTUAL_MODULE_IDS.LAYOUTS?RESOLVED_VIRTUAL_IDS.LAYOUTS:e===VIRTUAL_MODULE_IDS.ASSETS?RESOLVED_VIRTUAL_IDS.ASSETS:e===VIRTUAL_MODULE_IDS.RENDERER?RESOLVED_VIRTUAL_IDS.RENDERER:e===VIRTUAL_MODULE_IDS.CLIENT_ENTRY?RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY:e===VIRTUAL_MODULE_IDS.INTEGRATION_LOADER?RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER:null},async load(e){return e===RESOLVED_VIRTUAL_IDS.PAGE_ROUTES?await I(n,i):e===RESOLVED_VIRTUAL_IDS.PAGE_LOADER?await L(n,i):e===RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST?R():e===RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG?z(n,r):e===RESOLVED_VIRTUAL_IDS.CONFIG?generateConfigModule(n,r):e===RESOLVED_VIRTUAL_IDS.LAYOUTS?(a||=await B(n,r,p),a):e===RESOLVED_VIRTUAL_IDS.ASSETS?V(r):e===RESOLVED_VIRTUAL_IDS.RENDERER?H(n):e===RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY?await U(n,r):e===RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER?generateIntegrationLoaderModule(n):null},handleHotUpdate({file:e,server:t}){let n=e.includes(`/pages/`)&&!e.endsWith(`.css`),r=e.includes(`/components/`)&&/\.[tj]sx?$/.test(e),i=(e.includes(`/layouts/`)||e.includes(`_layout`))&&/\.[tj]sx?$/.test(e),o=e.endsWith(`.css`);if(n||r||i||o){if(n){let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.PAGE_ROUTES);e&&t.moduleGraph.invalidateModule(e);let n=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.PAGE_LOADER);n&&t.moduleGraph.invalidateModule(n)}if(i){a=null,h();let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.LAYOUTS);e&&t.moduleGraph.invalidateModule(e);let n=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY);n&&t.moduleGraph.invalidateModule(n)}setTimeout(()=>{t.ws.send({type:`full-reload`,path:`*`})},500)}if(e.includes(`vite.config`)||e.includes(`avalon.config`)||e.includes(`nitro.config`)){let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.CONFIG);e&&t.moduleGraph.invalidateModule(e)}}}}function F(e,t,n,r){e.watcher.on(`change`,e=>{e.includes(`_middleware`)&&(m(),r?.()),(e.includes(`/render/`)||e.includes(`/layout/`)||e.includes(`/islands/`))&&(G=null,K=null),(e.includes(`/layouts/`)||e.includes(`_layout`))&&globalThis.__avalonLayoutResolver?.clearCache?.()}),e.watcher.on(`add`,e=>{e.includes(`_middleware`)&&(m(),r?.())}),e.watcher.on(`unlink`,e=>{e.includes(`_middleware`)&&(m(),r?.())})}async function I(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),r=await n(await t(e.pagesDir,e.modules,process.cwd()),{developmentMode:e.isDev});return`export const pageRoutes = ${JSON.stringify(r,null,2)};\nexport default pageRoutes;\n`}catch{return`export const pageRoutes = [];
|
|
1
|
+
import{existsSync as e,readdirSync as t,rmSync as n}from"node:fs";import{stat as r}from"node:fs/promises";import{createRequire as i}from"node:module";import{dirname as a,join as o,relative as s,resolve as c}from"node:path";import{nitro as l}from"nitro/vite";import{isRunnableDevEnvironment as u}from"vite";import{getUniversalCSSForHead as d}from"../islands/universal-css-collector.js";import{getUniversalHeadForInjection as f,injectSolidHydrationScriptIfNeeded as p}from"../islands/universal-head-collector.js";import{clearMiddlewareCache as m,discoverScopedMiddleware as h,executeScopedMiddleware as g}from"../middleware/index.js";import{createNitroConfig as _}from"../nitro/config.js";import{createIslandManifestPlugin as v,createNitroBuildPlugin as y,createSourceMapConfig as b,createSourceMapPlugin as x}from"../nitro/index.js";import{collectCssFromModuleGraph as S,injectSsrCss as C}from"../render/collect-css.js";import{generateErrorPage as w,generateFallback404 as T}from"../render/error-pages.js";function E(t){let n=o(a(i(import.meta.url).resolve(`@useavalon/avalon`)),t);if(t.endsWith(`.ts`)&&!e(n)){let t=n.replace(/\.ts$/,`.js`);if(e(t))return t}return n}function D(t,n){let r=o(a(i(o(process.cwd(),`package.json`)).resolve(`@useavalon/${t}`)),n);if(n.endsWith(`.ts`)&&!e(r)){let t=r.replace(/\.ts$/,`.js`);if(e(t))return t}return r}export const VIRTUAL_MODULE_IDS={PAGE_ROUTES:`virtual:avalon/page-routes`,PAGE_LOADER:`virtual:avalon/page-loader`,ISLAND_MANIFEST:`virtual:avalon/island-manifest`,RUNTIME_CONFIG:`virtual:avalon/runtime-config`,CONFIG:`virtual:avalon/config`,LAYOUTS:`virtual:avalon/layouts`,ASSETS:`virtual:avalon/assets`,RENDERER:`virtual:avalon/renderer`,CLIENT_ENTRY:`virtual:avalon/client-entry`,INTEGRATION_LOADER:`virtual:avalon/integration-loader`};export const RESOLVED_VIRTUAL_IDS={PAGE_ROUTES:`\0${VIRTUAL_MODULE_IDS.PAGE_ROUTES}`,PAGE_LOADER:`\0${VIRTUAL_MODULE_IDS.PAGE_LOADER}`,ISLAND_MANIFEST:`\0${VIRTUAL_MODULE_IDS.ISLAND_MANIFEST}`,RUNTIME_CONFIG:`\0${VIRTUAL_MODULE_IDS.RUNTIME_CONFIG}`,CONFIG:`\0${VIRTUAL_MODULE_IDS.CONFIG}`,LAYOUTS:`\0${VIRTUAL_MODULE_IDS.LAYOUTS}`,ASSETS:`\0${VIRTUAL_MODULE_IDS.ASSETS}`,RENDERER:`\0${VIRTUAL_MODULE_IDS.RENDERER}`,CLIENT_ENTRY:`\0${VIRTUAL_MODULE_IDS.CLIENT_ENTRY}`,INTEGRATION_LOADER:`\0${VIRTUAL_MODULE_IDS.INTEGRATION_LOADER}`};export function createNitroIntegration(e,t={}){let n=_(t,e),r=E(`src/server-islands/route.ts`),i={preset:n.preset,serverDir:t.serverDir??n.serverDir??`./server`,routeRules:n.routeRules,runtimeConfig:n.runtimeConfig,compatibilityDate:n.compatibilityDate,scanDirs:[`.`],noExternals:[`estree-walker`,/^@useavalon\//,/^estree-util/,/^preact/,/^react/,/^react-dom/,/^vue/,/^@vue\//],handlers:[{route:`/_server-islands/**`,handler:r}]};t.renderer===!1?i.renderer=!1:n.renderer&&(i.renderer=n.renderer),n.publicRuntimeConfig&&(i.publicRuntimeConfig=n.publicRuntimeConfig),n.publicAssets&&(i.publicAssets=n.publicAssets),n.compressPublicAssets&&(i.compressPublicAssets=n.compressPublicAssets),n.serverEntry&&(i.serverEntry=n.serverEntry);let a=n.traceDeps??[];i.traceDeps=[...new Set([`undici`,...a])],n.prerender&&(i.prerender={routes:[],crawlLinks:!1});let o=l(i),s=createNitroCoordinationPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),c=createVirtualModulesPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),u=y(e,t),d=v(e,{verbose:e.verbose,generatePreloadHints:!0}),f=x(b(t.preset??`node_server`,e.isDev));return{nitroOptions:n,plugins:[...Array.isArray(o)?o:[o],s,c,u,d,f]}}export function createNitroCoordinationPlugin(e){let{avalonConfig:t,verbose:r}=e;return{name:`avalon:nitro-coordination`,enforce:`pre`,config(e,{command:t}){if(t===`serve`)return{server:{watch:{ignored:[`**/.output/**`,`**/dist/**`,`**/.netlify/**`,`**/.vercel/**`,`**/.cloudflare/**`,`**/.wrangler/**`,`**/.firebase/**`,`**/.amplify-hosting/**`]},fs:{deny:[`.output`,`dist`,`.netlify`,`.vercel`,`.cloudflare`,`.wrangler`,`.firebase`,`.amplify-hosting`]}}}},configResolved(e){globalThis.__avalonHydrationMode=e.command===`serve`?`entry-client`:`per-island`},configureServer(e){globalThis.__viteDevServer=e;let i=e.config.root||process.cwd(),a=e.config.build?.outDir?o(i,e.config.build.outDir):o(i,`dist`);try{n(a,{recursive:!0,force:!0})}catch{}try{n(o(i,`.output`),{recursive:!0,force:!0})}catch{}let s=!1,c=new Promise(t=>{let n=()=>{(e.environments?.nitro)?.devServer?.entry||s?(s=!0,t()):setTimeout(n,50)};setTimeout(n,100),setTimeout(()=>{s=!0,t()},15e3)});e.middlewares.use(async(e,t,n)=>{if(s)return n();let r=e.url||`/`;if(r.startsWith(`/@`)||r.startsWith(`/__`)||/\/[^/]+\.[a-z0-9]+(\?|$)/i.test(r))return n();await c,n()});let l=null;async function d(){return l||=await h({baseDir:`${e.config.root||process.cwd()}/src`,devMode:!1}),l}function f(){l=null}F(e,t,r,f),d().catch(e=>{console.warn(`[middleware] Failed to discover middleware:`,e)});let p=e.environments?.ssr,m=!!p&&u(p);m&&P(e,t.integrations,r).catch(e=>{console.error(`[prewarm] Core modules pre-warm failed:`,e)}),e.middlewares.use(async(t,n,r)=>{let i=t.url||`/`;if(!i.startsWith(`/_server-islands/`)||!m)return r();try{let{decrypt:r}=await e.ssrLoadModule(E(`src/server-islands/encryption.ts`)),{h:a}=await e.ssrLoadModule(`preact`),o=(await e.ssrLoadModule(`preact-render-to-string`)).default,s=new URL(i,`http://${t.headers.host||`localhost`}`).searchParams.get(`p`)||``;if(!s&&t.method===`POST`&&(s=await new Promise((e,n)=>{let r=[],i=e=>r.push(e),a=()=>{c(),e(Buffer.concat(r).toString(`utf8`))},o=e=>{c(),n(e)},s=setTimeout(()=>{c(),n(Error(`Timed out reading server island request body`))},15e3);function c(){clearTimeout(s),t.off(`data`,i),t.off(`end`,a),t.off(`error`,o)}t.on(`data`,i),t.on(`end`,a),t.on(`error`,o)})),!s){n.statusCode=400,n.setHeader(`Content-Type`,`text/plain`),n.end(`Missing encrypted props`);return}let c,l,u;try{let e;if(s.startsWith(`dev.`)){let t=s.slice(4);e=Buffer.from(t,`base64url`).toString(`utf8`)}else e=r(s);let t=JSON.parse(e);t.__island&&(u=t.__island,delete t.__island),t.__src&&(l=t.__src,delete t.__src),c=t}catch{n.statusCode=400,n.setHeader(`Content-Type`,`text/plain`),n.end(`Bad Request: decryption failed`);return}if(!l){n.statusCode=404,n.setHeader(`Content-Type`,`text/plain`),n.end(`Component not found (no __src in payload)`);return}let d=(await e.ssrLoadModule(l)).default;if(typeof d!=`function`){n.statusCode=500,n.setHeader(`Content-Type`,`text/plain`),n.end(`Module "${l}" does not export a default component function`);return}let f=o(a(d,c));if(u){let e=i.split(`/`)[2]?.split(`?`)[0]||``,t=u.elementId??`si-${e}`,n=u.componentSrc??l,r=JSON.stringify(c),a=u.condition??`on:client`,o=u.framework??`preact`,s=`<script type="module">
|
|
2
|
+
import{hydrateServerIsland}from"/@fs${E(`src/client/server-island-hydrate.ts`)}";
|
|
3
|
+
hydrateServerIsland(${JSON.stringify(t)},${JSON.stringify(n)},${r},${JSON.stringify(a)},${JSON.stringify(o)});
|
|
4
|
+
<\/script>`;f+=s}n.statusCode=200,n.setHeader(`Content-Type`,`text/html`),n.setHeader(`Cache-Control`,`private, no-store`),n.end(f)}catch(e){console.error(`[server-islands] Dev handler error:`,e),n.statusCode=500,n.setHeader(`Content-Type`,`text/plain`),n.end(`Server island render failed: ${e instanceof Error?e.message:String(e)}`)}}),e.middlewares.use(async(n,i,a)=>{if(!m)return a();let o=n.url||`/`;if(o.endsWith(`.html`)&&(o=o.slice(0,-5)||`/`),o===`/index`&&(o=`/`),o.startsWith(`/@`)||o.startsWith(`/__`)||o.startsWith(`/node_modules/`)||o.startsWith(`/src/client/`)||o.startsWith(`/packages/`)||o.includes(`.`)&&!o.endsWith(`/`)||o.startsWith(`/api/`))return a();try{if(await M(e,o,n,i,d,r)||await q(e,o,t,i))return;let a=await J(e,o,t);if(a){i.statusCode=200,i.setHeader(`Content-Type`,`text/html`),i.end(a);return}await N(e,o,i,t)}catch(e){console.error(`[SSR Error]`,e),i.statusCode=500,i.setHeader(`Content-Type`,`text/html`),i.end(w(e))}})},buildStart(){}}}async function M(e,t,n,r,i,a){let o=performance.now(),s=await i();if(s.length===0)return!1;let c={};for(let[e,t]of Object.entries(n.headers))typeof t==`string`?c[e]=t:Array.isArray(t)&&(c[e]=t.join(`, `));let l=`http://${n.headers.host||`localhost`}${t}`,u=await g({url:l,method:n.method||`GET`,path:t,node:{req:n,res:r},req:new Request(l,{method:n.method||`GET`,headers:c}),context:{}},s,{devMode:!1}),d=performance.now()-o;return d>100&&console.warn(`⚠️ Slow middleware: ${d.toFixed(0)}ms for ${t}`),u?(r.statusCode=u.status,u.headers.forEach((e,t)=>{r.setHeader(t,e)}),r.end(await u.text()),!0):!1}async function N(e,t,n,r){try{let{discoverErrorPages:i,getErrorPageModule:a,generateDefaultErrorPage:o}=await import(`../nitro/error-handler.js`),s=a(404,await i({isDev:r.isDev,pagesDir:r.pagesDir,loadPageModule:async t=>await e.ssrLoadModule(t)}));if(s?.default&&typeof s.default==`function`){let{renderToHtml:e}=await import(`../render/ssr.js`),r=s.default,i=await e({component:()=>r({statusCode:404,message:`Page not found: ${t}`,url:t})},{});n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(i);return}let c=o(404,`Page not found: ${t}`,r.isDev);n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(c)}catch{n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(T(t))}}async function P(e,t,n){let r=performance.now(),i=[{path:E(`src/render/ssr.ts`),assignTo:`ssr`},{path:E(`src/core/layout/enhanced-layout-resolver.ts`),assignTo:`layout`},{path:E(`src/middleware/index.ts`),assignTo:null},...t.map(e=>({path:D(e,`server/renderer.ts`),assignTo:null}))],a=(await Promise.allSettled(i.map(async({path:t,assignTo:n})=>{let r=await e.ssrLoadModule(t);n===`ssr`&&(G=r),n===`layout`&&(K=r)}))).filter(e=>e.status===`fulfilled`).length,o=performance.now()-r;n&&a>0&&console.log(`🔥 SSR ready in ${o.toFixed(0)}ms (${a}/${i.length} core modules)`)}export function createVirtualModulesPlugin(e){let{avalonConfig:n,nitroConfig:r,verbose:i}=e,a=null,l=o,u=s,d=c,f=process.cwd(),p=[];function m(e){try{let n=t(e,{withFileTypes:!0});for(let t of n){let n=l(e,t.name);if(t.isDirectory()&&t.name!==`node_modules`&&!t.name.startsWith(`.`))m(n);else if(t.isFile()&&t.name.endsWith(`.css`)){let e=u(f,n).replaceAll(`\\`,`/`);p.push(e.startsWith(`/`)?e:`/${e}`)}}}catch{}}function h(){p.length=0;for(let e of r.globalCSS??[])p.push(e.startsWith(`/`)?e:`/${e}`);n.modules&&m(d(f,n.modules.dir)),m(d(f,n.layoutsDir));let e=d(f,n.layoutsDir,`..`);e!==f&&e!==d(f,n.layoutsDir)&&e.startsWith(f)&&m(e)}return n.isDev&&h(),{name:`avalon:nitro-virtual-modules`,enforce:`pre`,resolveId(e){return e===VIRTUAL_MODULE_IDS.PAGE_ROUTES?RESOLVED_VIRTUAL_IDS.PAGE_ROUTES:e===VIRTUAL_MODULE_IDS.PAGE_LOADER?RESOLVED_VIRTUAL_IDS.PAGE_LOADER:e===VIRTUAL_MODULE_IDS.ISLAND_MANIFEST?RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST:e===VIRTUAL_MODULE_IDS.RUNTIME_CONFIG?RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG:e===VIRTUAL_MODULE_IDS.CONFIG?RESOLVED_VIRTUAL_IDS.CONFIG:e===VIRTUAL_MODULE_IDS.LAYOUTS?RESOLVED_VIRTUAL_IDS.LAYOUTS:e===VIRTUAL_MODULE_IDS.ASSETS?RESOLVED_VIRTUAL_IDS.ASSETS:e===VIRTUAL_MODULE_IDS.RENDERER?RESOLVED_VIRTUAL_IDS.RENDERER:e===VIRTUAL_MODULE_IDS.CLIENT_ENTRY?RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY:e===VIRTUAL_MODULE_IDS.INTEGRATION_LOADER?RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER:null},async load(e){return e===RESOLVED_VIRTUAL_IDS.PAGE_ROUTES?await I(n,i):e===RESOLVED_VIRTUAL_IDS.PAGE_LOADER?await L(n,i):e===RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST?R():e===RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG?z(n,r):e===RESOLVED_VIRTUAL_IDS.CONFIG?generateConfigModule(n,r):e===RESOLVED_VIRTUAL_IDS.LAYOUTS?(a||=await B(n,r,p),a):e===RESOLVED_VIRTUAL_IDS.ASSETS?V(r):e===RESOLVED_VIRTUAL_IDS.RENDERER?H(n):e===RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY?await U(n,r):e===RESOLVED_VIRTUAL_IDS.INTEGRATION_LOADER?generateIntegrationLoaderModule(n):null},handleHotUpdate({file:e,server:t}){let n=e.includes(`/pages/`)&&!e.endsWith(`.css`),r=e.includes(`/components/`)&&/\.[tj]sx?$/.test(e),i=(e.includes(`/layouts/`)||e.includes(`_layout`))&&/\.[tj]sx?$/.test(e),o=e.endsWith(`.css`);if(n||r||i||o){if(n){let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.PAGE_ROUTES);e&&t.moduleGraph.invalidateModule(e);let n=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.PAGE_LOADER);n&&t.moduleGraph.invalidateModule(n)}if(i){a=null,h();let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.LAYOUTS);e&&t.moduleGraph.invalidateModule(e);let n=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY);n&&t.moduleGraph.invalidateModule(n)}setTimeout(()=>{t.ws.send({type:`full-reload`,path:`*`})},500)}if(e.includes(`vite.config`)||e.includes(`avalon.config`)||e.includes(`nitro.config`)){let e=t.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.CONFIG);e&&t.moduleGraph.invalidateModule(e)}}}}function F(e,t,n,r){e.watcher.on(`change`,e=>{e.includes(`_middleware`)&&(m(),r?.()),(e.includes(`/render/`)||e.includes(`/layout/`)||e.includes(`/islands/`))&&(G=null,K=null),(e.includes(`/layouts/`)||e.includes(`_layout`))&&globalThis.__avalonLayoutResolver?.clearCache?.()}),e.watcher.on(`add`,e=>{e.includes(`_middleware`)&&(m(),r?.())}),e.watcher.on(`unlink`,e=>{e.includes(`_middleware`)&&(m(),r?.())})}async function I(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),r=await n(await t(e.pagesDir,e.modules,process.cwd()),{developmentMode:e.isDev});return`export const pageRoutes = ${JSON.stringify(r,null,2)};\nexport default pageRoutes;\n`}catch{return`export const pageRoutes = [];
|
|
2
5
|
export default pageRoutes;
|
|
3
6
|
`}}async function L(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),{relative:r}=await import(`node:path`),i=process.cwd(),a=await n(await t(e.pagesDir,e.modules,i),{developmentMode:e.isDev}),o=[],s=[];for(let e=0;e<a.length;e++){let t=a[e],n=`page_${e}`,c=r(i,t.filePath).replaceAll(`\\`,`/`),l=c.startsWith(`/`)?c:`/${c}`;o.push(`import * as ${n} from '${l}';`),s.push(` { pattern: ${JSON.stringify(t.pattern)}, params: ${JSON.stringify(t.params)}, module: ${n} }`)}return[...o,``,`const routes = [`,s.join(`,
|
|
4
7
|
`),`];`,``,`/**`,` * Match a pathname against discovered routes and return the page module.`,` * Uses the same pattern matching as Avalon's route discovery.`,` */`,`export function loadPage(pathname) {`,` const cleanPath = pathname.split('?')[0];`,` for (const route of routes) {`,` if (matchRoute(cleanPath, route.pattern, route.params)) {`,` return route.module;`,` }`,` }`,` return null;`,`}`,``,`function matchRoute(pathname, pattern, paramNames) {`,` // Exact match`,` if (pattern === pathname) return true;`,` // Normalize trailing slashes`,` const normPath = pathname === '/' ? '/' : pathname.replace(/\\/$/, '');`,` const normPattern = pattern === '/' ? '/' : pattern.replace(/\\/$/, '');`,` if (normPath === normPattern) return true;`,` // Dynamic segments: /users/:id matches /users/123`,` if (paramNames.length > 0) {`,` const patternParts = normPattern.split('/');`,` const pathParts = normPath.split('/');`,` if (patternParts.length !== pathParts.length) return false;`,` return patternParts.every((part, i) => part.startsWith(':') || part === pathParts[i]);`,` }`,` return false;`,`}`,``,`export default { loadPage, routes };`,``].join(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire as e}from"node:module";import{dirname as t,join as n}from"node:path";import{islandClientBundlerPlugin as r}from"../build/island-client-bundler.js";import{islandCodeSplittingPlugin as i}from"../build/island-code-splitting.js";import{mdxIslandTransform as a}from"../build/mdx-island-transform.js";import{createMDXPlugin as o}from"../build/mdx-plugin.js";import{pageIslandTransform as s}from"../build/page-island-transform.js";import{registry as c}from"../core/integrations/registry.js";import{discoverIntegrationsFromIslandUsage as l}from"./auto-discover.js";import{checkDirectoriesExist as u,resolveConfig as d}from"./config.js";import{createImagePlugin as f}from"./image-optimization.js";import{activateIntegrations as p,activateSingleIntegration as m}from"./integration-activator.js";import{islandSidecarPlugin as h}from"./island-sidecar-plugin.js";import{createNitroIntegration as g}from"./nitro-integration.js";import{formatValidationResults as
|
|
1
|
+
import{createRequire as e}from"node:module";import{dirname as t,join as n}from"node:path";import{islandClientBundlerPlugin as r}from"../build/island-client-bundler.js";import{islandCodeSplittingPlugin as i}from"../build/island-code-splitting.js";import{mdxIslandTransform as a}from"../build/mdx-island-transform.js";import{createMDXPlugin as o}from"../build/mdx-plugin.js";import{pageIslandTransform as s}from"../build/page-island-transform.js";import{registry as c}from"../core/integrations/registry.js";import{discoverIntegrationsFromIslandUsage as l}from"./auto-discover.js";import{checkDirectoriesExist as u,resolveConfig as d}from"./config.js";import{createImagePlugin as f}from"./image-optimization.js";import{activateIntegrations as p,activateSingleIntegration as m}from"./integration-activator.js";import{islandSidecarPlugin as h}from"./island-sidecar-plugin.js";import{createNitroIntegration as g}from"./nitro-integration.js";import{serverIslandsPlugin as _}from"./server-islands-plugin.js";import{formatValidationResults as v,validateActiveIntegrations as y}from"./validation.js";export async function collectIntegrationPlugins(e,t=!1){let n=[],r=[];for(let i of e){let e=await b(i,t);i===`lit`?r.push(...e):n.push(...e)}return[...r,...n]}async function b(e,t){let n=c.get(e);if(!n||typeof n.vitePlugin!=`function`)return[];try{let e=await n.vitePlugin();return(Array.isArray(e)?e:[e]).filter(e=>e!=null)}catch(t){return console.warn(`[avalon] Failed to load vite plugin for ${e}:`,t instanceof Error?t.message:t),[]}}async function x(e,t){let n=new Set;try{let r=await l(e.pagesDir,e.layoutsDir,t,e.modules?.dir);for(let t of r)e.integrations.includes(t)&&n.add(t)}catch{for(let t of e.integrations)n.add(t)}return n}async function S(e){if(!e.lazyIntegrations||e.integrations.length===0)return[...e.integrations];let t=await x(e);return t.size===0?[...e.integrations]:Array.from(t)}async function C(e){try{let t=await o({jsxImportSource:e.mdx.jsxImportSource,syntaxHighlighting:e.mdx.syntaxHighlighting,remarkPlugins:e.mdx.remarkPlugins,rehypePlugins:e.mdx.rehypePlugins,development:!0});return t.push(a({verbose:e.verbose})),t}catch(t){return e.showWarnings&&console.warn(`⚠️ Could not configure MDX plugin:`,t),[]}}function w(e,t,n){let{plugins:r,nitroOptions:i}=g(e,t);return globalThis.__nitroConfig=i,{plugins:r,options:i}}async function T(e,t,n){if(e.autoDiscoverIntegrations)try{let r=await l(e.pagesDir,e.layoutsDir,t,e.modules?.dir);for(let t of r)if(!n.has(t))try{await m(t,n,e.verbose)}catch(n){e.showWarnings&&console.warn(` ⚠️ Could not auto-load integration: ${t}`,n)}}catch(t){e.showWarnings&&console.warn(` ⚠️ Auto-discovery failed:`,t)}}function E(e,t){if(!e.validateIntegrations||t.size===0)return;let n=y(t,e.showWarnings);n.allValid||(console.error(v(n)),e.showWarnings&&console.warn(` ⚠️ Some integrations have validation issues.`))}export async function avalon(a){let o,c,l=new Set,m=d(a,!0),g=await S(m);g.length>0&&await p({...m,integrations:g},l);let v=await C(m),y=await f(m.image,m.verbose),b=[];l.size>0&&(b=await collectIntegrationPlugins(l,m.verbose));let x=[];if(a?.nitro){let{plugins:e}=w(m,a.nitro,m.verbose);x=e}let D=h({verbose:m.verbose}),O=_({verbose:m.verbose}),k=e(import.meta.url),A=null;try{A=n(t(k.resolve(`@useavalon/avalon/client`)),`main.js`)}catch{}let j=new Set([`preact`,`react`,`vue`,`svelte`,`solid`,`lit`,`qwik`].flatMap(e=>[`/@useavalon/${e}/client`,`/@useavalon/${e}/client/hmr`,`@useavalon/${e}/client`,`@useavalon/${e}/client/hmr`])),M={name:`avalon`,enforce:`pre`,config(e,{command:t}){let n={preact:[`preact`,`preact/hooks`],react:[`react`,`react-dom`,`react-dom/client`],vue:[`vue`],svelte:[`svelte`,`svelte/internal`],solid:[`solid-js`,`solid-js/web`],lit:[`lit`,`@lit-labs/ssr-client`],qwik:[`@builder.io/qwik`]},r=g.flatMap(e=>n[e]??[]);return{define:{__AVALON_PER_ISLAND__:JSON.stringify(t===`build`)},oxc:{exclude:[/node_modules\/@useavalon\/.*\.tsx?$/]},ssr:{noExternal:[/^@useavalon\//]},optimizeDeps:{exclude:[`@useavalon/avalon`,...g.map(e=>`@useavalon/${e}`)],include:r}}},configResolved(e){c=e,o=d(a,e.command===`serve`),globalThis.__avalonConfig=o,u(o,e.root)},async resolveId(e){if(e===`/src/client/main.js`&&A)return A;if(j.has(e)){let t=e.startsWith(`/`)?e.slice(1):e;return(await this.resolve(t))?.id??null}return null},async transform(e,t){if(t.includes(`@useavalon/`)&&/\.tsx?$/.test(t)){let{transform:n}=await import(`oxc-transform`),r=await n(t,e,{sourcemap:!0,typescript:{onlyRemoveTypeImports:!1}});return{code:r.code,map:r.map,moduleType:`js`}}},async buildStart(){await T(o,c?.root,l),E(o,l)},configureServer(e){globalThis.__viteDevServer=e}},N=b.filter(e=>e.name?.includes(`lit`)),P=b.filter(e=>!e.name?.includes(`lit`));return[s({pagesDir:m.pagesDir,layoutsDir:m.layoutsDir,modules:m.modules,verbose:m.verbose}),r(m,a?.nitro),i(m,a?.nitro),...y,...N,...v,M,D,O,...x,...P]}export function getResolvedConfig(){return globalThis.__avalonConfig}export function getPagesDir(){return globalThis.__avalonConfig?.pagesDir??`src/pages`}export function getLayoutsDir(){return globalThis.__avalonConfig?.layoutsDir??`src/layouts`}export function getNitroConfig(){return globalThis.__nitroConfig}export function isNitroEnabled(){return globalThis.__nitroConfig!==void 0}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Islands Vite Plugin
|
|
3
|
+
*
|
|
4
|
+
* Collects server island components during build by scanning for `server` prop usage
|
|
5
|
+
* in JSX/TSX files. Generates a component manifest mapping componentId → modulePath
|
|
6
|
+
* and embeds the encryption key in the server bundle.
|
|
7
|
+
*
|
|
8
|
+
* Detection:
|
|
9
|
+
* Any component used with a `server` prop (e.g. `<UserAvatar server={{ fallback: ... }} />`)
|
|
10
|
+
* is registered as a server island. The component's import path is resolved and hashed
|
|
11
|
+
* to produce a stable componentId.
|
|
12
|
+
*
|
|
13
|
+
* Outputs:
|
|
14
|
+
* - A virtual module `virtual:server-island-manifest` that exports the manifest
|
|
15
|
+
* - A virtual module `virtual:server-island-key` that exports the encryption key
|
|
16
|
+
*/
|
|
17
|
+
import type { Plugin } from "vite";
|
|
18
|
+
export interface ServerIslandsPluginOptions {
|
|
19
|
+
/** Whether to log verbose output */
|
|
20
|
+
verbose?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Vite plugin that collects server island components during build.
|
|
24
|
+
*
|
|
25
|
+
* Responsibilities:
|
|
26
|
+
* 1. Scans for `server` prop usage in JSX/TSX files during transform
|
|
27
|
+
* 2. Registers discovered components in the manifest (componentId → modulePath)
|
|
28
|
+
* 3. Provides virtual modules for the manifest and encryption key
|
|
29
|
+
*/
|
|
30
|
+
export declare function serverIslandsPlugin(options?: ServerIslandsPluginOptions): Plugin;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{dirname as e,relative as t,resolve as n}from"node:path";import{generateKey as r,getKey as i}from"../server-islands/encryption.js";import{addToManifest as a,clearManifest as o,generateComponentId as s,getManifest as c}from"../server-islands/manifest.js";const l=`virtual:server-island-manifest`,u=`\0`+l,d=`virtual:server-island-key`,f=`\0`+d,p=/<([A-Z][a-zA-Z0-9_$]*)\s[\s\S]*?\bserver\s*[={/>]/;function m(e){let t=[],n=/^[ \t]*import\s+([A-Z]\w*)\s+from\s+(['"][^'"]+['"])/gm,r=null;for(r=n.exec(e);r!==null;r=n.exec(e))t.push({localName:r[1],importPath:r[2].slice(1,-1)});return t}function h(e,t){return new RegExp(String.raw`<${t}\s[\s\S]*?\bserver\s*[={/>]`).test(e)}function g(r,i,a){return r.startsWith(`/src/`)||r.startsWith(`/app/`)?r:r.startsWith(`@/`)?`/app/${r.slice(2)}`:r.startsWith(`@shared/`)?`/app/shared/${r.slice(8)}`:r.startsWith(`@modules/`)?`/app/modules/${r.slice(9)}`:r.startsWith(`~/`)?`/src/${r.slice(2)}`:r.startsWith(`$components/`)?`/src/components/${r.slice(12)}`:r.startsWith(`$islands/`)?`/src/islands/${r.slice(9)}`:r.startsWith(`.`)?`/`+t(a,n(e(i),r)).replaceAll(`\\`,`/`):r}export function serverIslandsPlugin(e={}){let t,n,_=!!process.env.AVALON_KEY;return{name:`avalon:server-islands`,enforce:`pre`,config(){process.env.AVALON_KEY||(process.env.AVALON_KEY=r())},configResolved(e){t=e.root},buildStart(){o(),n=i(),e.verbose&&console.log(`[avalon:server-islands] Encryption key source: ${_?`AVALON_KEY env var`:`generated per-build`}`)},resolveId(e){return e===l?u:e===d?f:null},load(e){if(e===u){let e=c(),t=Object.entries(e),n=`export const serverIslandManifest = ${JSON.stringify(e)};\n\n`;n+=`export const serverIslandLoaders = {
|
|
2
|
+
`;for(let[e,r]of t)n+=` ${JSON.stringify(e)}: () => import(${JSON.stringify(r)}),\n`;return n+=`};
|
|
3
|
+
`,n}return e===f?`export const serverIslandKey = ${JSON.stringify(n)};`:null},transform(n,r){if(!/\.(tsx|jsx)$/.test(r)||r.includes(`node_modules`)||!p.test(n))return null;let i=m(n);if(i.length===0)return null;for(let o of i)if(h(n,o.localName)){let n=g(o.importPath,r,t),i=s(n);a(i,n),e.verbose&&console.log(`[avalon:server-islands] Registered: ${o.localName} → ${i} (${n})`)}return null},generateBundle(){let t=c(),n=Object.keys(t).length;n>0&&e.verbose&&console.log(`[avalon:server-islands] Manifest contains ${n} server island(s)`)}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/avalon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.91-canary.20260531.55c9ced",
|
|
4
4
|
"description": "Multi-framework islands architecture for the modern web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
"markdown",
|
|
30
30
|
"mdx"
|
|
31
31
|
],
|
|
32
|
+
"bin": {
|
|
33
|
+
"avalon": "bin/avalon.ts"
|
|
34
|
+
},
|
|
32
35
|
"scripts": {
|
|
33
36
|
"build:client": "bun run scripts/build-client.ts",
|
|
34
37
|
"build": "bun run scripts/build.ts",
|
|
@@ -144,7 +147,7 @@
|
|
|
144
147
|
}
|
|
145
148
|
},
|
|
146
149
|
"dependencies": {
|
|
147
|
-
"@useavalon/core": "^0.1.
|
|
150
|
+
"@useavalon/core": "^0.1.9-canary.20260531.55c9ced",
|
|
148
151
|
"@mdx-js/rollup": "^3.0.0",
|
|
149
152
|
"h3": "^2.0.1-rc.16",
|
|
150
153
|
"marked": "^17.0.4",
|