@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.
Files changed (31) hide show
  1. package/bin/avalon.ts +17 -0
  2. package/dist/mod.d.ts +1 -0
  3. package/dist/src/build/page-island-transform.js +2 -2
  4. package/dist/src/client/server-island-hydrate.d.ts +12 -0
  5. package/dist/src/client/server-island-hydrate.js +1 -0
  6. package/dist/src/islands/framework-base-css.d.ts +2 -2
  7. package/dist/src/islands/framework-base-css.js +1 -1
  8. package/dist/src/islands/island.d.ts +7 -1
  9. package/dist/src/islands/island.js +1 -1
  10. package/dist/src/islands/types.d.ts +3 -0
  11. package/dist/src/nitro/renderer.js +1 -1
  12. package/dist/src/server-islands/encryption.d.ts +32 -0
  13. package/dist/src/server-islands/encryption.js +1 -0
  14. package/dist/src/server-islands/endpoint.d.ts +48 -0
  15. package/dist/src/server-islands/endpoint.js +4 -0
  16. package/dist/src/server-islands/manifest.d.ts +27 -0
  17. package/dist/src/server-islands/manifest.js +1 -0
  18. package/dist/src/server-islands/renderer.d.ts +18 -0
  19. package/dist/src/server-islands/renderer.js +17 -0
  20. package/dist/src/server-islands/route.d.ts +15 -0
  21. package/dist/src/server-islands/route.js +1 -0
  22. package/dist/src/server-islands/types.d.ts +17 -0
  23. package/dist/src/server-islands/types.js +1 -0
  24. package/dist/src/types/island-jsx.d.ts +4 -1
  25. package/dist/src/types/island-prop.d.ts +7 -0
  26. package/dist/src/types/virtual-modules.d.ts +12 -0
  27. package/dist/src/vite-plugin/nitro-integration.js +4 -1
  28. package/dist/src/vite-plugin/plugin.js +1 -1
  29. package/dist/src/vite-plugin/server-islands-plugin.d.ts +30 -0
  30. package/dist/src/vite-plugin/server-islands-plugin.js +3 -0
  31. 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 u(e,t,i,a=[]){let s=new Map;for(let c of t){let t=n(c.importPath,i,a),l=r(t);if(RegExp(`<${c.localName}${String.raw`[\s][^>]*island[\s]*[={]`}`).test(e)){s.set(c.localName,{srcPath:t,framework:l,importPath:c.importPath,autoIsland:!1});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})}return s}function d(e,t){for(;t<e.length&&/\s/.test(e[t]);)t++;return t}function f(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 p(e,t){for(t++;t<e.length&&e[t]!=="`";){if(e[t]===`\\`){t+=2;continue}if(e[t]===`$`&&e[t+1]===`{`){t=m(t+1,e);continue}t++}return t<e.length?t+1:t}function m(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=f(t,n):n++}return n<t.length?n+1:n}function h(e,t){let n=t+1,r=m(t,e);return{value:e.slice(n,r-1),endIdx:r}}function g(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 _(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=d(e,r),e[r]!==`=`)return{name:i,value:null,endIdx:r};if(r=d(e,r+1),e[r]===`{`){let t=h(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}if(e[r]===`"`||e[r]===`'`){let t=g(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}return null}function v(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 y(e,t,n){let r=d(e,t+1+n.length),i=null,a=[];for(;r<e.length;){r=d(e,r);let t=v(e,r,n);if(t)return{endIdx:t.endIdx,islandProp:i,otherProps:a};let o=_(e,r);if(!o)return null;if(r=o.endIdx,o.name===`island`)i=o.value??`{}`;else{let e=o.value===null?`${o.name}: true`:`${o.name}: ${o.value}`;a.push(e)}}return null}function b(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 })}`;let c=e.islandProp??``;return`{await __pageRenderIsland({ src: "`+t+`"`+a+s+`, ...(`+c+`)`+o+`, ssr: (`+c+`).ssr !== undefined ? (`+c+`).ssr : true })}`}function x(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 S(e,t,n,r,i){let a=`<${t}`,o=``,s=0;for(;s<e.length;){if(e[s]==="`"){let t=s;s=p(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(!x(e,s,a)){o+=e[s],s++;continue}let c=y(e,s,t);if(!c||!c.islandProp&&!i){let t=c?c.endIdx:s+1;o+=e.slice(s,t),s=t;continue}o+=b(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,d){let f=a(d,r,o);if(!i(d,n,o)&&!f)return null;let p=t(e);if(p.length===0||!c(e,p.map(e=>e.localName))&&!l(e,p))return null;let m=u(e,p,d,s);if(m.size===0)return null;let h=`import { renderIsland as __pageRenderIsland } from '@useavalon/avalon';\n${e}`;for(let[e,t]of m)h=S(h,e,t.srcPath,t.framework,t.autoIsland);return{code:h,map:null}}}}
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{devError as r,devLog as i,devWarn as a,isDev as o,logRenderTiming as s}from"../utils/dev-logger.js";import{analyzeComponentFile as c,renderComponentSSROnly as l}from"./component-analysis.js";import{detectFramework as u}from"./framework-detection.js";import{isCustomDirective as d,serializeDirectiveScript as f}from"./hydration-directives.js";import{detectFrameworkFromPath as p,loadIntegration as m}from"./integration-loader.js";import{addModulepreload as h}from"./modulepreload-collector.js";import{generatePerIslandScript as g}from"./per-island-script.js";import{addUniversalCSS as _}from"./universal-css-collector.js";import{addUniversalHead as v}from"./universal-head-collector.js";function y(e){return`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}`}function b(){return globalThis.__avalonHydrationMode===void 0?globalThis.__viteDevServer?!1:typeof __AVALON_PER_ISLAND__<`u`?__AVALON_PER_ISLAND__:!o():globalThis.__avalonHydrationMode===`per-island`}function x(r,i){if(!b()||i.shouldSkipHydration)return r;let a=n(i.src);i.condition===`on:client`&&h(a);let o=d(i.condition),s=o?f(i.condition):void 0;return t(e,null,r,t(`div`,{dangerouslySetInnerHTML:{__html:g({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 S(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 C(e,t,r,i,a){let o={"data-condition":t,"data-src":n(e),"data-props":JSON.stringify(r),"data-render-strategy":`hydrate`,...S(i)};if(d(t)){o[`data-custom-directive`]=t;let e=f(t);e&&(o[`data-directive-script`]=e)}return a&&(o[`data-condition-arg`]=a),o}function w(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 T(e){let t=e.match(/<style[^>]*>([\s\S]*?)<\/style>/i);return t?t[1].trim():null}function E(e,t,n,r){if(e.css&&_(e.css,t,n,e.scopeId),e.head){let a=e.head.trim(),o=w(a);if(o===`style`){let o=T(a);o&&(i(`${r} Extracting CSS from head <style> tag`),_(o,t,n,e.scopeId));return}v(e.head,t,n,o)}}function D(e){let{islandId:n,detectedFramework:r,shouldSkipHydration:a,src:o,condition:s,conditionArg:c,props:l,hydrationData:u,children:d}=e,f={id:n,"data-framework":r},p=a?{"data-render-strategy":`ssr-only`}:C(o,s,l,u,c);r===`lit`&&i(`🔍 [Island Component] ${o} - 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),x(h,{islandId:n,src:o,condition:s,conditionArg:c,props:l,framework:r,shouldSkipHydration:a})}function O(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 p={id:r,"data-condition":s,"data-src":n(o),"data-props":JSON.stringify(c),"data-render-strategy":`hydrate`,"data-framework":i,...S(l)};if(d(s)){p[`data-custom-directive`]=s;let e=f(s);e&&(p[`data-directive-script`]=e)}return u&&(p[`data-condition-arg`]=u),x(t(`avalon-island`,p),{islandId:r,src:o,condition:s,conditionArg:u,props:c,framework:i,shouldSkipHydration:a})}export default function k({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:o,ssr:s=t!==`on:client`,framework:c,ssrOnly:l=!1,renderOptions:u={},hydrationData:d={}}){let f=y(e),m=l||!!u.forceSSROnly,h=c||p(e),g=o!=null&&o!==``;return i(`🔍 [Island Component] ${e}`,{ssr:s,ssrOnly:l,hasChildren:g,framework:c,condition:t}),s&&g?D({islandId:f,detectedFramework:h,shouldSkipHydration:m,src:e,condition:t,conditionArg:n,props:r,hydrationData:d,children:o}):(s&&!g&&m&&a(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),O({islandId:f,detectedFramework:h,shouldSkipHydration:m,src:e,condition:t,props:r,hydrationData:d,conditionArg:n}))}function A(e,i){let a=i instanceof Error?i.message:String(i);return r(`🚨 Island SSR failed for ${e}:`,i),i instanceof Error&&i.stack&&r(`Stack trace:`,i.stack),t(`avalon-island`,{id:y(e),"data-src":n(e),"data-ssr-error":a,"data-render-strategy":`client-only`})}async function j({src:e,condition:t,conditionArg:n,props:i,children:a,ssr:s,framework:c,ssrOnly:l,renderOptions:u,component:d}){let f=`🏝️ [${e}]`;if(!s||a)return k({src:e,condition:t,conditionArg:n,props:i,children:a,ssr:s,framework:c,ssrOnly:l,renderOptions:u});let p;try{p=await m(c)}catch(a){return r(`${f} Failed to load ${c} integration:`,a),k({src:e,condition:t,conditionArg:n,props:i,ssr:!1,framework:c,ssrOnly:l,renderOptions:u})}try{let r=await p.render({component:d??null,props:i,src:e,condition:t,ssrOnly:l,viteServer:globalThis.__viteDevServer,isDev:o()});return E(r,e,c,f),k({src:e,condition:t,conditionArg:n,props:i,children:r.html,ssr:!0,framework:c,ssrOnly:l,renderOptions:u,hydrationData:l?void 0:r.hydrationData})}catch(a){return r(`${f} Fast path SSR failed:`,a),k({src:e,condition:t,conditionArg:n,props:i,ssr:!1,framework:c,ssrOnly:l,renderOptions:u})}}async function M(e,t,n,r){if(t||n.detectScripts===!1)return t;try{let t=await c(e,n);if(t.decision.warnings?.length)for(let e of t.decision.warnings)a(`${r} Analysis warning: ${e}`);return!t.decision.shouldHydrate}catch(e){return a(`${r} Component analysis failed:`,e),t}}async function N(e){return e.endsWith(`.vue`)?`vue`:e.endsWith(`.svelte`)?`svelte`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)||e.endsWith(`.ts`)||e.endsWith(`.js`)?u(e):`unknown`}async function P(e,t,n,r,i,a,s){let c=await N(e),l=c,u=await(await F(c,a)).render({component:s??null,props:n,src:e,condition:t,ssrOnly:r,viteServer:globalThis.__viteDevServer,isDev:o()});return E(u,e,c,a),k({src:e,condition:t,props:n,children:u.html,ssr:!0,framework:l,ssrOnly:r,renderOptions:i,hydrationData:r?void 0:u.hydrationData})}async function F(e,t){try{i(`${t} Loading integration for framework: ${e}`);let n=await m(e);return i(`${t} ✅ Integration loaded successfully`),n}catch(n){throw r(`${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:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:a=t!==`on:client`,framework:c,ssrOnly:l=!1,renderOptions:u={},component:d}){let f=o()?performance.now():0,p=`🏝️ [${e}]`;try{return l&&!a&&(a=!0),c?await j({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:c,ssrOnly:l,renderOptions:u,component:d}):await I({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,ssrOnly:l,renderOptions:u,logPrefix:p,component:d})}catch(t){return A(e,t)}finally{o()&&s(e,performance.now()-f)}}async function I(e){let{src:t,condition:n,conditionArg:a,props:o,children:s,ssr:c,ssrOnly:l,renderOptions:u,logPrefix:d,component:f}=e;if(i(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:c,ssrOnly:l,hasChildren:!!s,condition:n}),await M(t,l,u,d))return L(t,n,o,s,c,u,d);if(!c||s)return k({src:t,condition:n,conditionArg:a,props:o,children:s,ssr:c,renderOptions:u});try{return await P(t,n,o,l,u,d,f)}catch(e){let i=await N(t);return r(`${d} Framework rendering failed:`,e),k({src:t,condition:n,conditionArg:a,props:o,ssr:!1,framework:i,renderOptions:u})}}function L(e,t,n,i,a,o,s){return a&&!i?l({src:e,condition:t,props:n,renderOptions:o}).catch(i=>(r(`${s} SSR failed for SSR-only component:`,i),k({src:e,condition:t,props:n,ssr:!1,ssrOnly:!0,renderOptions:o}))):k({src:e,condition:t,props:n,children:i,ssr:a,ssrOnly:!0,renderOptions:o})}
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(`&`,`&amp;`).replaceAll(`"`,`&quot;`).replaceAll(`<`,`&lt;`).replaceAll(`>`,`&gt;`)}
@@ -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 _,validateActiveIntegrations as v}from"./validation.js";export async function collectIntegrationPlugins(e,t=!1){let n=[],r=[];for(let i of e){let e=await y(i,t);i===`lit`?r.push(...e):n.push(...e)}return[...r,...n]}async function y(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 b(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 x(e){if(!e.lazyIntegrations||e.integrations.length===0)return[...e.integrations];let t=await b(e);return t.size===0?[...e.integrations]:Array.from(t)}async function S(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 C(e,t,n){let{plugins:r,nitroOptions:i}=g(e,t);return globalThis.__nitroConfig=i,{plugins:r,options:i}}async function w(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 T(e,t){if(!e.validateIntegrations||t.size===0)return;let n=v(t,e.showWarnings);n.allValid||(console.error(_(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 x(m);g.length>0&&await p({...m,integrations:g},l);let _=await S(m),v=await f(m.image,m.verbose),y=[];l.size>0&&(y=await collectIntegrationPlugins(l,m.verbose));let b=[];if(a?.nitro){let{plugins:e}=C(m,a.nitro,m.verbose);b=e}let E=h({verbose:m.verbose}),D=e(import.meta.url),O=null;try{O=n(t(D.resolve(`@useavalon/avalon/client`)),`main.js`)}catch{}let k=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`])),A={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`&&O)return O;if(k.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 w(o,c?.root,l),T(o,l)},configureServer(e){globalThis.__viteDevServer=e}},j=y.filter(e=>e.name?.includes(`lit`)),M=y.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),...v,...j,..._,A,E,...b,...M]}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}
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.90",
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.8",
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",