@useavalon/avalon 0.1.91 → 0.1.93-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/avalon.ts +17 -0
- package/dist/mod.d.ts +1 -0
- package/dist/src/build/page-island-transform.js +2 -2
- package/dist/src/client/server-island-hydrate.d.ts +12 -0
- package/dist/src/client/server-island-hydrate.js +1 -0
- package/dist/src/islands/framework-base-css.d.ts +2 -2
- package/dist/src/islands/framework-base-css.js +1 -1
- package/dist/src/islands/island.d.ts +7 -1
- package/dist/src/islands/island.js +1 -1
- package/dist/src/islands/types.d.ts +3 -0
- package/dist/src/nitro/renderer.js +1 -1
- package/dist/src/server-islands/encryption.d.ts +32 -0
- package/dist/src/server-islands/encryption.js +1 -0
- package/dist/src/server-islands/endpoint.d.ts +48 -0
- package/dist/src/server-islands/endpoint.js +4 -0
- package/dist/src/server-islands/manifest.d.ts +27 -0
- package/dist/src/server-islands/manifest.js +1 -0
- package/dist/src/server-islands/renderer.d.ts +18 -0
- package/dist/src/server-islands/renderer.js +17 -0
- package/dist/src/server-islands/route.d.ts +15 -0
- package/dist/src/server-islands/route.js +1 -0
- package/dist/src/server-islands/types.d.ts +17 -0
- package/dist/src/server-islands/types.js +1 -0
- package/dist/src/types/island-jsx.d.ts +4 -1
- package/dist/src/types/island-prop.d.ts +7 -0
- package/dist/src/types/virtual-modules.d.ts +12 -0
- package/dist/src/vite-plugin/nitro-integration.js +17 -12
- package/dist/src/vite-plugin/plugin.js +1 -1
- package/dist/src/vite-plugin/server-islands-plugin.d.ts +39 -0
- package/dist/src/vite-plugin/server-islands-plugin.js +3 -0
- package/package.json +5 -2
package/bin/avalon.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { generateKey } from "../src/server-islands/encryption.ts";
|
|
3
|
+
|
|
4
|
+
const command = process.argv[2];
|
|
5
|
+
|
|
6
|
+
if (command === "key") {
|
|
7
|
+
const key = generateKey();
|
|
8
|
+
console.log(`\nGenerated AVALON_KEY:\n`);
|
|
9
|
+
console.log(` ${key}\n`);
|
|
10
|
+
console.log(`Set this in your environment:\n`);
|
|
11
|
+
console.log(` export AVALON_KEY="${key}"\n`);
|
|
12
|
+
} else {
|
|
13
|
+
console.log("Usage: avalon <command>\n");
|
|
14
|
+
console.log("Commands:");
|
|
15
|
+
console.log(" key Generate a cryptographically random AES-256-GCM key");
|
|
16
|
+
process.exit(command === "--help" || command === "-h" ? 0 : 1);
|
|
17
|
+
}
|
package/dist/mod.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type { CacheConfig as IslandCacheConfig, CacheStats as IslandCacheStats,
|
|
|
23
23
|
export { clearCache, clearIslandCache, configureCache, getCacheConfig, getCacheStats, invalidateCacheForFile, invalidateCacheForPath, logCacheStats, } from "./src/islands/render-cache.ts";
|
|
24
24
|
export type { Framework, RenderParams, SvelteSSRCSSEntry } from "./src/islands/types.ts";
|
|
25
25
|
export { renderToHtml } from "./src/render/ssr.ts";
|
|
26
|
+
export type { ServerIslandProp } from "./src/server-islands/types.ts";
|
|
26
27
|
export type { IslandDirective } from "./src/types/island-prop.d.ts";
|
|
27
28
|
export type { NitroCoordinationPluginOptions, NitroIntegrationResult, } from "./src/vite-plugin/nitro-integration.ts";
|
|
28
29
|
export { createNitroCoordinationPlugin, createNitroIntegration, createVirtualModulesPlugin, getAvalonConfig, getViteDevServer, isDevelopmentMode, RESOLVED_VIRTUAL_IDS, VIRTUAL_MODULE_IDS, } from "./src/vite-plugin/nitro-integration.ts";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{dirname as e}from"node:path";function
|
|
2
|
-
`,s),n=t===-1?e.length:t+1;o+=e.slice(s,n),s=n;continue}if(e[s]===`/`&&e[s+1]===`*`){let t=e.indexOf(`*/`,s+2),n=t===-1?e.length:t+2;o+=e.slice(s,n),s=n;continue}if(!
|
|
1
|
+
import{dirname as e}from"node:path";import{addToManifest as t,generateComponentId as n}from"../server-islands/manifest.js";function r(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 i(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 a(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 o(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 s(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 c=new Set([`qwik`]);function l(e){let t=a(e);return t!==void 0&&c.has(t)}function u(e,t){return t.some(t=>RegExp(`<${t}${String.raw`[\s][^>]*island[\s]*[={]`}`).test(e))}function d(e,t){return t.some(t=>RegExp(`<${t}${String.raw`[\s][^>]*server[\s]*[={]`}`).test(e))}function f(e,t){return t.some(t=>l(t.importPath)?RegExp(`<${t.localName}${String.raw`[\s/>]`}`).test(e):!1)}function p(e,r,o,s=[]){let l=new Map;for(let u of r){let r=i(u.importPath,o,s),d=a(r),f=RegExp(`<${u.localName}${String.raw`[\s][^>]*island[\s]*[={]`}`),p=RegExp(`<${u.localName}${String.raw`[\s][^>]*server[\s]*[={]`}`),m=f.test(e),h=p.test(e);if(m||h){l.set(u.localName,{srcPath:r,framework:d,importPath:u.importPath,autoIsland:!1,hasServerProp:h}),h&&t(n(r),r);continue}d&&c.has(d)&&RegExp(`<${u.localName}${String.raw`[\s/>]`}`).test(e)&&l.set(u.localName,{srcPath:r,framework:d,importPath:u.importPath,autoIsland:!0,hasServerProp:!1})}return l}function m(e,t){for(;t<e.length&&/\s/.test(e[t]);)t++;return t}function h(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 g(e,t){for(t++;t<e.length&&e[t]!=="`";){if(e[t]===`\\`){t+=2;continue}if(e[t]===`$`&&e[t+1]===`{`){t=_(t+1,e);continue}t++}return t<e.length?t+1:t}function _(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=h(t,n):n++}return n<t.length?n+1:n}function v(e,t){let n=t+1,r=_(t,e);return{value:e.slice(n,r-1),endIdx:r}}function y(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 b(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=m(e,r),e[r]!==`=`)return{name:i,value:null,endIdx:r};if(r=m(e,r+1),e[r]===`{`){let t=v(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}if(e[r]===`"`||e[r]===`'`){let t=y(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}return null}function x(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 S(e,t,n){let r=m(e,t+1+n.length),i=null,a=null,o=[];for(;r<e.length;){r=m(e,r);let t=x(e,r,n);if(t)return{endIdx:t.endIdx,islandProp:i,serverProp:a,otherProps:o};let s=b(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 C(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 w(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 T(e,t,n,r,i){let a=`<${t}`,o=``,s=0;for(;s<e.length;){if(e[s]==="`"){let t=s;s=g(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(!w(e,s,a)){o+=e[s],s++;continue}let c=S(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+=C(c,n,r,i&&!c.islandProp,t),s=c.endIdx}return o}export function pageIslandTransform(e={}){let{pagesDir:t=`src/pages`,layoutsDir:n=`src/layouts`,modules:i=null}=e,a=[];return{name:`avalon:page-island-transform`,enforce:`pre`,configResolved(e){a=e.resolve?.alias??[]},transform(e,c){let l=s(c,n,i);if(!o(c,t,i)&&!l)return null;let m=r(e);if(m.length===0)return null;let h=m.map(e=>e.localName);if(!u(e,h)&&!d(e,h)&&!f(e,m))return null;let g=p(e,m,c,a);if(g.size===0)return null;let _=`import { renderIsland as __pageRenderIsland } from '@useavalon/avalon';\n${e}`;for(let[e,t]of g)_=T(_,e,t.srcPath,t.framework,t.autoIsland);return{code:_,map:null}}}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hydrate a server island element with a component, respecting the hydration strategy.
|
|
3
|
+
* Uses the framework integration system — same as normal island hydration.
|
|
4
|
+
*
|
|
5
|
+
* @param elementId - DOM id of the <avalon-server-island> wrapper
|
|
6
|
+
* @param componentPath - Module path to import the component from
|
|
7
|
+
* @param props - Component props
|
|
8
|
+
* @param condition - Hydration strategy (on:client, on:visible, on:interaction, on:idle)
|
|
9
|
+
* @param framework - Framework identifier (preact, react, solid, vue, svelte)
|
|
10
|
+
* @param renderId - Optional framework hydration key (Solid uses this to match data-hk markers)
|
|
11
|
+
*/
|
|
12
|
+
export declare function hydrateServerIsland(elementId: string, componentPath: string, props: Record<string, unknown>, condition?: string, framework?: string, renderId?: string): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{loadIntegrationModule as e}from"virtual:avalon/integration-loader";function t(e){if(e.default)return e.default;for(let t of Object.values(e))if(typeof t==`function`)return t;return e}const n=5e3;export async function hydrateServerIsland(r,i,a,o=`on:client`,s=`preact`,c){let l=document.getElementById(r);if(!l||l.dataset.hydrated)return;c&&(l.dataset.solidRenderId=c);let u=async()=>{if(l.dataset.hydrated)return;let n=t(await import(i)),r=await e(s);if(!r.hydrate||typeof r.hydrate!=`function`)throw Error(`Integration ${s} does not export a hydrate function`);await r.hydrate(l,n,a),l.dataset.hydrated=`true`};if(o===`on:client`)await u();else if(o===`on:visible`){let e=l.firstElementChild||l,t=new IntersectionObserver(e=>{e[0].isIntersecting&&(u(),t.disconnect())},{rootMargin:`50px`,threshold:0});t.observe(e)}else if(o===`on:interaction`){let e=()=>{u(),l.removeEventListener(`mouseenter`,e),l.removeEventListener(`focusin`,e),l.removeEventListener(`click`,e)};l.addEventListener(`mouseenter`,e),l.addEventListener(`focusin`,e),l.addEventListener(`click`,e)}else o===`on:idle`?`requestIdleCallback`in globalThis?requestIdleCallback(()=>u(),{timeout:n}):setTimeout(()=>u(),n):await u()}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Framework Baseline CSS
|
|
3
3
|
*
|
|
4
4
|
* Provides default styling for Avalon's framework-emitted custom elements
|
|
5
|
-
* (`<avalon-island>`, `<avalon-page>`, `<avalon-page-content>`).
|
|
5
|
+
* (`<avalon-island>`, `<avalon-page>`, `<avalon-page-content>`, `<avalon-server-island>`).
|
|
6
6
|
*
|
|
7
7
|
* These elements are used as DOM anchors for hydration and content
|
|
8
8
|
* placement. They must be transparent to layout — otherwise an island
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
*/
|
|
34
34
|
export declare function injectFrameworkBaseCSS(html: string): string;
|
|
35
35
|
/** Exposed for tests */
|
|
36
|
-
export declare const __FRAMEWORK_BASE_CSS = "avalon-island,avalon-page,avalon-page-content{display:contents}";
|
|
36
|
+
export declare const __FRAMEWORK_BASE_CSS = "avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=`avalon-island,avalon-page,avalon-page-content{display:contents}`,t=`<style data-avalon-base="true">${e}</style>`;export function injectFrameworkBaseCSS(e){return e.includes(`data-avalon-base="true"`)||!e.includes(`<head>`)&&!e.includes(`<head `)?e:e.replace(/<head(\s[^>]*)?>/i,e=>`${e}${t}`)}export const __FRAMEWORK_BASE_CSS=e;
|
|
1
|
+
const e=`avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}`,t=`<style data-avalon-base="true">${e}</style>`;export function injectFrameworkBaseCSS(e){return e.includes(`data-avalon-base="true"`)||!e.includes(`<head>`)&&!e.includes(`<head `)?e:e.replace(/<head(\s[^>]*)?>/i,e=>`${e}${t}`)}export const __FRAMEWORK_BASE_CSS=e;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { JSX } from "preact";
|
|
2
2
|
import type { ViteDevServer } from "vite";
|
|
3
3
|
import type { AnalyzerOptions } from "../core/components/component-analyzer.ts";
|
|
4
|
+
import type { ServerIslandProp } from "../server-islands/types.ts";
|
|
5
|
+
import type { IslandDirective } from "../types/island-prop.d.ts";
|
|
4
6
|
import type { Framework } from "./types.ts";
|
|
5
7
|
declare global {
|
|
6
8
|
var __viteDevServer: ViteDevServer | undefined;
|
|
@@ -36,6 +38,10 @@ export interface IslandProps {
|
|
|
36
38
|
hydrationData?: Record<string, unknown>;
|
|
37
39
|
/** Pre-imported component reference (avoids dynamic import in bundled SSR) */
|
|
38
40
|
component?: unknown;
|
|
41
|
+
/** Server island configuration — defers rendering to a server endpoint after page load */
|
|
42
|
+
server?: ServerIslandProp;
|
|
43
|
+
/** Island directive for combined server + client islands */
|
|
44
|
+
island?: IslandDirective;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* Universal Island component – renders `<avalon-island>` custom elements for better DOM structure.
|
|
@@ -60,4 +66,4 @@ export default function Island({ src, condition, conditionArg, props, children,
|
|
|
60
66
|
* Error isolation: If SSR fails, returns an error placeholder instead of throwing,
|
|
61
67
|
* allowing the page to continue rendering other islands.
|
|
62
68
|
*/
|
|
63
|
-
export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, }: IslandProps): Promise<JSX.Element>;
|
|
69
|
+
export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, server, island, }: IslandProps): Promise<JSX.Element>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Fragment as e,h as t}from"preact";import{getIslandBundlePath as n}from"../build/island-manifest.js";import{
|
|
1
|
+
import{Fragment as e,h as t}from"preact";import{getIslandBundlePath as n}from"../build/island-manifest.js";import{addToManifest as r,generateComponentId as i}from"../server-islands/manifest.js";import{renderServerIsland as a}from"../server-islands/renderer.js";import{devError as o,devLog as s,devWarn as c,isDev as l,logRenderTiming as u}from"../utils/dev-logger.js";import{analyzeComponentFile as d,renderComponentSSROnly as f}from"./component-analysis.js";import{detectFramework as p}from"./framework-detection.js";import{isCustomDirective as m,serializeDirectiveScript as h}from"./hydration-directives.js";import{detectFrameworkFromPath as g,loadIntegration as _}from"./integration-loader.js";import{addModulepreload as v}from"./modulepreload-collector.js";import{generatePerIslandScript as y}from"./per-island-script.js";import{addUniversalCSS as b}from"./universal-css-collector.js";import{addUniversalHead as x}from"./universal-head-collector.js";function S(e){return`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}`}function C(){return globalThis.__avalonHydrationMode===void 0?globalThis.__viteDevServer?!1:typeof __AVALON_PER_ISLAND__<`u`?__AVALON_PER_ISLAND__:!l():globalThis.__avalonHydrationMode===`per-island`}function w(r,i){if(!C()||i.shouldSkipHydration)return r;let a=n(i.src);i.condition===`on:client`&&v(a);let o=m(i.condition),s=o?h(i.condition):void 0;return t(e,null,r,t(`div`,{dangerouslySetInnerHTML:{__html:y({islandId:i.islandId,componentSrc:a,framework:i.framework,condition:i.condition,conditionArg:i.conditionArg,propsJson:JSON.stringify(i.props),isCustomDirective:o,directiveScript:s??void 0})},"data-island-script":``}))}function T(e){let t={};e.renderId&&(t[`data-solid-render-id`]=e.renderId);let n=e.metadata;return n?.tagName&&(t[`data-tag-name`]=n.tagName),t}function E(e,t,r,i,a){let o={"data-condition":t,"data-src":n(e),"data-props":JSON.stringify(r),"data-render-strategy":`hydrate`,...T(i)};if(m(t)){o[`data-custom-directive`]=t;let e=h(t);e&&(o[`data-directive-script`]=e)}return a&&(o[`data-condition-arg`]=a),o}function D(e){return e.startsWith(`<script`)?`script`:e.startsWith(`<style`)?`style`:e.startsWith(`<meta`)?`meta`:e.startsWith(`<link`)?`link`:e.includes(`window._$HY`)||e.includes(`_$HY=`)?`script`:`other`}function O(e){let t=e.match(/<style[^>]*>([\s\S]*?)<\/style>/i);return t?t[1].trim():null}function k(e,t,n,r){if(e.css&&b(e.css,t,n,e.scopeId),e.head){let i=e.head.trim(),a=D(i);if(a===`style`){let a=O(i);a&&(s(`${r} Extracting CSS from head <style> tag`),b(a,t,n,e.scopeId));return}x(e.head,t,n,a)}}function A(e){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,conditionArg:c,props:l,hydrationData:u,children:d}=e,f={id:n,"data-framework":r},p=i?{"data-render-strategy":`ssr-only`}:E(a,o,l,u,c);r===`lit`&&s(`🔍 [Island Component] ${a} - Lit hydration data:`,{hydrationDataKeys:Object.keys(u),metadata:u.metadata});let m={...f,...p},h;return h=typeof d==`string`?t(`avalon-island`,{...m,dangerouslySetInnerHTML:{__html:d}}):t(`avalon-island`,m,d),w(h,{islandId:n,src:a,condition:o,conditionArg:c,props:l,framework:r,shouldSkipHydration:i})}function j(e){let{islandId:r,detectedFramework:i,shouldSkipHydration:a,src:o,condition:s,props:c,hydrationData:l,conditionArg:u}=e;if(a)return t(`avalon-island`,{id:r,"data-render-strategy":`ssr-only`,"data-framework":i});let d={id:r,"data-condition":s,"data-src":n(o),"data-props":JSON.stringify(c),"data-render-strategy":`hydrate`,"data-framework":i,...T(l)};if(m(s)){d[`data-custom-directive`]=s;let e=h(s);e&&(d[`data-directive-script`]=e)}return u&&(d[`data-condition-arg`]=u),w(t(`avalon-island`,d),{islandId:r,src:o,condition:s,conditionArg:u,props:c,framework:i,shouldSkipHydration:a})}export default function M({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:a=t!==`on:client`,framework:o,ssrOnly:l=!1,renderOptions:u={},hydrationData:d={}}){let f=S(e),p=l||!!u.forceSSROnly,m=o||g(e),h=i!=null&&i!==``;return s(`🔍 [Island Component] ${e}`,{ssr:a,ssrOnly:l,hasChildren:h,framework:o,condition:t}),a&&h?A({islandId:f,detectedFramework:m,shouldSkipHydration:p,src:e,condition:t,conditionArg:n,props:r,hydrationData:d,children:i}):(a&&!h&&p&&c(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),j({islandId:f,detectedFramework:m,shouldSkipHydration:p,src:e,condition:t,props:r,hydrationData:d,conditionArg:n}))}function N(e,r){let i=r instanceof Error?r.message:String(r);return o(`🚨 Island SSR failed for ${e}:`,r),r instanceof Error&&r.stack&&o(`Stack trace:`,r.stack),t(`avalon-island`,{id:S(e),"data-src":n(e),"data-ssr-error":i,"data-render-strategy":`client-only`})}async function P({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u,component:d}){let f=`🏝️ [${e}]`;if(!a||i)return M({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u});let p;try{p=await _(s)}catch(i){return o(`${f} Failed to load ${s} integration:`,i),M({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}try{let i=await p.render({component:d??null,props:r,src:e,condition:t,ssrOnly:c,viteServer:globalThis.__viteDevServer,isDev:l()});return k(i,e,s,f),M({src:e,condition:t,conditionArg:n,props:r,children:i.html,ssr:!0,framework:s,ssrOnly:c,renderOptions:u,hydrationData:c?void 0:i.hydrationData})}catch(i){return o(`${f} Fast path SSR failed:`,i),M({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}}async function F(e,t,n,r){if(t||n.detectScripts===!1)return t;try{let t=await d(e,n);if(t.decision.warnings?.length)for(let e of t.decision.warnings)c(`${r} Analysis warning: ${e}`);return!t.decision.shouldHydrate}catch(e){return c(`${r} Component analysis failed:`,e),t}}async function I(e){return e.endsWith(`.vue`)?`vue`:e.endsWith(`.svelte`)?`svelte`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)||e.endsWith(`.ts`)||e.endsWith(`.js`)?p(e):`unknown`}async function L(e,t,n,r,i,a,o){let s=await I(e),c=s,u=await(await R(s,a)).render({component:o??null,props:n,src:e,condition:t,ssrOnly:r,viteServer:globalThis.__viteDevServer,isDev:l()});return k(u,e,s,a),M({src:e,condition:t,props:n,children:u.html,ssr:!0,framework:c,ssrOnly:r,renderOptions:i,hydrationData:r?void 0:u.hydrationData})}async function R(e,t){try{s(`${t} Loading integration for framework: ${e}`);let n=await _(e);return s(`${t} ✅ Integration loaded successfully`),n}catch(n){throw o(`${t} Failed to load ${e} integration:`,n),Error(`Failed to load integration for framework '${e}'. Make sure @useavalon/${e} is installed.\nInstall it with: deno add @useavalon/${e}`,{cause:n})}}export async function renderIsland({src:o,condition:s=`on:client`,conditionArg:c,props:d={},children:f,ssr:p=s!==`on:client`,framework:m,ssrOnly:h=!1,renderOptions:_={},component:v,server:y,island:b}){let x=l()?performance.now():0,S=`🏝️ [${o}]`;try{if(y){let s=i(o);r(s,o);let c=m||g(o);return t(e,null,t(`div`,{dangerouslySetInnerHTML:{__html:a(s,d,y,b,b?n(o):void 0,o,c)},"data-server-island-wrapper":``}))}return h&&!p&&(p=!0),m?await P({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:p,framework:m,ssrOnly:h,renderOptions:_,component:v}):await z({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:p,ssrOnly:h,renderOptions:_,logPrefix:S,component:v})}catch(e){return N(o,e)}finally{l()&&u(o,performance.now()-x)}}async function z(e){let{src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,ssrOnly:l,renderOptions:u,logPrefix:d,component:f}=e;if(s(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:c,ssrOnly:l,hasChildren:!!a,condition:n}),await F(t,l,u,d))return B(t,n,i,a,c,u,d);if(!c||a)return M({src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,renderOptions:u});try{return await L(t,n,i,l,u,d,f)}catch(e){let a=await I(t);return o(`${d} Framework rendering failed:`,e),M({src:t,condition:n,conditionArg:r,props:i,ssr:!1,framework:a,renderOptions:u})}}function B(e,t,n,r,i,a,s){return i&&!r?f({src:e,condition:t,props:n,renderOptions:a}).catch(r=>(o(`${s} SSR failed for SSR-only component:`,r),M({src:e,condition:t,props:n,ssr:!1,ssrOnly:!0,renderOptions:a}))):M({src:e,condition:t,props:n,children:r,ssr:i,ssrOnly:!0,renderOptions:a})}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JSX } from "preact";
|
|
2
2
|
import type { AnalyzerOptions } from "../core/components/component-analyzer.ts";
|
|
3
|
+
import type { ServerIslandProp } from "../server-islands/types.ts";
|
|
3
4
|
/**
|
|
4
5
|
* Framework type for island components
|
|
5
6
|
* Represents the supported UI frameworks for island architecture
|
|
@@ -28,6 +29,8 @@ export interface IslandProps {
|
|
|
28
29
|
ssrOnly?: boolean;
|
|
29
30
|
/** Component render options for intelligent detection */
|
|
30
31
|
renderOptions?: AnalyzerOptions;
|
|
32
|
+
/** Server island configuration — defers rendering to a server endpoint after page load */
|
|
33
|
+
server?: ServerIslandProp;
|
|
31
34
|
}
|
|
32
35
|
/**
|
|
33
36
|
* Parameters for framework-specific render functions
|
|
@@ -181,4 +181,4 @@ import{getRequestURL as e}from"h3";import{h as t}from"preact";import n from"prea
|
|
|
181
181
|
</details>
|
|
182
182
|
`:``}
|
|
183
183
|
</div>
|
|
184
|
-
`}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function D(e,t,n){return async()=>(e.value??=await a({baseDir:t,devMode:n}),e.value)}function O(e,t,n){return async(r,i)=>e?c(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:t,isDev:n=!1,enableCustomErrorPages:a=!0}=e,c={isDev:n,avalonConfig:t,loadPageModule:e.loadPageModule,pagesDir:t.pagesDir};a&&s(c).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let u=D({value:null},t.srcDir||`src`,n),m=O(a,c,n);async function h(a){let s=getRequestURL(a).pathname;try{let c=await o(a,await u(),{devMode:n});if(c)return n&&console.log(`[renderer] Middleware terminated request for ${s}`),c;let f=a.context.route,p=null;if(p=f||(e.resolvePageRoute?await e.resolvePageRoute(s,t.pagesDir):await k(s,t.pagesDir)),!p)return m(l(`Page not found: ${s}`),a);let h=e.loadPageModule?await e.loadPageModule(p.filePath):await A(p.filePath),g=createRenderContext(a,a.context.params||p.params);if(e.resolveLayouts&&(g.layoutContext={layouts:await e.resolveLayouts(s,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(h,g,{onShellReady:()=>{setResponseHeader(a,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(h,g,{},e.wrapWithLayouts),a=injectHydrationScript(t.html,n);return a=r(a),a=i(a),new Response(a,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Renderer Error]`,e),m(e instanceof Error?e:Error(String(e)),a)}}return Object.assign(h,{async fetch(a){let o=new URL(a.url,`http://localhost`).pathname;try{let s=null;if(s=e.resolvePageRoute?await e.resolvePageRoute(o,t.pagesDir):await k(o,t.pagesDir),!s)return createErrorResponse(l(`Page not found: ${o}`),n);let c=await renderPage(e.loadPageModule?await e.loadPageModule(s.filePath):await A(s.filePath),createRenderContextFromRequest(a,s.params),{},e.wrapWithLayouts),u=injectHydrationScript(c.html,n);return u=r(u),u=i(u),new Response(u,{status:c.statusCode,headers:c.headers})}catch(e){return console.error(`[Nitro Renderer .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}async function k(e,t){return e===`/`||e===``?{filePath:`src/pages/index.tsx`,pattern:`/`,params:{}}:{filePath:`src/pages/${e.replace(/^\//,``).replace(/\/$/,``)}.tsx`,pattern:e,params:{}}}async function A(e){return{default:()=>null,metadata:{title:`Avalon Page`}}}export function createNitroCatchAllRenderer(e){let{avalonConfig:t,isDev:n=!1,loadPageModule:a,resolveLayouts:c,enableCustomErrorPages:u=!0}=e,m={isDev:n,avalonConfig:t,loadPageModule:a,pagesDir:t.pagesDir};u&&s(m).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let h=D({value:null},t.srcDir||`src`,n),_=O(u,m,n);async function v(s){let u=getRequestURL(s).pathname;try{let f=await o(s,await h(),{devMode:n});if(f)return n&&console.log(`[renderer] Middleware terminated request for ${u}`),f;let p=s.context.params||{},m=p.slug||u.replace(/^\//,``)||`index`,g=`${t.pagesDir}/${m}.tsx`,v;try{v=await a(g)}catch(e){try{v=await a(`${t.pagesDir}/${m}/index.tsx`)}catch(r){return n&&(console.debug(`[renderer] Page not found: ${g}`,e),console.debug(`[renderer] Index fallback not found: ${t.pagesDir}/${m}/index.tsx`,r)),_(l(`Page not found: ${u}`),s)}}let y=createRenderContext(s,p);if(c&&(y.layoutContext={layouts:await c(u,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(v,y,{onShellReady:()=>{setResponseHeader(s,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(v,y,{},e.wrapWithLayouts),a=injectHydrationScript(t.html,n);return a=r(a),a=i(a),new Response(a,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Catch-All Renderer Error]`,e),_(e instanceof Error?e:Error(String(e)),s)}}return Object.assign(v,{async fetch(o){let s=new URL(o.url,`http://localhost`).pathname;try{let c=s.replace(/^\//,``)||`index`,u=`${t.pagesDir}/${c}.tsx`,d;try{d=await a(u)}catch{try{d=await a(`${t.pagesDir}/${c}/index.tsx`)}catch{return createErrorResponse(l(`Page not found: ${s}`),n)}}let p=createRenderContextFromRequest(o),m=await renderPage(d,p,{},e.wrapWithLayouts),h=injectHydrationScript(m.html,n);return h=r(h),h=i(h),new Response(h,{status:m.statusCode,headers:m.headers})}catch(e){return console.error(`[Nitro CatchAll .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}export{clearMiddlewareCache as clearRendererMiddlewareCache}from"../middleware/index.js";
|
|
184
|
+
`}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function D(e,t,n){return async()=>(e.value??=await a({baseDir:t,devMode:n}),e.value)}function O(e,t,n){return async(r,i)=>e?c(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:t,isDev:n=!1,enableCustomErrorPages:a=!0}=e,c={isDev:n,avalonConfig:t,loadPageModule:e.loadPageModule,pagesDir:t.pagesDir};a&&s(c).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let u=D({value:null},t.srcDir||`src`,n),m=O(a,c,n);async function h(a){let s=getRequestURL(a).pathname;if(s.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let c=await o(a,await u(),{devMode:n});if(c)return n&&console.log(`[renderer] Middleware terminated request for ${s}`),c;let f=a.context.route,p=null;if(p=f||(e.resolvePageRoute?await e.resolvePageRoute(s,t.pagesDir):await k(s,t.pagesDir)),!p)return m(l(`Page not found: ${s}`),a);let h=e.loadPageModule?await e.loadPageModule(p.filePath):await A(p.filePath),g=createRenderContext(a,a.context.params||p.params);if(e.resolveLayouts&&(g.layoutContext={layouts:await e.resolveLayouts(s,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(h,g,{onShellReady:()=>{setResponseHeader(a,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(h,g,{},e.wrapWithLayouts),a=injectHydrationScript(t.html,n);return a=r(a),a=i(a),new Response(a,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Renderer Error]`,e),m(e instanceof Error?e:Error(String(e)),a)}}return Object.assign(h,{async fetch(a){let o=new URL(a.url,`http://localhost`).pathname;if(o.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let s=null;if(s=e.resolvePageRoute?await e.resolvePageRoute(o,t.pagesDir):await k(o,t.pagesDir),!s)return createErrorResponse(l(`Page not found: ${o}`),n);let c=await renderPage(e.loadPageModule?await e.loadPageModule(s.filePath):await A(s.filePath),createRenderContextFromRequest(a,s.params),{},e.wrapWithLayouts),u=injectHydrationScript(c.html,n);return u=r(u),u=i(u),new Response(u,{status:c.statusCode,headers:c.headers})}catch(e){return console.error(`[Nitro Renderer .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}async function k(e,t){return e===`/`||e===``?{filePath:`src/pages/index.tsx`,pattern:`/`,params:{}}:{filePath:`src/pages/${e.replace(/^\//,``).replace(/\/$/,``)}.tsx`,pattern:e,params:{}}}async function A(e){return{default:()=>null,metadata:{title:`Avalon Page`}}}export function createNitroCatchAllRenderer(e){let{avalonConfig:t,isDev:n=!1,loadPageModule:a,resolveLayouts:c,enableCustomErrorPages:u=!0}=e,m={isDev:n,avalonConfig:t,loadPageModule:a,pagesDir:t.pagesDir};u&&s(m).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let h=D({value:null},t.srcDir||`src`,n),_=O(u,m,n);async function v(s){let u=getRequestURL(s).pathname;if(u.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let f=await o(s,await h(),{devMode:n});if(f)return n&&console.log(`[renderer] Middleware terminated request for ${u}`),f;let p=s.context.params||{},m=p.slug||u.replace(/^\//,``)||`index`,g=`${t.pagesDir}/${m}.tsx`,v;try{v=await a(g)}catch(e){try{v=await a(`${t.pagesDir}/${m}/index.tsx`)}catch(r){return n&&(console.debug(`[renderer] Page not found: ${g}`,e),console.debug(`[renderer] Index fallback not found: ${t.pagesDir}/${m}/index.tsx`,r)),_(l(`Page not found: ${u}`),s)}}let y=createRenderContext(s,p);if(c&&(y.layoutContext={layouts:await c(u,t)}),t.streaming&&!e.wrapWithLayouts){let e=await renderPageStream(v,y,{onShellReady:()=>{setResponseHeader(s,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(v,y,{},e.wrapWithLayouts),a=injectHydrationScript(t.html,n);return a=r(a),a=i(a),new Response(a,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Catch-All Renderer Error]`,e),_(e instanceof Error?e:Error(String(e)),s)}}return Object.assign(v,{async fetch(o){let s=new URL(o.url,`http://localhost`).pathname;if(s.startsWith(`/_server-islands/`))return new Response(`Not handled by renderer`,{status:404});try{let c=s.replace(/^\//,``)||`index`,u=`${t.pagesDir}/${c}.tsx`,d;try{d=await a(u)}catch{try{d=await a(`${t.pagesDir}/${c}/index.tsx`)}catch{return createErrorResponse(l(`Page not found: ${s}`),n)}}let p=createRenderContextFromRequest(o),m=await renderPage(d,p,{},e.wrapWithLayouts),h=injectHydrationScript(m.html,n);return h=r(h),h=i(h),new Response(h,{status:m.statusCode,headers:m.headers})}catch(e){return console.error(`[Nitro CatchAll .fetch() Error]`,e),createErrorResponse(e instanceof Error?e:Error(String(e)),n)}}})}export{clearMiddlewareCache as clearRendererMiddlewareCache}from"../middleware/index.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a new random AES-256 key as a base64 string.
|
|
3
|
+
*/
|
|
4
|
+
export declare function generateKey(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Gets the encryption key from the AVALON_KEY env var, or falls back to a
|
|
7
|
+
* per-process build-time key in development.
|
|
8
|
+
*
|
|
9
|
+
* In production, AVALON_KEY is REQUIRED: a per-process key would cause
|
|
10
|
+
* cross-process decryption failures (one instance encrypts, another can't
|
|
11
|
+
* decrypt). We throw to surface the misconfiguration instead of failing
|
|
12
|
+
* silently at request time.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getKey(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Encrypts data using AES-256-GCM.
|
|
17
|
+
* Output format: base64url(iv + ciphertext + authTag)
|
|
18
|
+
*
|
|
19
|
+
* @param data - The plaintext string to encrypt
|
|
20
|
+
* @param key - Optional base64-encoded key. Defaults to getKey().
|
|
21
|
+
* @returns base64url-encoded encrypted string
|
|
22
|
+
*/
|
|
23
|
+
export declare function encrypt(data: string, key?: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Decrypts a base64url-encoded AES-256-GCM encrypted string.
|
|
26
|
+
*
|
|
27
|
+
* @param data - The base64url-encoded encrypted string
|
|
28
|
+
* @param key - Optional base64-encoded key. Defaults to getKey().
|
|
29
|
+
* @returns The decrypted plaintext string
|
|
30
|
+
* @throws Error if decryption fails (tampered data, wrong key, etc.)
|
|
31
|
+
*/
|
|
32
|
+
export declare function decrypt(data: string, key?: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createCipheriv as e,createDecipheriv as t,randomBytes as n}from"node:crypto";const r=`aes-256-gcm`;let i,a=!1;export function generateKey(){return n(32).toString(`base64`)}export function getKey(){let e=process.env.AVALON_KEY;if(e){let t=Buffer.from(e,`base64`);if(t.length!==32)throw Error(`AVALON_KEY must be a base64-encoded 256-bit (32-byte) key. Got ${t.length} bytes.`);return e}if(process.env.NODE_ENV===`production`)throw Error("AVALON_KEY is required in production for server islands. Generate one with `npx avalon key` and set it as an environment variable so all server instances share the same encryption secret.");return a||(a=!0,console.warn(`[avalon] AVALON_KEY not set — using a per-process key for server islands. This only works for single-process development. Set AVALON_KEY for production.`)),i||=generateKey(),i}function s(e){return e.replaceAll(`+`,`-`).replaceAll(`/`,`_`).replace(/=+$/,``)}function c(e){let t=e.replaceAll(`-`,`+`).replaceAll(`_`,`/`),n=t.length%4;return n===2?t+=`==`:n===3&&(t+=`=`),t}export function encrypt(t,i){let a=Buffer.from(i??getKey(),`base64`);if(a.length!==32)throw Error(`Encryption key must be 32 bytes. Got ${a.length} bytes.`);let o=n(12),c=e(r,a,o),l=Buffer.concat([c.update(t,`utf8`),c.final()]),u=c.getAuthTag();return s(Buffer.concat([o,l,u]).toString(`base64`))}export function decrypt(e,n){let i=Buffer.from(n??getKey(),`base64`);if(i.length!==32)throw Error(`Encryption key must be 32 bytes. Got ${i.length} bytes.`);let a=Buffer.from(c(e),`base64`);if(a.length<28)throw Error(`Invalid encrypted data: too short`);let o=a.subarray(0,12),s=a.subarray(a.length-16),l=a.subarray(12,a.length-16),u=t(r,i,o);return u.setAuthTag(s),Buffer.concat([u.update(l),u.final()]).toString(`utf8`)}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Islands Endpoint
|
|
3
|
+
*
|
|
4
|
+
* Nitro route handler for `/_server-islands/:componentId`.
|
|
5
|
+
* Handles decrypting props, importing the component from the manifest,
|
|
6
|
+
* rendering it with Preact SSR, and returning HTML with Cache-Control headers.
|
|
7
|
+
*
|
|
8
|
+
* Supports both GET (props in query param `p`) and POST (props in body) requests.
|
|
9
|
+
*
|
|
10
|
+
* @module server-islands/endpoint
|
|
11
|
+
*/
|
|
12
|
+
import type { H3Event } from "h3";
|
|
13
|
+
/**
|
|
14
|
+
* Options for configuring the server island endpoint handler.
|
|
15
|
+
*/
|
|
16
|
+
export interface ServerIslandEndpointOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Whether running in development mode.
|
|
19
|
+
* In dev mode, error messages are included in 500 responses.
|
|
20
|
+
*/
|
|
21
|
+
isDev?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Default Cache-Control header value.
|
|
24
|
+
* @default "private, no-store"
|
|
25
|
+
*/
|
|
26
|
+
defaultCacheControl?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a Nitro-compatible event handler for the server islands endpoint.
|
|
30
|
+
*
|
|
31
|
+
* The handler:
|
|
32
|
+
* 1. Extracts the componentId from the URL path
|
|
33
|
+
* 2. Extracts encrypted props from query param (GET) or body (POST)
|
|
34
|
+
* 3. Decrypts the props using AES-256-GCM
|
|
35
|
+
* 4. Looks up the component module path from the manifest
|
|
36
|
+
* 5. Dynamically imports the component
|
|
37
|
+
* 6. Renders it with Preact's renderToString
|
|
38
|
+
* 7. Returns HTML with appropriate headers
|
|
39
|
+
*
|
|
40
|
+
* Error responses:
|
|
41
|
+
* - 400 Bad Request: decryption failure or missing props
|
|
42
|
+
* - 404 Not Found: component not in manifest
|
|
43
|
+
* - 500 Internal Server Error: render failure
|
|
44
|
+
*
|
|
45
|
+
* @param options - Endpoint configuration options
|
|
46
|
+
* @returns An async handler function compatible with H3/Nitro
|
|
47
|
+
*/
|
|
48
|
+
export declare function defineServerIslandHandler(options?: ServerIslandEndpointOptions): (event: H3Event) => Promise<Response>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{serverIslandLoaders as e}from"virtual:server-island-manifest";import{h as t}from"preact";import n from"preact-render-to-string";import{loadIntegration as r}from"../islands/integration-loader.js";import{decrypt as i}from"./encryption.js";import{lookupComponent as a}from"./manifest.js";const o=e??{};function s(){return o}export function defineServerIslandHandler(e={}){let{isDev:o=process.env.NODE_ENV!==`production`,defaultCacheControl:c=`private, no-store`}=e;return async e=>{let u=l(e);if(!u)return new Response(`Missing component ID`,{status:400,headers:{"Content-Type":`text/plain`}});let d;try{d=await f(e)}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=i(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=a(u)??h;if(!g)return new Response(`Component not found`,{status:404,headers:{"Content-Type":`text/plain`}});let _;try{let e=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 e=_.default;v=n(t(e,p))}else{let e=await(await r(x)).render({component:_.default,props:p,src:g,condition:m?.condition??`on:client`,ssrOnly:!1,viteServer:void 0,isDev:o});v=e.html;let t=e.hydrationData;t?.renderId&&(b=t.renderId),e.css&&(v=`${v}<style>${e.css}</style>`)}}catch(e){if(m&&o)v=``,y=!0;else{let t=o&&e instanceof Error?`${e.message}\n${e.stack}`:``;return console.error(`[server-islands] Render error for`,g,e),new Response(t||`Internal Server Error`,{status:500,headers:{"Content-Type":`text/plain`}})}}else v=``,y=!0;if(m){let e=m.elementId??`si-${u}`,t=m.componentSrc??g,n;try{n=JSON.stringify(p)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Failed to serialize props for server island (componentId=${u}, module=${g}, elementId=${e}): ${n}`)}let r=y?`on:client`:m.condition??`on:client`;if(o){let i=new URL(`../client/server-island-hydrate.ts`,import.meta.url).pathname,a=m.framework??`preact`,o=b?JSON.stringify(b):`undefined`,s=`<script type="module">
|
|
2
|
+
import{hydrateServerIsland}from"/@fs${i}";
|
|
3
|
+
hydrateServerIsland(${JSON.stringify(e)},${JSON.stringify(t)},${n},${JSON.stringify(r)},${JSON.stringify(a)},${o});
|
|
4
|
+
<\/script>`;v+=s}else{let{generatePerIslandScript:r}=await import(`../islands/per-island-script.ts`),i=r({islandId:e,componentSrc:t,framework:m.framework,condition:m.condition,conditionArg:m.conditionArg,propsJson:n});v+=i}}return new Response(v,{status:200,headers:{"Content-Type":`text/html`,"Cache-Control":c}})}}function l(e){let t=e.context?.params;if(t?.componentId)return t.componentId;let n=u(e);return/\/_server-islands\/([^/?]+)/.exec(n)?.[1]}function u(e){if(e.url)return e.url.pathname;let t=e.req?.url;return t?new URL(t,`http://localhost`).pathname:`/`}function d(e){return e.req?.method??`GET`}async function f(e){let t=d(e).toUpperCase();if(t===`GET`)return(e.url?e.url.searchParams:new URLSearchParams(``)).get(`p`)||void 0;if(t===`POST`){let t=e.web?.request;if(t)return await t.text();if(e.node?.req)return await p(e.node.req);if(e._body!==void 0)return String(e._body)}}function p(e){return new Promise((t,n)=>{let r=[];e.on(`data`,e=>r.push(e)),e.on(`end`,()=>t(Buffer.concat(r).toString(`utf8`))),e.on(`error`,n)})}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a stable 12-character component ID from a module's relative path.
|
|
3
|
+
* Uses base64url(sha256(relativePath)).slice(0, 12) for brevity and URL safety.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateComponentId(relativePath: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Registers a component in the manifest at build time.
|
|
8
|
+
*
|
|
9
|
+
* @param componentId - The hashed component identifier
|
|
10
|
+
* @param modulePath - The relative module path to the component
|
|
11
|
+
*/
|
|
12
|
+
export declare function addToManifest(componentId: string, modulePath: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Looks up a component's module path by its ID at runtime.
|
|
15
|
+
*
|
|
16
|
+
* @param componentId - The hashed component identifier
|
|
17
|
+
* @returns The module path if found, or undefined
|
|
18
|
+
*/
|
|
19
|
+
export declare function lookupComponent(componentId: string): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the full manifest as a plain object for serialization into the server bundle.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getManifest(): Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Clears the manifest. Useful for testing or rebuilds.
|
|
26
|
+
*/
|
|
27
|
+
export declare function clearManifest(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as e}from"node:crypto";export function generateComponentId(t){return e(`sha256`).update(t).digest(`base64url`).slice(0,12)}const n=new Map;export function addToManifest(e,t){n.set(e,t)}export function lookupComponent(e){return n.get(e)}export function getManifest(){return Object.fromEntries(n)}export function clearManifest(){n.clear()}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IslandDirective } from "../types/island-prop.d.ts";
|
|
2
|
+
import type { ServerIslandProp } from "./types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Renders a server island placeholder with fallback content and an inline
|
|
5
|
+
* fetch script that will request the real component HTML after page load.
|
|
6
|
+
*
|
|
7
|
+
* @param componentId - The hashed component ID (from manifest)
|
|
8
|
+
* @param props - The component's props (excluding `server` and `island`)
|
|
9
|
+
* @param serverProp - The server island configuration
|
|
10
|
+
* @param islandProp - Optional island directive for combined server+client islands
|
|
11
|
+
* @param componentSrc - Optional component source path (used for combined island hydration)
|
|
12
|
+
* @returns Complete HTML string with wrapper element and fetch script
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderServerIsland(componentId: string, props: Record<string, unknown>, serverProp: ServerIslandProp, islandProp?: IslandDirective, componentSrc?: string,
|
|
15
|
+
/** The original source path of the component (used for dev-mode loading) */
|
|
16
|
+
srcPath?: string,
|
|
17
|
+
/** Framework identifier (preact, solid, vue, etc.) */
|
|
18
|
+
framework?: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import e from"preact-render-to-string";import{encrypt as t}from"./encryption.js";const n=process.env.NODE_ENV!==`production`;function r(e){return n?`dev.${Buffer.from(e,`utf8`).toString(`base64url`)}`:t(e)}let i=0;function a(e,t){try{return JSON.stringify(e)}catch(e){let n=e instanceof Error?e.message:String(e);throw Error(`Failed to serialize server island props (elementId=${t.elementId}, componentSrc=${t.componentSrc??`unknown`}, combinedIsland=${t.hasIsland}). Props must be JSON-serializable (no functions, circular refs, or BigInt): ${n}`)}}export function renderServerIsland(t,n,o,l,u,d,f){let p=`si-${t}-${i++}`,m={...n};d&&(m.__src=d),l&&(m.__island={condition:l.condition??`on:client`,conditionArg:l.conditionArg,framework:f??`preact`,componentSrc:u,elementId:p});let h=r(a(m,{componentSrc:u,elementId:p,hasIsland:!!l})),g=o.fallback?e(o.fallback):``,_=o.timeout??1e4,v=s({elementId:p,endpointPath:`/_server-islands/${t}`,timeout:_,islandProp:l});return`${`<avalon-server-island id="${p}" data-p="${c(h)}">`}${g}</avalon-server-island>\n${v}`}function s(e){let{elementId:t,endpointPath:n,timeout:r,islandProp:i}=e,a=i?`el.querySelectorAll('script[type="module"]').forEach(s=>{const n=document.createElement("script");n.type="module";n.textContent=s.textContent;s.replaceWith(n);});`:``;return`<script type="module">
|
|
2
|
+
(async()=>{
|
|
3
|
+
const el=document.getElementById("${t}");
|
|
4
|
+
if(!el)return;
|
|
5
|
+
const p=el.dataset.p;
|
|
6
|
+
const url="${n}?p="+encodeURIComponent(p);
|
|
7
|
+
const ctrl=new AbortController();
|
|
8
|
+
const t=setTimeout(()=>ctrl.abort(),${r});
|
|
9
|
+
try{
|
|
10
|
+
const r=await fetch(...(url.length>2048?["${n}",{method:"POST",body:p,headers:{"content-type":"text/plain"},signal:ctrl.signal}]:[url,{signal:ctrl.signal}]));
|
|
11
|
+
clearTimeout(t);
|
|
12
|
+
if(!r.ok)return;
|
|
13
|
+
const html=await r.text();
|
|
14
|
+
el.innerHTML=html;${a?`\n${a}`:``}
|
|
15
|
+
}catch(e){clearTimeout(t);}
|
|
16
|
+
})()
|
|
17
|
+
<\/script>`}function c(e){return e.replaceAll(`&`,`&`).replaceAll(`"`,`"`).replaceAll(`<`,`<`).replaceAll(`>`,`>`)}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Islands Nitro Route Handler
|
|
3
|
+
*
|
|
4
|
+
* This file is registered as a Nitro handler by the Avalon build integration.
|
|
5
|
+
* It handles requests to `/_server-islands/:componentId` by delegating to
|
|
6
|
+
* the `defineServerIslandHandler` factory.
|
|
7
|
+
*
|
|
8
|
+
* Supports both GET and POST methods:
|
|
9
|
+
* - GET: encrypted props in the `p` query parameter
|
|
10
|
+
* - POST: encrypted props in the request body
|
|
11
|
+
*
|
|
12
|
+
* @module server-islands/route
|
|
13
|
+
*/
|
|
14
|
+
declare const handler: (event: import("h3").H3Event) => Promise<Response>;
|
|
15
|
+
export default handler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineServerIslandHandler as e}from"./endpoint.js";const t=e();export default t;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { JSX } from "preact";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for a server island component.
|
|
4
|
+
*
|
|
5
|
+
* When a component receives the `server` prop, it is excluded from the initial
|
|
6
|
+
* page render and instead fetched on-demand from a dedicated server endpoint
|
|
7
|
+
* after the page loads. This enables prerendered/cached pages to contain
|
|
8
|
+
* personalized or dynamic content without sacrificing cacheability.
|
|
9
|
+
*/
|
|
10
|
+
export interface ServerIslandProp {
|
|
11
|
+
/** JSX to render as placeholder until the server response arrives */
|
|
12
|
+
fallback?: JSX.Element;
|
|
13
|
+
/** Cache-Control header for the island endpoint response */
|
|
14
|
+
cache?: string;
|
|
15
|
+
/** Fetch timeout in milliseconds (default: 10000) */
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Automatically included via tsconfig.json `compilerOptions.types`.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { IslandDirective } from "./island-prop.d.ts";
|
|
7
|
+
import type { IslandDirective, ServerIslandProp } from "./island-prop.d.ts";
|
|
8
8
|
|
|
9
9
|
/** Force TypeScript to expand the type inline on hover instead of showing the alias name */
|
|
10
10
|
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
|
@@ -13,6 +13,7 @@ declare module "preact" {
|
|
|
13
13
|
namespace JSX {
|
|
14
14
|
interface IntrinsicAttributes {
|
|
15
15
|
island?: Expand<IslandDirective>;
|
|
16
|
+
server?: Expand<ServerIslandProp>;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
}
|
|
@@ -21,6 +22,7 @@ declare global {
|
|
|
21
22
|
namespace JSX {
|
|
22
23
|
interface IntrinsicAttributes {
|
|
23
24
|
island?: Expand<IslandDirective>;
|
|
25
|
+
server?: Expand<ServerIslandProp>;
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
}
|
|
@@ -28,5 +30,6 @@ declare global {
|
|
|
28
30
|
declare module "@vue/runtime-core" {
|
|
29
31
|
interface ComponentCustomProps {
|
|
30
32
|
island?: Expand<IslandDirective>;
|
|
33
|
+
server?: Expand<ServerIslandProp>;
|
|
31
34
|
}
|
|
32
35
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ServerIslandProp } from "../server-islands/types.ts";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Type augmentation for the `island` prop on island components.
|
|
3
5
|
*
|
|
@@ -11,8 +13,13 @@
|
|
|
11
13
|
*
|
|
12
14
|
* Custom directives:
|
|
13
15
|
* <Counter island={{ condition: 'on:delay' }} />
|
|
16
|
+
*
|
|
17
|
+
* Server islands:
|
|
18
|
+
* <UserAvatar server={{ fallback: <AvatarSkeleton /> }} userId={session.id} />
|
|
14
19
|
*/
|
|
15
20
|
|
|
21
|
+
export type { ServerIslandProp } from "../server-islands/types.ts";
|
|
22
|
+
|
|
16
23
|
export type IslandDirective = {
|
|
17
24
|
/**
|
|
18
25
|
* Hydration condition — controls when the island's JavaScript loads and executes.
|
|
@@ -60,3 +60,15 @@ declare module "virtual:avalon/integration-loader" {
|
|
|
60
60
|
export function preLitHydration(): Promise<void>;
|
|
61
61
|
export function loadHMRAdapter(framework: string): Promise<unknown>;
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
declare module "virtual:server-island-manifest" {
|
|
65
|
+
/** Mapping of componentId → module path for all registered server islands */
|
|
66
|
+
export const serverIslandManifest: Record<string, string>;
|
|
67
|
+
/** Lazy loaders that dynamically import each server island component, preventing tree-shaking */
|
|
68
|
+
export const serverIslandLoaders: Record<string, () => Promise<{ default: unknown }>>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
declare module "virtual:server-island-key" {
|
|
72
|
+
/** The AES-256-GCM encryption key (base64-encoded) embedded at build time */
|
|
73
|
+
export const serverIslandKey: string;
|
|
74
|
+
}
|
|
@@ -1,22 +1,27 @@
|
|
|
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,readFileSync as n,rmSync as r}from"node:fs";import{stat as i}from"node:fs/promises";import{createRequire as a}from"node:module";import{dirname as o,join as s,relative as c,resolve as l}from"node:path";import{nitro as u}from"nitro/vite";import{isRunnableDevEnvironment as d}from"vite";import{getUniversalCSSForHead as f}from"../islands/universal-css-collector.js";import{getUniversalHeadForInjection as p,injectSolidHydrationScriptIfNeeded as m}from"../islands/universal-head-collector.js";import{clearMiddlewareCache as h,discoverScopedMiddleware as g,executeScopedMiddleware as _}from"../middleware/index.js";import{createNitroConfig as v}from"../nitro/config.js";import{createIslandManifestPlugin as y,createNitroBuildPlugin as b,createSourceMapConfig as x,createSourceMapPlugin as S}from"../nitro/index.js";import{collectCssFromModuleGraph as C,injectSsrCss as w}from"../render/collect-css.js";import{generateErrorPage as T,generateFallback404 as E}from"../render/error-pages.js";import{generateComponentId as D}from"../server-islands/manifest.js";import{resolveToRelativePath as O}from"./server-islands-plugin.js";function k(e){let t=P(e),n=Object.entries(t),r=`export const serverIslandManifest = ${JSON.stringify(t)};\n\n`;r+=`export const serverIslandLoaders = {
|
|
2
|
+
`;for(let[e,t]of n)r+=` ${JSON.stringify(e)}: () => import(${JSON.stringify(t)}),\n`;return r+=`};
|
|
3
|
+
`,r}const A=/^[ \t]*import\s+([A-Z]\w*)\s+from\s+(['"][^'"]+['"])/gm,j=new Set([`node_modules`,`dist`,`.output`,`.netlify`,`.vercel`,`.cloudflare`,`.wrangler`,`.firebase`,`.amplify-hosting`,`.git`]);function M(e,t){return new RegExp(String.raw`<${t}\s[\s\S]*?\bserver\s*[={/>]`).test(e)}function N(e,n){let r;try{r=t(e,{withFileTypes:!0})}catch{return}for(let t of r){let r=s(e,t.name);if(t.isDirectory()){if(j.has(t.name))continue;N(r,n)}else if(t.isFile()){if(!/\.(tsx|jsx)$/.test(t.name)||/\.(test|spec)\.[jt]sx$/.test(t.name)||r.includes(`__tests__`))continue;n.push(r)}}}function P(t){let r={},i=[];for(let n of[`app`,`src`]){let r=s(t,n);e(r)&&N(r,i)}for(let e of i){let i;try{i=n(e,`utf8`)}catch{continue}if(!/\bserver\s*[={/>]/.test(i))continue;A.lastIndex=0;let a=A.exec(i);for(;a!==null;a=A.exec(i)){let n=a[1],o=a[2].slice(1,-1);if(!M(i,n))continue;let s=O(o,e,t),c=D(s);c in r||(r[c]=s)}}return r}function F(t){let n=s(o(a(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 ee(t,n){let r=s(o(a(s(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=v(t,e),r=process.cwd(),i=F(`src/server-islands/route.ts`),a={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:i}],virtual:{"virtual:server-island-manifest":()=>k(r)}};t.renderer===!1?a.renderer=!1:n.renderer&&(a.renderer=n.renderer),n.publicRuntimeConfig&&(a.publicRuntimeConfig=n.publicRuntimeConfig),n.publicAssets&&(a.publicAssets=n.publicAssets),n.compressPublicAssets&&(a.compressPublicAssets=n.compressPublicAssets),n.serverEntry&&(a.serverEntry=n.serverEntry);let o=n.traceDeps??[];a.traceDeps=[...new Set([`undici`,...o])],n.prerender&&(a.prerender={routes:[],crawlLinks:!1});let s=u(a),c=createNitroCoordinationPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),l=createVirtualModulesPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),d=b(e,t),f=y(e,{verbose:e.verbose,generatePreloadHints:!0}),p=S(x(t.preset??`node_server`,e.isDev));return{nitroOptions:n,plugins:[...Array.isArray(s)?s:[s],c,l,d,f,p]}}export function createNitroCoordinationPlugin(e){let{avalonConfig:t,verbose:n}=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?s(i,e.config.build.outDir):s(i,`dist`);try{r(a,{recursive:!0,force:!0})}catch{}try{r(s(i,`.output`),{recursive:!0,force:!0})}catch{}let o=!1,c=new Promise(t=>{let n=()=>{(e.environments?.nitro)?.devServer?.entry||o?(o=!0,t()):setTimeout(n,50)};setTimeout(n,100),setTimeout(()=>{o=!0,t()},15e3)});e.middlewares.use(async(e,t,n)=>{if(o)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 u(){return l||=await g({baseDir:`${e.config.root||process.cwd()}/src`,devMode:!1}),l}function f(){l=null}R(e,t,n,f),u().catch(e=>{console.warn(`[middleware] Failed to discover middleware:`,e)});let p=e.environments?.ssr,m=!!p&&d(p);m&&L(e,t.integrations,n).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(F(`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">
|
|
4
|
+
import{hydrateServerIsland}from"/@fs${F(`src/client/server-island-hydrate.ts`)}";
|
|
5
|
+
hydrateServerIsland(${JSON.stringify(t)},${JSON.stringify(n)},${r},${JSON.stringify(a)},${JSON.stringify(o)});
|
|
6
|
+
<\/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(r,i,a)=>{if(!m)return a();let o=r.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 te(e,o,r,i,u,n)||await ne(e,o,t,i))return;let a=await re(e,o,t);if(a){i.statusCode=200,i.setHeader(`Content-Type`,`text/html`),i.end(a);return}await I(e,o,i,t)}catch(e){console.error(`[SSR Error]`,e),i.statusCode=500,i.setHeader(`Content-Type`,`text/html`),i.end(T(e))}})},buildStart(){}}}async function te(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 _({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 I(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(E(t))}}async function L(e,t,n){let r=performance.now(),i=[{path:F(`src/render/ssr.ts`),assignTo:`ssr`},{path:F(`src/core/layout/enhanced-layout-resolver.ts`),assignTo:`layout`},{path:F(`src/middleware/index.ts`),assignTo:null},...t.map(e=>({path:ee(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`&&(J=r),n===`layout`&&(Y=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,o=s,u=c,d=l,f=process.cwd(),p=[];function m(e){try{let n=t(e,{withFileTypes:!0});for(let t of n){let n=o(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 z(n,i):e===RESOLVED_VIRTUAL_IDS.PAGE_LOADER?await B(n,i):e===RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST?V():e===RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG?H(n,r):e===RESOLVED_VIRTUAL_IDS.CONFIG?generateConfigModule(n,r):e===RESOLVED_VIRTUAL_IDS.LAYOUTS?(a||=await U(n,r,p),a):e===RESOLVED_VIRTUAL_IDS.ASSETS?W(r):e===RESOLVED_VIRTUAL_IDS.RENDERER?G(n):e===RESOLVED_VIRTUAL_IDS.CLIENT_ENTRY?await K(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 R(e,t,n,r){e.watcher.on(`change`,e=>{e.includes(`_middleware`)&&(h(),r?.()),(e.includes(`/render/`)||e.includes(`/layout/`)||e.includes(`/islands/`))&&(J=null,Y=null),(e.includes(`/layouts/`)||e.includes(`_layout`))&&globalThis.__avalonLayoutResolver?.clearCache?.()}),e.watcher.on(`add`,e=>{e.includes(`_middleware`)&&(h(),r?.())}),e.watcher.on(`unlink`,e=>{e.includes(`_middleware`)&&(h(),r?.())})}async function z(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
7
|
export default pageRoutes;
|
|
3
|
-
`}}async function
|
|
8
|
+
`}}async function B(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
9
|
`),`];`,``,`/**`,` * 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(`
|
|
5
10
|
`)}catch(e){return console.error(`[page-loader] Failed to generate page loader:`,e),`export function loadPage() { return null; }
|
|
6
11
|
export default { loadPage, routes: [] };
|
|
7
|
-
`}}function
|
|
12
|
+
`}}function V(){return`export const islandManifest = { islands: {}, clientEntry: "", css: [] };
|
|
8
13
|
export default islandManifest;
|
|
9
|
-
`}function
|
|
14
|
+
`}function H(e,t){let n={avalon:{streaming:t.streaming??!0,pagesDir:e.pagesDir,layoutsDir:e.layoutsDir,isDev:e.isDev},...t.runtimeConfig};return`export const runtimeConfig = ${JSON.stringify(n,null,2)};\nexport function useRuntimeConfig() { return runtimeConfig; }\nexport default runtimeConfig;\n`}export function generateConfigModule(e,t){let n=globalThis.__avalonConfig?.isDev??e.isDev,r={streaming:t.streaming??!0,pagesDir:e.pagesDir,layoutsDir:e.layoutsDir,isDev:n,...t.runtimeConfig};return`globalThis.__avalonHydrationMode = "${n?`entry-client`:`per-island`}";\nconst config = ${JSON.stringify(r,null,2)};\nexport function useAvalonConfig() { return config; }\nexport default config;\n`}async function U(e,t,n){let{getAllLayoutDirs:r}=await import(`./module-discovery.js`),{relative:i,resolve:a}=await import(`node:path`),{stat:o,readFile:c}=await import(`node:fs/promises`),l=process.cwd(),u=await r(e.layoutsDir,e.modules,l),d=[],f=0,p=a(l,e.layoutsDir);for(let{dir:t,prefix:n}of u){let r=s(t,`_layout.tsx`);try{if(!(await o(r)).isFile())continue}catch{continue}let a=i(l,r).replaceAll(`\\`,`/`),c=a.startsWith(`/`)?a:`/${a}`,u=t.startsWith(p),m=u&&!e.modules?`RootLayout`:`Layout_${f}`;d.push({prefix:n,importPath:c,varName:m,isShared:u,filePath:r}),f++}let m=d.filter(e=>e.isShared),h=d.filter(e=>!e.isShared),g=new Map;for(let e of h)try{let t=(await c(e.filePath,`utf8`)).match(/layoutConfig\s*=\s*{[\s\S]*?skipLayouts\s*:\s*\[([^\]]*)\]/)?.[1]??``,n=/['"`]_layout['"`]/.test(t);g.set(e.importPath,n)}catch{g.set(e.importPath,!1)}let _=d.map(e=>`import ${e.varName} from '${e.importPath}';`),v=h.toSorted((e,t)=>t.prefix.length-e.prefix.length).map(e=>{let t=e.prefix===`/`,n=g.get(e.importPath)??!1,r=t||n;return` { prefix: ${JSON.stringify(e.prefix)}, Layout: ${e.varName}, skipRoot: ${r} }`}),y=m.length>0?m[0].varName:`null`,b=JSON.stringify(n);return[`// Auto-generated by Avalon — do not edit`,`import { h } from 'preact';`,`import preactRenderToString from 'preact-render-to-string';`,`import { getUniversalCSSForHead } from '@useavalon/avalon/islands/universal-css-collector';`,`import { getUniversalHeadForInjection, injectSolidHydrationScriptIfNeeded } from '@useavalon/avalon/islands/universal-head-collector';`,..._,``,`const RootLayoutComponent = ${y};`,`const _cssLinks = ${b};`,``,`const moduleLayouts = [`,v.join(`,
|
|
10
15
|
`),`];`,``,`function getLayoutsForPath(pathname) {`,` for (const entry of moduleLayouts) {`,` if (entry.prefix === '/' ? pathname === '/' : pathname.startsWith(entry.prefix)) {`,` return entry;`,` }`,` }`,` return null;`,`}`,``,`function injectUniversalAssets(html) {`,` // In dev, inject <link> tags for all discovered CSS files.`,` // The ?direct suffix makes Vite return raw CSS (text/css) instead`,` // of a JS module wrapper, so <link rel="stylesheet"> works.`,` if (process.env.NODE_ENV !== 'production' && _cssLinks.length > 0) {`,` var links = _cssLinks.map(function(href) {`,` return '<link rel="stylesheet" href="' + href + '?direct">';`,` }).join('\\n');`,` if (html.includes('</head>')) {`,` html = html.replace('</head>', links + '\\n</head>');`,` }`,` }`,` const universalCSS = getUniversalCSSForHead(true);`,` if (universalCSS && html.includes('</head>')) {`,` html = html.replace('</head>', universalCSS + '\\n</head>');`,` }`,` const universalHead = getUniversalHeadForInjection(true);`,` if (universalHead && html.includes('</head>')) {`,` html = html.replace('</head>', universalHead + '\\n</head>');`,` }`,` html = injectSolidHydrationScriptIfNeeded(html);`,` return html;`,`}`,``,`export async function wrapWithLayouts(pageHtml, pageModule, context, injectAssets) {`,` const pathname = context.url.pathname;`,` const frontmatter = {`,` ...(pageModule.frontmatter || {}),`,` ...(pageModule.metadata || {}),`,` currentPath: pathname,`,` };`,` const pageLayoutConfig = pageModule.layoutConfig;`,` const skipAll = pageLayoutConfig?.skipLayouts?.includes('_layout');`,``,` const layoutEntry = getLayoutsForPath(pathname);`,` const routeInfo = { path: pathname, params: context.params, query: context.url.searchParams };`,` let html;`,``,` if (!layoutEntry || skipAll) {`,` if (RootLayoutComponent && !skipAll) {`,` const rootProps = {`,` children: h('avalon-page', { id: 'app', dangerouslySetInnerHTML: { __html: pageHtml } }),`,` frontmatter,`,` data: {},`,` route: routeInfo,`,` };`,` const rootResult = RootLayoutComponent(rootProps);`,` const resolvedRoot = rootResult instanceof Promise ? await rootResult : rootResult;`,` html = '<!DOCTYPE html>\\n' + preactRenderToString(resolvedRoot);`,` } else {`,` // skipAll is true — page provides its own HTML shell or needs a minimal one`,` if (pageHtml.trimStart().startsWith('<html')) {`,` html = '<!DOCTYPE html>\\n' + pageHtml;`,` } else {`,` const title = String(frontmatter.title || 'Avalon');`,` html = [`,` '<!DOCTYPE html>',`,` '<html lang="en">',`,` '<head>',`,` '<meta charset="utf-8">',`,` '<meta name="viewport" content="width=device-width, initial-scale=1">',`,` '<title>' + title + '</title>',`,` '</head>',`,` '<body>',`,` '<div id="app">' + pageHtml + '</div>',`,` '</body>',`,` '</html>',`,` ].join('\\n');`,` }`,` }`,` } else {`,` const layoutProps = {`,` children: h('avalon-page', { dangerouslySetInnerHTML: { __html: pageHtml } }),`,` frontmatter,`,` data: {},`,` route: routeInfo,`,` };`,` const layoutResult = layoutEntry.Layout(layoutProps);`,` const resolvedLayout = layoutResult instanceof Promise ? await layoutResult : layoutResult;`,` let wrappedHtml = preactRenderToString(resolvedLayout);`,``,` // If the layout provides its own HTML shell (starts with <html),`,` // skip root wrapping regardless of skipRoot flag — the layout IS the document.`,` const layoutProvidesShell = wrappedHtml.trimStart().startsWith('<html');`,``,` if (!layoutProvidesShell && !layoutEntry.skipRoot && RootLayoutComponent) {`,` const rootProps = {`,` children: h('avalon-page', { dangerouslySetInnerHTML: { __html: wrappedHtml } }),`,` frontmatter,`,` data: {},`,` route: routeInfo,`,` };`,` const rootResult = RootLayoutComponent(rootProps);`,` const resolvedRoot = rootResult instanceof Promise ? await rootResult : rootResult;`,` wrappedHtml = preactRenderToString(resolvedRoot);`,` }`,``,` html = '<!DOCTYPE html>\\n' + wrappedHtml;`,` }`,``,` if (injectAssets) {`,` html = injectAssets(html);`,` }`,` return injectUniversalAssets(html);`,`}`,``,`export default { wrapWithLayouts };`,``].join(`
|
|
11
|
-
`)}function
|
|
12
|
-
`)}function
|
|
13
|
-
`)}async function
|
|
16
|
+
`)}function W(e){let t=e.clientEntry??`app/entry-client`;return[`// Auto-generated by Avalon — do not edit`,`// @ts-ignore — virtual import resolved by Nitro's Vite assets plugin at build time`,`import clientAssets from '${t.startsWith(`/`)?t:`/${t}`}?assets=client';`,``,`function buildAssetTags() {`,` const cssLinks = (clientAssets?.css ?? [])`,` .filter(attr => {`,` const href = attr.href || '';`,` // The SSR build produces ssr-index-*.css containing all global, reset,`,` // token, and layout CSS module styles. The client build may also emit`,` // entry-client-*.css (a subset) and index-*.css. To avoid duplicates,`,` // prefer ssr-index when it exists; otherwise fall back to the others.`,` var hasSsrIndex = (clientAssets?.css ?? []).some(function(a) { return (a.href || '').includes('ssr-index'); });`,` if (hasSsrIndex) return href.includes('ssr-index');`,` if (href.includes('entry-client') && href.endsWith('.css')) return true;`,` if (/\\/index-[^/]+\\.css$/.test(href)) return true;`,` return false;`,` })`,` .map(attr => '<link rel="stylesheet" href="' + attr.href + '">')`,` .join('\\n');`,` const jsPreloads = (clientAssets?.js ?? [])`,` .map(attr => '<link rel="modulepreload" href="' + attr.href + '">')`,` .join('\\n');`,` const entryScript = clientAssets?.entry`,` ? '<script type="module" src="' + clientAssets.entry + '"><\/script>'`,` : '';`,` return { cssLinks, jsPreloads, entryScript };`,`}`,``,`export function injectAssets(html) {`,` const { cssLinks, jsPreloads, entryScript } = buildAssetTags();`,` if (html.includes('</head>')) {`,` html = html.replace('</head>', cssLinks + '\\n' + jsPreloads + '\\n</head>');`,` }`,` if (html.includes('</body>')) {`,` html = html.replace('</body>', entryScript + '\\n</body>');`,` }`,` return html;`,`}`,``,`export { clientAssets };`,`export default { injectAssets, clientAssets };`,``].join(`
|
|
17
|
+
`)}function G(e){let t=Array.isArray(e.integrations)?e.integrations:[],n=[],r=[];for(let e of t){let t=`${e}Integration`;n.push(`import { ${t} } from '@useavalon/${e}';`),r.push(`registry.register(${t});`)}return[`// Auto-generated by Avalon — do not edit`,`import { createNitroRenderer } from '@useavalon/avalon/nitro/renderer';`,`import { registerBuiltinDirectives } from '@useavalon/avalon';`,`import { registry } from '@useavalon/avalon/islands/integration-registry';`,`import avalonConfig from 'virtual:avalon/config';`,`import { loadPage } from 'virtual:avalon/page-loader';`,`import { wrapWithLayouts } from 'virtual:avalon/layouts';`,`import { injectAssets } from 'virtual:avalon/assets';`,...n,``,`// Pre-register framework integrations for SSR`,...r,``,`// Register built-in custom hydration directives (on:delay, on:scroll, etc.)`,`registerBuiltinDirectives();`,``,`// Set hydration mode flag — intentionally duplicated from virtual:avalon/config`,`// for module-load-order resilience (config import may be tree-shaken or deferred).`,`globalThis.__avalonHydrationMode = avalonConfig.isDev ? "entry-client" : "per-island";`,``,`export default createNitroRenderer({`,` avalonConfig,`,` isDev: avalonConfig.isDev,`,` resolvePageRoute: async (pathname) => {`,` const mod = loadPage(pathname);`,` if (!mod || !('default' in mod)) return null;`,` return { filePath: '[virtual:' + pathname + ']', pattern: pathname, params: {} };`,` },`,` loadPageModule: async (filePath) => {`,` const match = filePath.match(/^\\[virtual:(.+)\\]$/);`,` const pathname = match ? match[1] : filePath;`,` const mod = loadPage(pathname);`,` if (mod) return mod;`,` return { default: () => null, metadata: { title: 'Avalon' } };`,` },`,` wrapWithLayouts: (pageHtml, pageModule, context) =>`,` wrapWithLayouts(pageHtml, pageModule, context, injectAssets),`,`});`,``].join(`
|
|
18
|
+
`)}async function K(e,t){let{getAllLayoutDirs:n}=await import(`./module-discovery.js`),{readdir:r}=await import(`node:fs/promises`),{relative:i,join:a}=await import(`node:path`),o=process.cwd(),s=await n(e.layoutsDir,e.modules,o),c=[];for(let{dir:e}of s)try{let t=await r(e,{withFileTypes:!0});for(let n of t){if(!n.isFile()||!n.name.endsWith(`.css`))continue;let t=i(o,a(e,n.name)).replaceAll(`\\`,`/`),r=t.startsWith(`/`)?t:`/${t}`;c.push(r)}}catch{}let l=globalThis.__avalonConfig?.isDev??e.isDev?`@useavalon/avalon/client/main`:`@useavalon/avalon/client/main-slim`,u=[`// Auto-generated by Avalon — do not edit`,`// Island hydration runtime`];(globalThis.__avalonHydrationMode??`entry-client`)===`entry-client`?u.push(`import '${l}';`):u.push(`// Per-island hydration mode — no shared runtime needed`),u.push(``);let d=t.globalCSS??[];for(let e of d){let t=e.startsWith(`/`)?e:`/${e}`;u.push(`// Global CSS`),u.push(`import '${t}';`)}if(d.length>0&&u.push(``),c.length>0){u.push(`// Layout CSS (auto-discovered)`);let e=0;for(let t of c)t.includes(`.module.`)?(u.push(`import _lcss${e} from '${t}';`),e++):u.push(`import '${t}';`)}return u.push(``),u.join(`
|
|
14
19
|
`)}export function generateIntegrationLoaderModule(e){let t=(e.integrations??[]).map(e=>typeof e==`string`?e:e.name),n={preact:`@useavalon/preact/client`,react:`@useavalon/preact/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`,solid:`@useavalon/solid/client`,lit:`@useavalon/lit/client`,qwik:`@useavalon/qwik/client`},r={preact:`@useavalon/preact/client/hmr`,react:`@useavalon/react/client/hmr`,vue:`@useavalon/vue/client/hmr`,svelte:`@useavalon/svelte/client/hmr`,solid:`@useavalon/solid/client/hmr`,lit:`@useavalon/lit/client/hmr`,qwik:`@useavalon/qwik/client/hmr`},i=t.includes(`lit`),a=t.includes(`solid`),o=(globalThis.__avalonConfig?.isDev??e.isDev)===!0,s=[`// Auto-generated by Avalon — only includes configured integrations`,``];a&&!o&&(s.push(`// --- Inlined Solid adapter (production) ---`),s.push(`// Eliminates a separate chunk + network request for the Solid client adapter.`),s.push(`// Only imports hydrate/createComponent — no render() fallback (saves ~1-2 KiB).`),s.push(`function _ensureHydrationContext() {`),s.push(` if (!globalThis._$HY) {`),s.push(` globalThis._$HY = { events: [], completed: new WeakSet(), r: {}, fe() {} };`),s.push(` }`),s.push(`}`),s.push(``),s.push(`async function _solidHydrate(container, Component, props) {`),s.push(` if (!container) throw new Error("Container element is required for hydration");`),s.push(` if (!Component || typeof Component !== "function") {`),s.push(` throw new Error("Invalid Solid component: expected function, got " + typeof Component);`),s.push(` }`),s.push(` var el = container;`),s.push(` var renderId = el.dataset.solidRenderId || el.dataset.renderId;`),s.push(` var { hydrate: solidHydrate, createComponent } = await import("solid-js/web");`),s.push(` _ensureHydrationContext();`),s.push(` solidHydrate(function() { return createComponent(Component, props || {}); }, el, { renderId: renderId || "" });`),s.push(`}`),s.push(``),s.push(`var _solidModule = { hydrate: _solidHydrate };`),s.push(``)),s.push(`// --- loadIntegrationModule ---`),s.push(`export async function loadIntegrationModule(framework) {`),s.push(` switch (framework) {`);for(let e of t){let t=n[e];t&&(e===`solid`&&!o?(s.push(` case "solid":`),s.push(` return _solidModule;`)):e===`react`?s.push(` case "react":`):e===`preact`?(s.push(` case "preact":`),s.push(` return import("${t}");`)):(s.push(` case "${e}":`),s.push(` return import("${t}");`)))}t.includes(`react`)&&!t.includes(`preact`)&&s.push(` return import("${n.react}");`),s.push(` default:`),s.push(" throw new Error(`Unknown or unconfigured framework: ${framework}`);"),s.push(` }`),s.push(`}`),s.push(``),s.push(`// --- Lit hydration pre-load (only if Lit is configured) ---`),i?(s.push(`export async function preLitHydration() {`),s.push(` await import("@useavalon/lit/client");`),s.push(`}`)):s.push(`export async function preLitHydration() {}`),s.push(``),s.push(`// --- HMR adapter loader ---`),s.push(`export async function loadHMRAdapter(framework) {`),s.push(` switch (framework) {`);for(let e of t){let t=r[e];t&&(s.push(` case "${e}":`),s.push(` return import("${t}").then(m => m.${e}Adapter);`))}return s.push(` default: return null;`),s.push(` }`),s.push(`}`),s.join(`
|
|
15
|
-
`)}export function getViteDevServer(){return globalThis.__viteDevServer}export function getAvalonConfig(){return globalThis.__avalonConfig}export function isDevelopmentMode(){return globalThis.__avalonConfig?.isDev??!0}const
|
|
16
|
-
`)}</style>`;k=D.includes(`</head>`)?D.replace(`</head>`,`${e}\n</head>`):D+e}k.trim().toLowerCase().startsWith(`<!doctype`)||(k=`<!DOCTYPE html>\n${k}`);let A=
|
|
20
|
+
`)}export function getViteDevServer(){return globalThis.__viteDevServer}export function getAvalonConfig(){return globalThis.__avalonConfig}export function isDevelopmentMode(){return globalThis.__avalonConfig?.isDev??!0}const q=`<!--AVALON_STREAM_BOUNDARY-->`;let J=null,Y=null;async function ne(e,t,n,r){if(!n.modules)return!1;let i=t.split(`?`)[0],a=await Q(i,n,e);if(!a)return!1;try{let t=await e.ssrLoadModule(a),n=t.default;if(!n)return!1;let o=t.layoutConfig,s=await C(e,a),c=await Z(i,e),l=[];for(let t of c){let n=await e.ssrLoadModule(t);l.push({file:t,module:n})}for(let t of c){let n=await C(e,t);s.push(...n)}if(l.length===0)return!1;let{render:u}=await e.ssrLoadModule(`preact-render-to-string`),{h:d}=await e.ssrLoadModule(`preact`),h=o?.skipLayouts||[],g=l.filter(({file:e})=>{let t=e.split(`/`).pop()?.replace(/\.[^.]+$/,``)||``;return!h.includes(t)}),_=t.frontmatter,v=t.metadata,y={children:null,frontmatter:{..._,...v,currentPath:i},params:{},url:i},b=[],x=[];for(let e of g){let t=e.module.default;if(!(!t||typeof t!=`function`))try{let n=t({...y,children:d(`div`,null,`test`)}),r=u(n instanceof Promise?await n:n);r.trim().startsWith(`<html`)||r.includes(`<!DOCTYPE`)?b.push(e):x.push(e)}catch{x.push(e)}}if(b.length===0)return!1;let{module:S}=b[b.length-1],w=S.default;if(!w||typeof w!=`function`)return!1;let T;try{let e=w({...y,children:d(`div`,{dangerouslySetInnerHTML:{__html:q}})});T=u(e instanceof Promise?await e:e)}catch{return!1}let E=T.indexOf(q);if(E===-1)return!1;let D=T.slice(0,E),O=T.slice(E+29),k=D;if(s.length>0){let e=`<style data-avalon-ssr-css>${s.join(`
|
|
21
|
+
`)}</style>`;k=D.includes(`</head>`)?D.replace(`</head>`,`${e}\n</head>`):D+e}k.trim().toLowerCase().startsWith(`<!doctype`)||(k=`<!DOCTYPE html>\n${k}`);let A=f(!0);A&&k.includes(`</head>`)&&(k=k.replace(`</head>`,`${A}\n</head>`));let j=p(!0);j&&k.includes(`</head>`)&&(k=k.replace(`</head>`,`${j}\n</head>`)),k=m(k),r.statusCode=200,r.setHeader(`Content-Type`,`text/html; charset=utf-8`),r.setHeader(`Transfer-Encoding`,`chunked`),r.setHeader(`X-Avalon-Streaming`,`1`),r.flushHeaders(),r.write(k);let M;try{let e=typeof n==`function`?n():n;M=u(e instanceof Promise?await e:e)}catch(e){console.error(`[SSR Streaming] Error rendering page component:`,e),M=`<div>Error rendering page</div>`}if(M.trim().startsWith(`<!DOCTYPE html>`)||M.trim().startsWith(`<html`))return r.end(M),!0;let N=M;for(let{module:e}of x){let t=e.default;if(!(!t||typeof t!=`function`))try{let e=t({...y,children:d(`div`,{dangerouslySetInnerHTML:{__html:N}})});N=u(e instanceof Promise?await e:e)}catch(e){console.error(`[SSR Streaming] Error rendering wrapper layout:`,e)}}let P=N+O;if(!P.includes(`/src/client/main.js`)&&!P.includes(`/@vite/client`)){let e=P.lastIndexOf(`</body>`);e!==-1&&(P=P.slice(0,e)+`
|
|
17
22
|
<script type="module" src="/@vite/client"><\/script>
|
|
18
23
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
19
|
-
`+P.slice(e))}return r.end(P),!0}catch(e){return r.headersSent?(r.end(`<div>Streaming SSR error: ${e.message}</div></body></html>`),!0):!1}}async function
|
|
24
|
+
`+P.slice(e))}return r.end(P),!0}catch(e){return r.headersSent?(r.end(`<div>Streaming SSR error: ${e.message}</div></body></html>`),!0):!1}}async function re(e,t,n){let r=t.split(`?`)[0],i=await Q(r,n,e);if(!i)return null;try{let t=await e.ssrLoadModule(i),a=t.default;if(!a)return console.warn(`[SSR] Page ${i} has no default export`),null;let o=await C(e,i),s=await Z(r,e),c=[];for(let t of s){let n=await e.ssrLoadModule(t);c.push({file:t,module:n})}for(let t of s){let n=await C(e,t);o.push(...n)}let l;return l=n.modules&&c.length>0?await ie(a,t,c,r,n,e):await ae(a,t,r,n,e),o.length>0&&(l=w(l,o)),l}catch(e){throw console.error(`[SSR] Error rendering ${i}:`,e),e}}async function ie(e,t,n,r,i,a){let{render:o}=await a.ssrLoadModule(`preact-render-to-string`),{h:s}=await a.ssrLoadModule(`preact`),c=t.layoutConfig?.skipLayouts||[],l=n.filter(({file:e})=>{let t=e.split(`/`).pop()?.replace(/\.[^.]+$/,``)||``;return!c.includes(t)}),u;try{let t=typeof e==`function`?e():e;u=o(t instanceof Promise?await t:t)}catch(e){console.error(`[SSR] Error rendering page component:`,e),u=`<div>Error rendering page</div>`}if(u.trim().startsWith(`<!DOCTYPE html>`)||u.trim().startsWith(`<html`))return X(u);let d=t.frontmatter,f=t.metadata,p={children:null,frontmatter:{...d,...f,currentPath:r},params:{},url:r},m=[],h=[];for(let e of l){let t=e.module.default;if(!(!t||typeof t!=`function`))try{let n=t({...p,children:s(`div`,null,`test`)}),r=o(n instanceof Promise?await n:n);r.trim().startsWith(`<html`)||r.includes(`<!DOCTYPE`)?m.push(e):h.push(e)}catch{h.push(e)}}let g=u;for(let{module:e}of h){let t=e.default;if(!(!t||typeof t!=`function`))try{let e=t({...p,children:s(`div`,{dangerouslySetInnerHTML:{__html:g}})});g=o(e instanceof Promise?await e:e)}catch(e){console.error(`[SSR] Error rendering wrapper layout:`,e)}}if(m.length>0){let{module:e}=m[m.length-1],t=e.default;if(t&&typeof t==`function`)try{let e=t({...p,children:s(`div`,{dangerouslySetInnerHTML:{__html:g}})});g=o(e instanceof Promise?await e:e)}catch(e){console.error(`[SSR] Error rendering shell layout:`,e)}}if(g.trim().startsWith(`<!DOCTYPE html>`)||g.trim().startsWith(`<html`))return X(g);let _=t.metadata||{},v=_.title||`Avalon App`,y=_.description||``;return`<!DOCTYPE html>
|
|
20
25
|
<html lang="en">
|
|
21
26
|
<head>
|
|
22
27
|
<meta charset="utf-8">
|
|
@@ -29,12 +34,12 @@ export default islandManifest;
|
|
|
29
34
|
${g}
|
|
30
35
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
31
36
|
</body>
|
|
32
|
-
</html>`}function X(e){let t=e;if(t.trim().toLowerCase().startsWith(`<!doctype`)||(t=`<!DOCTYPE html>\n${t}`),!t.includes(`data-universal-ssr="true"`)){let e=
|
|
37
|
+
</html>`}function X(e){let t=e;if(t.trim().toLowerCase().startsWith(`<!doctype`)||(t=`<!DOCTYPE html>\n${t}`),!t.includes(`data-universal-ssr="true"`)){let e=f(!0);e&&t.includes(`</head>`)&&(t=t.replace(`</head>`,`${e}\n</head>`))}let n=p(!0);if(n&&t.includes(`</head>`)&&(t=t.replace(`</head>`,`${n}\n</head>`)),t=m(t),t.includes(`/src/client/main.js`)||t.includes(`/@vite/client`))return t;let r=t.lastIndexOf(`</body>`);return r===-1?t+`
|
|
33
38
|
<script type="module" src="/@vite/client"><\/script>
|
|
34
39
|
<script type="module" src="/src/client/main.js"><\/script>`:t.slice(0,r)+`
|
|
35
40
|
<script type="module" src="/@vite/client"><\/script>
|
|
36
41
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
37
|
-
`+t.slice(r)}async function Z(e,t){let n=t.config.root||process.cwd(),
|
|
42
|
+
`+t.slice(r)}async function Z(e,t){let n=t.config.root||process.cwd(),r=globalThis.__avalonConfig,a=`_layout.tsx`,o=[],s=e.split(`/`).filter(Boolean),c=[``];for(let e=0;e<s.length;e++)c.push(`/${s.slice(0,e+1).join(`/`)}`);async function l(e){try{if((await i(e)).isFile()){let t=e.slice(n.length);o.includes(t)||o.push(t)}}catch{}}if(r?.layoutsDir&&await l(`${`${n}/${r.layoutsDir}`}/${a}`),r?.modules){let e=`${n}/${r.modules.dir}`,t=r.modules.layoutsDirName,i=s[0]||``,o=[`home`,`root`,`main`,`index`];if(!i||o.includes(i.toLowerCase()))for(let n of o)await l(`${e}/${n}/${t}/${a}`);else await l(`${e}/${i}/${t}/${a}`)}let u=`${n}/src/layouts`;for(let e of c)await l(e===``?`${u}/${a}`:`${u}${e}/${a}`);return o}async function Q(e,t,n){let r=e;r.endsWith(`/`)&&r!==`/`&&(r=r.slice(0,-1)),r===`/`&&(r=`/index`);let a=[`.tsx`,`.ts`,`.jsx`,`.js`,`.mdx`,`.md`],o=n.config.root||process.cwd();async function s(e){try{if((await i(`${o}/${e}`)).isFile())return`/${e}`}catch{}return null}if(t.modules){let n=t.modules.dir,i=t.modules.pagesDirName,o=e.split(`/`).filter(Boolean),c=o[0]||``,l=[`home`,`root`,`main`,`index`],u,d;if(!c||l.includes(c.toLowerCase()))u=`home`,d=r;else{u=c;let e=o.slice(1);d=e.length>0?`/${e.join(`/`)}`:`/index`}for(let e of a){let t=await s(`${n}/${u}/${i}${d}${e}`);if(t)return t}if(!d.endsWith(`/index`))for(let e of a){let t=await s(`${n}/${u}/${i}${d}/index${e}`);if(t)return t}}let c=t.pagesDir;for(let e of a){let t=await s(`${c}${r}${e}`);if(t)return t}if(!r.endsWith(`/index`))for(let e of a){let t=await s(`${c}${r}/index${e}`);if(t)return t}return null}async function ae(e,t,n,r,i){let a=t.metadata||{};try{J||=await i.ssrLoadModule(F(`src/render/ssr.ts`));let o=J;Y||=await i.ssrLoadModule(F(`src/core/layout/enhanced-layout-resolver.ts`));let s=Y,c={component:()=>typeof e==`function`?e():e,options:{title:a.title||`Avalon App`},frontmatter:t.frontmatter};if(o.renderToHtmlWithLayouts&&s.EnhancedLayoutResolver&&s.EnhancedLayoutResolverUtils)try{let e=i.config.root||process.cwd();if(!globalThis.__avalonLayoutResolver){let t=s.EnhancedLayoutResolver,n=r.layoutsDir||`src/layouts`;globalThis.__avalonLayoutResolver=new t({baseDirectory:`${e}/${n}`,filePattern:`_layout.tsx`,excludeDirectories:[`node_modules`,`.git`,`dist`,`build`],enableWatching:!0,developmentMode:!1,enableCaching:!0,cacheTTL:60*1e3,maxCacheSize:100,enableStreaming:!0,enableErrorBoundaries:!0,enableMetrics:!1,enableDebugInfo:!1,modulesDir:r.modules?`${e}/${r.modules.dir}`:void 0,modulesLayoutsDirName:r.modules?.layoutsDirName})}let t=`http://localhost${n}`,l={params:{},query:{},url:t,request:{method:`GET`,url:t,headers:new Headers}};return await o.renderToHtmlWithLayouts(c,globalThis.__avalonLayoutResolver,l,n,{title:a.title||`Avalon App`},void 0,{suppressWarnings:!0})}catch{}if(o.renderToHtml)return await o.renderToHtml(c,{title:a.title||`Avalon App`},void 0,{suppressWarnings:!0})}catch{}let o=a.title||`Avalon App`,s=a.description||``,c=``;try{let t=await i.ssrLoadModule(`preact-render-to-string`);t.render&&typeof e==`function`&&(c=t.render(e()))}catch{c=`<p>Loading page: ${$(n)}</p>`}return`<!DOCTYPE html>
|
|
38
43
|
<html lang="en">
|
|
39
44
|
<head>
|
|
40
45
|
<meta charset="utf-8">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire as e}from"node:module";import{dirname as t,join as n}from"node:path";import{islandClientBundlerPlugin as r}from"../build/island-client-bundler.js";import{islandCodeSplittingPlugin as i}from"../build/island-code-splitting.js";import{mdxIslandTransform as a}from"../build/mdx-island-transform.js";import{createMDXPlugin as o}from"../build/mdx-plugin.js";import{pageIslandTransform as s}from"../build/page-island-transform.js";import{registry as c}from"../core/integrations/registry.js";import{discoverIntegrationsFromIslandUsage as l}from"./auto-discover.js";import{checkDirectoriesExist as u,resolveConfig as d}from"./config.js";import{createImagePlugin as f}from"./image-optimization.js";import{activateIntegrations as p,activateSingleIntegration as m}from"./integration-activator.js";import{islandSidecarPlugin as h}from"./island-sidecar-plugin.js";import{createNitroIntegration as g}from"./nitro-integration.js";import{formatValidationResults as
|
|
1
|
+
import{createRequire as e}from"node:module";import{dirname as t,join as n}from"node:path";import{islandClientBundlerPlugin as r}from"../build/island-client-bundler.js";import{islandCodeSplittingPlugin as i}from"../build/island-code-splitting.js";import{mdxIslandTransform as a}from"../build/mdx-island-transform.js";import{createMDXPlugin as o}from"../build/mdx-plugin.js";import{pageIslandTransform as s}from"../build/page-island-transform.js";import{registry as c}from"../core/integrations/registry.js";import{discoverIntegrationsFromIslandUsage as l}from"./auto-discover.js";import{checkDirectoriesExist as u,resolveConfig as d}from"./config.js";import{createImagePlugin as f}from"./image-optimization.js";import{activateIntegrations as p,activateSingleIntegration as m}from"./integration-activator.js";import{islandSidecarPlugin as h}from"./island-sidecar-plugin.js";import{createNitroIntegration as g}from"./nitro-integration.js";import{serverIslandsPlugin as _}from"./server-islands-plugin.js";import{formatValidationResults as v,validateActiveIntegrations as y}from"./validation.js";export async function collectIntegrationPlugins(e,t=!1){let n=[],r=[];for(let i of e){let e=await b(i,t);i===`lit`?r.push(...e):n.push(...e)}return[...r,...n]}async function b(e,t){let n=c.get(e);if(!n||typeof n.vitePlugin!=`function`)return[];try{let e=await n.vitePlugin();return(Array.isArray(e)?e:[e]).filter(e=>e!=null)}catch(t){return console.warn(`[avalon] Failed to load vite plugin for ${e}:`,t instanceof Error?t.message:t),[]}}async function x(e,t){let n=new Set;try{let r=await l(e.pagesDir,e.layoutsDir,t,e.modules?.dir);for(let t of r)e.integrations.includes(t)&&n.add(t)}catch{for(let t of e.integrations)n.add(t)}return n}async function S(e){if(!e.lazyIntegrations||e.integrations.length===0)return[...e.integrations];let t=await x(e);return t.size===0?[...e.integrations]:Array.from(t)}async function C(e){try{let t=await o({jsxImportSource:e.mdx.jsxImportSource,syntaxHighlighting:e.mdx.syntaxHighlighting,remarkPlugins:e.mdx.remarkPlugins,rehypePlugins:e.mdx.rehypePlugins,development:!0});return t.push(a({verbose:e.verbose})),t}catch(t){return e.showWarnings&&console.warn(`⚠️ Could not configure MDX plugin:`,t),[]}}function w(e,t,n){let{plugins:r,nitroOptions:i}=g(e,t);return globalThis.__nitroConfig=i,{plugins:r,options:i}}async function T(e,t,n){if(e.autoDiscoverIntegrations)try{let r=await l(e.pagesDir,e.layoutsDir,t,e.modules?.dir);for(let t of r)if(!n.has(t))try{await m(t,n,e.verbose)}catch(n){e.showWarnings&&console.warn(` ⚠️ Could not auto-load integration: ${t}`,n)}}catch(t){e.showWarnings&&console.warn(` ⚠️ Auto-discovery failed:`,t)}}function E(e,t){if(!e.validateIntegrations||t.size===0)return;let n=y(t,e.showWarnings);n.allValid||(console.error(v(n)),e.showWarnings&&console.warn(` ⚠️ Some integrations have validation issues.`))}export async function avalon(a){let o,c,l=new Set,m=d(a,!0),g=await S(m);g.length>0&&await p({...m,integrations:g},l);let v=await C(m),y=await f(m.image,m.verbose),b=[];l.size>0&&(b=await collectIntegrationPlugins(l,m.verbose));let x=[];if(a?.nitro){let{plugins:e}=w(m,a.nitro,m.verbose);x=e}let D=h({verbose:m.verbose}),O=_({verbose:m.verbose}),k=e(import.meta.url),A=null;try{A=n(t(k.resolve(`@useavalon/avalon/client`)),`main.js`)}catch{}let j=new Set([`preact`,`react`,`vue`,`svelte`,`solid`,`lit`,`qwik`].flatMap(e=>[`/@useavalon/${e}/client`,`/@useavalon/${e}/client/hmr`,`@useavalon/${e}/client`,`@useavalon/${e}/client/hmr`])),M={name:`avalon`,enforce:`pre`,config(e,{command:t}){let n={preact:[`preact`,`preact/hooks`],react:[`react`,`react-dom`,`react-dom/client`],vue:[`vue`],svelte:[`svelte`,`svelte/internal`],solid:[`solid-js`,`solid-js/web`],lit:[`lit`,`@lit-labs/ssr-client`],qwik:[`@builder.io/qwik`]},r=g.flatMap(e=>n[e]??[]);return{define:{__AVALON_PER_ISLAND__:JSON.stringify(t===`build`)},oxc:{exclude:[/node_modules\/@useavalon\/.*\.tsx?$/]},ssr:{noExternal:[/^@useavalon\//]},optimizeDeps:{exclude:[`@useavalon/avalon`,...g.map(e=>`@useavalon/${e}`)],include:r}}},configResolved(e){c=e,o=d(a,e.command===`serve`),globalThis.__avalonConfig=o,u(o,e.root)},async resolveId(e){if(e===`/src/client/main.js`&&A)return A;if(j.has(e)){let t=e.startsWith(`/`)?e.slice(1):e;return(await this.resolve(t))?.id??null}return null},async transform(e,t){if(t.includes(`@useavalon/`)&&/\.tsx?$/.test(t)){let{transform:n}=await import(`oxc-transform`),r=await n(t,e,{sourcemap:!0,typescript:{onlyRemoveTypeImports:!1}});return{code:r.code,map:r.map,moduleType:`js`}}},async buildStart(){await T(o,c?.root,l),E(o,l)},configureServer(e){globalThis.__viteDevServer=e}},N=b.filter(e=>e.name?.includes(`lit`)),P=b.filter(e=>!e.name?.includes(`lit`));return[s({pagesDir:m.pagesDir,layoutsDir:m.layoutsDir,modules:m.modules,verbose:m.verbose}),r(m,a?.nitro),i(m,a?.nitro),...y,...N,...v,M,D,O,...x,...P]}export function getResolvedConfig(){return globalThis.__avalonConfig}export function getPagesDir(){return globalThis.__avalonConfig?.pagesDir??`src/pages`}export function getLayoutsDir(){return globalThis.__avalonConfig?.layoutsDir??`src/layouts`}export function getNitroConfig(){return globalThis.__nitroConfig}export function isNitroEnabled(){return globalThis.__nitroConfig!==void 0}
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
/**
|
|
19
|
+
* Resolve an import path to a project-relative path suitable for manifest registration.
|
|
20
|
+
* Handles relative imports, alias imports, and absolute imports.
|
|
21
|
+
*
|
|
22
|
+
* Exported so the Nitro integration's source-scanning manifest generator can reuse
|
|
23
|
+
* the EXACT same resolution logic — the resulting path must equal the `src` that
|
|
24
|
+
* `island.tsx` hashes at runtime via `generateComponentId(src)`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveToRelativePath(importPath: string, fileId: string, projectRoot: string): string;
|
|
27
|
+
export interface ServerIslandsPluginOptions {
|
|
28
|
+
/** Whether to log verbose output */
|
|
29
|
+
verbose?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Vite plugin that collects server island components during build.
|
|
33
|
+
*
|
|
34
|
+
* Responsibilities:
|
|
35
|
+
* 1. Scans for `server` prop usage in JSX/TSX files during transform
|
|
36
|
+
* 2. Registers discovered components in the manifest (componentId → modulePath)
|
|
37
|
+
* 3. Provides virtual modules for the manifest and encryption key
|
|
38
|
+
*/
|
|
39
|
+
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)}export function resolveToRelativePath(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,g=!!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: ${g?`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=resolveToRelativePath(o.importPath,r,t),i=s(n);a(i,n),e.verbose&&console.log(`[avalon:server-islands] Registered: ${o.localName} → ${i} (${n})`)}return null},generateBundle(){let t=c(),n=Object.keys(t).length;n>0&&e.verbose&&console.log(`[avalon:server-islands] Manifest contains ${n} server island(s)`)}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/avalon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.93-canary.0",
|
|
4
4
|
"description": "Multi-framework islands architecture for the modern web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
"markdown",
|
|
30
30
|
"mdx"
|
|
31
31
|
],
|
|
32
|
+
"bin": {
|
|
33
|
+
"avalon": "bin/avalon.ts"
|
|
34
|
+
},
|
|
32
35
|
"scripts": {
|
|
33
36
|
"build:client": "bun run scripts/build-client.ts",
|
|
34
37
|
"build": "bun run scripts/build.ts",
|
|
@@ -144,7 +147,7 @@
|
|
|
144
147
|
}
|
|
145
148
|
},
|
|
146
149
|
"dependencies": {
|
|
147
|
-
"@useavalon/core": "^0.1.
|
|
150
|
+
"@useavalon/core": "^0.1.11-canary.0",
|
|
148
151
|
"@mdx-js/rollup": "^3.0.0",
|
|
149
152
|
"h3": "^2.0.1-rc.16",
|
|
150
153
|
"marked": "^17.0.4",
|