@useavalon/avalon 0.3.0 → 0.3.1-canary.3

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.
@@ -1,2 +1,2 @@
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}}}}
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}const i=[{prefix:`@shared/`,map:e=>`/app/shared/${e}`},{prefix:`@modules/`,map:e=>`/app/modules/${e}`},{prefix:`@/`,map:e=>`/app/${e}`},{prefix:`$components/`,map:e=>`/src/components/${e}`},{prefix:`$islands/`,map:e=>`/src/islands/${e}`},{prefix:`~/`,map:e=>`/src/${e}`}];function a(e){return e.startsWith(`/`)?e:`/${e}`}function o(e,t){for(let{find:n,replacement:r}of t){let t=a(r);if(typeof n==`string`){if(e===n||e.startsWith(`${n}/`))return`${t}${e.slice(n.length)}`}else if(n.test(e))return e.replace(n,t)}return null}function s(e){for(let{prefix:t,map:n}of i)if(e.startsWith(t))return n(e.slice(t.length));return null}function c(t,n){if(!t.startsWith(`.`))return null;let r=n.replaceAll(`\\`,`/`),i=r.indexOf(`/app/`);if(i===-1&&(i=r.indexOf(`/src/`)),i===-1)return null;let a=e(r.slice(i)).split(`/`);for(let e of t.split(`/`))e===`..`?a.pop():e!==`.`&&a.push(e);return a.join(`/`)}function l(e,t,n=[]){return e.startsWith(`/`)?e:o(e,n)??s(e)??c(e,t)??`/src/${e.split(`/`).pop()}`}function u(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 d(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 f(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 p=new Set([`qwik`]);function m(e){let t=u(e);return t!==void 0&&p.has(t)}const h=String.raw`[\s][^>]*island[\s]*[={]`,g=String.raw`[\s][^>]*server[\s]*[={]`,_=String.raw`[\s/>]`;function v(e,t){return t.some(t=>RegExp(`<${t}${h}`).test(e))}function y(e,t){return t.some(t=>RegExp(`<${t}${g}`).test(e))}function b(e,t){return t.some(t=>m(t.importPath)?RegExp(`<${t.localName}${_}`).test(e):!1)}function x(e,r,i,a=[]){let o=new Map;for(let s of r){let r=l(s.importPath,i,a),c=u(r),d=RegExp(`<${s.localName}${h}`),f=RegExp(`<${s.localName}${g}`),m=d.test(e),v=f.test(e);if(m||v){o.set(s.localName,{srcPath:r,framework:c,importPath:s.importPath,autoIsland:!1,hasServerProp:v}),v&&t(n(r),r);continue}c&&p.has(c)&&RegExp(`<${s.localName}${_}`).test(e)&&o.set(s.localName,{srcPath:r,framework:c,importPath:s.importPath,autoIsland:!0,hasServerProp:!1})}return o}function S(e,t){for(;t<e.length&&/\s/.test(e[t]);)t++;return t}function C(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 w(e,t){for(t++;t<e.length&&e[t]!=="`";){if(e[t]===`\\`){t+=2;continue}if(e[t]===`$`&&e[t+1]===`{`){t=T(t+1,e);continue}t++}return t<e.length?t+1:t}function T(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=C(t,n):n++}return n<t.length?n+1:n}function E(e,t){let n=t+1,r=T(t,e);return{value:e.slice(n,r-1),endIdx:r}}function D(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 O(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=S(e,r),e[r]!==`=`)return{name:i,value:null,endIdx:r};if(r=S(e,r+1),e[r]===`{`){let t=E(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}if(e[r]===`"`||e[r]===`'`){let t=D(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}return null}function k(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 A(e,t,n){let r=S(e,t+1+n.length),i=null,a=null,o=[];for(;r<e.length;){r=S(e,r);let t=k(e,r,n);if(t)return{endIdx:t.endIdx,islandProp:i,serverProp:a,otherProps:o};let s=O(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 j(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 M(e,t){let n=t-1;for(;n>=0&&/\s/.test(e[n]);)n--;if(n<0)return!1;let r=e[n];return r===`>`?e[n-1]!==`=`:r===`}`}function N(e,t){let n=e.slice(t,t+2);if(e[t]==="`")return w(e,t);if(e[t]===`{`&&n===`{/`&&e[t+2]===`*`)return P(e,t);if(n===`//`){let n=e.indexOf(`
2
+ `,t);return n===-1?e.length:n+1}if(n===`/*`){let n=e.indexOf(`*/`,t+2);return n===-1?e.length:n+2}return-1}function P(e,t){let n=e.indexOf(`*/`,t+3);if(n===-1)return-1;let r=n+2;for(;r<e.length&&/\s/.test(e[r]);)r++;return r<e.length&&e[r]===`}`?r+1:-1}function F(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 I(e,t,n,r,i){let a=`<${t}`,o=``,s=0;for(;s<e.length;){let c=N(e,s);if(c!==-1){o+=e.slice(s,c),s=c;continue}let{text:l,next:u}=L(e,s,{tag:a,componentName:t,srcPath:n,framework:r,autoIsland:i});o+=l,s=u}return o}function L(e,t,n){if(!F(e,t,n.tag))return{text:e[t],next:t+1};let r=A(e,t,n.componentName);if(!r||!r.islandProp&&!r.serverProp&&!n.autoIsland){let n=r?r.endIdx:t+1;return{text:e.slice(t,n),next:n}}let i=j(r,n.srcPath,n.framework,n.autoIsland&&!r.islandProp,n.componentName);return{text:M(e,t)?`{${i}}`:i,next:r.endIdx}}function R(e){if(/\bexport\s+default\s+function\b/.test(e))return e.replace(/\bexport\s+default\s+function\b/,`export default async function`);let t=/\bexport\s+default\s+(\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/;if(t.test(e))return e.replace(t,`export default async $1 =>`);let n=/\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;/.exec(e);if(n){let t=n[1].replaceAll(`$`,String.raw`\$&`),r=new RegExp(String.raw`(^|[^.\w])function\s+${t}\s*\(`,`m`),i=new RegExp(String.raw`\basync\s+function\s+${t}\b`);if(r.test(e)&&!i.test(e))return e.replace(r,e=>e.replace(`function`,`async function`));let a=new RegExp(String.raw`(\b(?:const|let|var)\s+${t}\s*=\s*)(?!async\b)(\([^)]*\)\s*=>|function\b|[A-Za-z_$][\w$]*\s*=>)`);if(a.test(e))return e.replace(a,`$1async $2`)}return e}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,o){let s=f(o,n,i);if(!d(o,t,i)&&!s)return null;let c=r(e);if(c.length===0)return null;let l=c.map(e=>e.localName);if(!v(e,l)&&!y(e,l)&&!b(e,c))return null;let u=x(e,c,o,a);if(u.size===0)return null;let p=`import { renderIsland as __pageRenderIsland } from '@useavalon/avalon';\n${e}`;for(let[e,t]of u)p=I(p,e,t.srcPath,t.framework,t.autoIsland);return p.includes(`__pageRenderIsland(`)&&(p=R(p)),{code:p,map:null}}}}
@@ -14,6 +14,19 @@
14
14
  * @module islands/per-island-script
15
15
  */
16
16
  import type { HydrationCondition } from "./island.tsx";
17
+ /**
18
+ * Escapes an already-serialized JSON string for safe embedding inside an inline
19
+ * `<script>`. `JSON.stringify` does NOT escape `<`, `>`, `&`, or the JS line
20
+ * terminators U+2028/U+2029, so a prop string value such as
21
+ * `"</script><img src=x onerror=alert(1)>"` would break out of the script
22
+ * element and inject markup. Encoding these as `\uXXXX` keeps the value a valid
23
+ * JS expression while making tag/comment breakout impossible.
24
+ *
25
+ * Island props are the framework's primary channel for dynamic (often
26
+ * request-derived) data, so this must be applied everywhere props are embedded
27
+ * into inline script text.
28
+ */
29
+ export declare function escapeJsonForScript(json: string): string;
17
30
  export interface PerIslandScriptOptions {
18
31
  /** The island element's DOM id */
19
32
  islandId: string;
@@ -1 +1 @@
1
- export function generatePerIslandScript(e){let{islandId:r,componentSrc:i,framework:a,condition:o,conditionArg:s,propsJson:c,isCustomDirective:l,directiveScript:u}=e;return`<script type="module">${n(r,o,t(r,i,a,c),s,l,u)}<\/script>`}function t(e,t,n,r){return[`async function h(){`,`var e=document.getElementById(${JSON.stringify(e)});`,`if(!e||e.dataset.hydrated)return;`,`try{`,`var p=${r};`,`var m=await import(${JSON.stringify(t)});`,`var C=m.default||Object.values(m).find(function(v){return typeof v==="function"&&v.prototype})||m;`,`if(m.__hydrateIsland){await m.__hydrateIsland(e,C,p)}`,`else if(m.loadIntegrationModule){var i=await m.loadIntegrationModule(${JSON.stringify(n)});if(i.hydrate)await i.hydrate(e,C,p)}`,`e.dataset.hydrated="true";`,`}catch(err){console.error("Hydration error:",err)}`,`}`].join(``)}function n(e,t,n,r,i,a){if(t===`on:client`)return`${n}h();`;if(t===`on:visible`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){try{var t=e.firstElementChild||e;var o=new IntersectionObserver(function(n){`,`if(n[0].isIntersecting){h();o.disconnect()}`,`},{rootMargin:"50px",threshold:0});o.observe(t)}catch(_){h()}}`].join(``);if(t===`on:idle`)return[n,`if("requestIdleCallback" in globalThis){`,`globalThis.requestIdleCallback(function(){h()},{timeout:5000})`,`}else if(document.readyState==="complete"){`,`setTimeout(function(){h()},200)`,`}else{globalThis.addEventListener("load",function(){setTimeout(function(){h()},200)},{once:true})}`].join(``);if(t===`on:interaction`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var t=e.firstElementChild||e;var d=false;var ev=["click","touchstart","mouseenter","focusin"];`,`var fn=function(){if(d)return;d=true;ev.forEach(function(n){t.removeEventListener(n,fn)});h()};`,`ev.forEach(function(n){t.addEventListener(n,fn,{once:true,passive:true})})}`].join(``);if(t.startsWith(`media:`)){let e=t.slice(6);return[n,`try{var mq=globalThis.matchMedia(${JSON.stringify(e)});`,`if(mq.matches){h()}else{mq.addEventListener("change",function x(ev){`,`if(ev.matches){h();mq.removeEventListener("change",x)}},{once:true})}}catch(_){h()}`].join(``)}return i&&a?[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var dir=(${a});`,`dir(e,function(){h()}${r?`,${JSON.stringify(r)}`:``})}`].join(``):`${n}h();`}
1
+ export function escapeJsonForScript(e){return e.replaceAll(`<`,String.raw`\u003c`).replaceAll(`>`,String.raw`\u003e`).replaceAll(`&`,String.raw`\u0026`).replaceAll(`\u2028`,String.raw`\u2028`).replaceAll(`\u2029`,String.raw`\u2029`)}export function generatePerIslandScript(e){let{islandId:t,componentSrc:i,framework:a,condition:o,conditionArg:s,propsJson:c,isCustomDirective:l,directiveScript:u}=e;return`<script type="module">${r(t,o,n(t,i,a,c),s,l,u)}<\/script>`}function n(t,n,r,i){return[`async function h(){`,`var e=document.getElementById(${JSON.stringify(t)});`,`if(!e||e.dataset.hydrated)return;`,`try{`,`var p=${escapeJsonForScript(i)};`,`var m=await import(${JSON.stringify(n)});`,`var C=m.default||Object.values(m).find(function(v){return typeof v==="function"&&v.prototype})||m;`,`if(m.__hydrateIsland){await m.__hydrateIsland(e,C,p)}`,`else if(m.loadIntegrationModule){var i=await m.loadIntegrationModule(${JSON.stringify(r)});if(i.hydrate)await i.hydrate(e,C,p)}`,`e.dataset.hydrated="true";`,`}catch(err){console.error("Hydration error:",err)}`,`}`].join(``)}function r(e,t,n,r,i,a){if(t===`on:client`)return`${n}h();`;if(t===`on:visible`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){try{var t=e.firstElementChild||e;var o=new IntersectionObserver(function(n){`,`if(n[0].isIntersecting){h();o.disconnect()}`,`},{rootMargin:"50px",threshold:0});o.observe(t)}catch(_){h()}}`].join(``);if(t===`on:idle`)return[n,`if("requestIdleCallback" in globalThis){`,`globalThis.requestIdleCallback(function(){h()},{timeout:5000})`,`}else if(document.readyState==="complete"){`,`setTimeout(function(){h()},200)`,`}else{globalThis.addEventListener("load",function(){setTimeout(function(){h()},200)},{once:true})}`].join(``);if(t===`on:interaction`)return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var t=e.firstElementChild||e;var d=false;var ev=["click","touchstart","mouseenter","focusin"];`,`var fn=function(){if(d)return;d=true;ev.forEach(function(n){t.removeEventListener(n,fn)});h()};`,`ev.forEach(function(n){t.addEventListener(n,fn,{once:true,passive:true})})}`].join(``);if(t.startsWith(`media:`)){let e=t.slice(6);return[n,`try{var mq=globalThis.matchMedia(${JSON.stringify(e)});`,`if(mq.matches){h()}else{mq.addEventListener("change",function x(ev){`,`if(ev.matches){h();mq.removeEventListener("change",x)}},{once:true})}}catch(_){h()}`].join(``)}if(i&&a){let t=r?`,${JSON.stringify(r)}`:``;return[n,`var e=document.getElementById(${JSON.stringify(e)});`,`if(e){var dir=(${a});`,`dir(e,function(){h()}${t})}`].join(``)}return`${n}h();`}
@@ -72,8 +72,8 @@ import{readFile as e}from"node:fs/promises";import{h as t}from"preact";import{re
72
72
  <div class="error-container">
73
73
  <h1>Server Error</h1>
74
74
  <p>An error occurred while rendering the page:</p>
75
- <pre>${e.message}</pre>
76
- ${t&&e.stack?`<pre>${e.stack}</pre>`:``}
75
+ <pre>${w(e.message)}</pre>
76
+ ${t&&e.stack?`<pre>${w(e.stack)}</pre>`:``}
77
77
  </div>
78
78
  </body>
79
79
  </html>`}function N(e,t){let n=process.env.NODE_ENV!==`production`,r=t?`<p><strong>Component ID:</strong> ${t}</p>`:``,i=e.stack?`<pre style="background:#f5f5f5;padding:10px;border-radius:4px;overflow-x:auto;font-size:12px;margin-top:10px">${e.stack}</pre>`:``,a=n?`<details style="margin-top:15px"><summary style="cursor:pointer;color:#856404;font-weight:bold">Error Details (Development Mode)</summary><div style="margin-top:10px">${r}<p><strong>Error:</strong> ${e.message}</p>${i}</div></details>`:``;return`
@@ -28,14 +28,10 @@ export interface ServerIslandEndpointOptions {
28
28
  /**
29
29
  * Creates a Nitro-compatible event handler for the server islands endpoint.
30
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
31
+ * The handler extracts the componentId + encrypted props, decrypts them, imports
32
+ * the component from the manifest, renders it, appends a hydration script for
33
+ * combined islands, and returns HTML. Each phase is delegated to a helper that
34
+ * returns either its result or an error `Response`.
39
35
  *
40
36
  * Error responses:
41
37
  * - 400 Bad Request: decryption failure or missing props
@@ -1,4 +1,4 @@
1
- import{serverIslandCSS as e,serverIslandLoaders as t}from"virtual:server-island-manifest";import{h as n}from"preact";import r from"preact-render-to-string";import{loadIntegration as i}from"../islands/integration-loader.js";import{generatePerIslandScript as a}from"../islands/per-island-script.js";import{decrypt as o}from"./encryption.js";import{lookupComponent as s}from"./manifest.js";const c=t??{};function l(){return c}export function defineServerIslandHandler(t={}){let{isDev:c=process.env.NODE_ENV!==`production`,defaultCacheControl:u=`private, no-store`}=t;return async t=>{let f=d(t);if(!f)return new Response(`Missing component ID`,{status:400,headers:{"Content-Type":`text/plain`}});let p;try{p=await m(t)}catch{return new Response(`Failed to read request body`,{status:400,headers:{"Content-Type":`text/plain`}})}if(!p)return new Response(`Missing encrypted props`,{status:400,headers:{"Content-Type":`text/plain`}});let h,g,_;try{let e;if(p.startsWith(`dev.`)){let t=p.slice(4);e=Buffer.from(t,`base64url`).toString(`utf8`)}else e=o(p);let t=JSON.parse(e);t.__island&&(g=t.__island,delete t.__island),t.__src&&(_=t.__src,delete t.__src),h=t}catch{return new Response(`Bad Request: decryption failed`,{status:400,headers:{"Content-Type":`text/plain`}})}let v=s(f)??_;if(!v)return new Response(`Component not found`,{status:404,headers:{"Content-Type":`text/plain`}});let y;try{let e=l()[f];if(e?y=await e():(c&&console.log(`[server-islands] Loading component from:`,v),y=await import(v)),!y.default)throw TypeError(`Module "${v}" does not have a default export`)}catch(e){if(g&&c)console.warn(`[server-islands] Component import failed (will render client-side):`,v,e instanceof Error?e.message:e),y={default:null};else{let t=c&&e instanceof Error?e.message:`Component import failed`;return new Response(t,{status:500,headers:{"Content-Type":`text/plain`}})}}let b,x=!1,S,C=g?.framework??`preact`;if(y.default)try{if(C===`preact`||C===`react`){let e=y.default;b=r(n(e,h))}else{let t=await(await i(C)).render({component:y.default,props:h,src:v,condition:g?.condition??`on:client`,ssrOnly:!1,viteServer:void 0,isDev:c});b=t.html;let n=t.hydrationData;n?.renderId&&(S=n.renderId),t.css?b=`${b}<style>${t.css}</style>`:e[f]&&(b=`${b}<style>${e[f]}</style>`)}}catch(e){if(g&&c)b=``,x=!0;else{let t=c&&e instanceof Error?`${e.message}\n${e.stack}`:``;return console.error(`[server-islands] Render error for`,v,e),new Response(t||`Internal Server Error`,{status:500,headers:{"Content-Type":`text/plain`}})}}else b=``,x=!0;if(g){let e=g.elementId??`si-${f}`,t=g.componentSrc??v,n;try{n=JSON.stringify(h)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Failed to serialize props for server island (componentId=${f}, module=${v}, elementId=${e}): ${n}`)}let r=x?`on:client`:g.condition??`on:client`;if(c){let i=new URL(`../client/server-island-hydrate.ts`,import.meta.url).pathname,a=g.framework??`preact`,o=S?JSON.stringify(S):`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>`;b+=s}else{let r=a({islandId:e,componentSrc:t,framework:g.framework,condition:g.condition,conditionArg:g.conditionArg,propsJson:n});b+=r}}return new Response(b,{status:200,headers:{"Content-Type":`text/html`,"Cache-Control":u}})}}function d(e){let t=e.context?.params;if(t?.componentId)return t.componentId;let n=f(e);return/\/_server-islands\/([^/?]+)/.exec(n)?.[1]}function f(e){if(e.url)return e.url.pathname;let t=e.req?.url;return t?new URL(t,`http://localhost`).pathname:`/`}function p(e){return e.req?.method??`GET`}async function m(e){let t=p(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 h(e.node.req);if(e._body!==void 0)return String(e._body)}}function h(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)})}
1
+ import{serverIslandCSS as e,serverIslandLoaders as t}from"virtual:server-island-manifest";import{h as n}from"preact";import r from"preact-render-to-string";import{loadIntegration as i}from"../islands/integration-loader.js";import{escapeJsonForScript as a,generatePerIslandScript as o}from"../islands/per-island-script.js";import{decrypt as s}from"./encryption.js";import{lookupComponent as c}from"./manifest.js";const l=t??{};function u(){return l}function d(e,t){return new Response(e,{status:t,headers:{"Content-Type":`text/plain`}})}export function defineServerIslandHandler(e={}){let{isDev:t=process.env.NODE_ENV!==`production`,defaultCacheControl:n=`private, no-store`}=e;return async e=>{let r=y(e);if(!r)return d(`Missing component ID`,400);let i=await p(e);if(i instanceof Response)return i;let a=m(i,t);if(a instanceof Response)return a;let{props:o,islandMeta:s,srcPath:l}=a,u=c(r)??(t?l:void 0);if(!u)return d(`Component not found`,404);let f=await h(r,u,t,!!s);if(f instanceof Response)return f;let _=await g({componentModule:f,props:o,islandMeta:s,componentId:r,modulePath:u,isDev:t});if(_ instanceof Response)return _;let b=_.html;return s&&(b+=v({componentId:r,islandMeta:s,modulePath:u,props:o,ssrFailed:_.ssrFailed,hydrationRenderId:_.hydrationRenderId,isDev:t})),new Response(b,{status:200,headers:{"Content-Type":`text/html`,"Cache-Control":n}})}}async function p(e){let t;try{t=await S(e)}catch{return d(`Failed to read request body`,400)}return t||d(`Missing encrypted props`,400)}function m(e,t){try{let n;n=t&&e.startsWith(`dev.`)?Buffer.from(e.slice(4),`base64url`).toString(`utf8`):s(e);let r=JSON.parse(n),i={props:r};return r.__island&&(i.islandMeta=r.__island,delete r.__island),r.__src&&(i.srcPath=r.__src,delete r.__src),i}catch{return d(`Bad Request: decryption failed`,400)}}async function h(e,t,n,r){try{let r=u()[e],i;if(r?i=await r():(n&&console.log(`[server-islands] Loading component from:`,t),i=await import(t)),!i.default)throw TypeError(`Module "${t}" does not have a default export`);return i}catch(e){return r&&n?(console.warn(`[server-islands] Component import failed (will render client-side):`,t,e instanceof Error?e.message:e),{default:null}):d(n&&e instanceof Error?e.message:`Component import failed`,500)}}async function g(e){let{componentModule:t,props:i,islandMeta:a,componentId:o,modulePath:s,isDev:c}=e;if(!t.default)return{html:``,ssrFailed:!0};let l=a?.framework??`preact`;try{if(l===`preact`||l===`react`){let e=t.default;return{html:r(n(e,i)),ssrFailed:!1}}return await _({framework:l,component:t.default,props:i,islandMeta:a,componentId:o,modulePath:s,isDev:c})}catch(e){if(a&&c)return{html:``,ssrFailed:!0};let t=c&&e instanceof Error?`${e.message}\n${e.stack}`:``;return console.error(`[server-islands] Render error for`,s,e),d(t||`Internal Server Error`,500)}}async function _(t){let{framework:n,component:r,props:a,islandMeta:o,componentId:s,modulePath:c,isDev:l}=t,u=await(await i(n)).render({component:r,props:a,src:c,condition:o?.condition??`on:client`,ssrOnly:!1,viteServer:void 0,isDev:l}),d=u.html;u.css?d=`${d}<style>${u.css}</style>`:e[s]&&(d=`${d}<style>${e[s]}</style>`);let f=u.hydrationData?.renderId;return{html:d,ssrFailed:!1,hydrationRenderId:f}}function v(e){let{componentId:t,islandMeta:n,modulePath:r,props:i,ssrFailed:s,hydrationRenderId:c,isDev:l}=e,u=n.elementId??`si-${t}`,d=n.componentSrc??r,f;try{f=JSON.stringify(i)}catch(e){let n=e instanceof Error?e.message:String(e);throw Error(`Failed to serialize props for server island (componentId=${t}, module=${r}, elementId=${u}): ${n}`)}if(l){let e=s?`on:client`:n.condition??`on:client`,t=new URL(`../client/server-island-hydrate.ts`,import.meta.url).pathname,r=n.framework??`preact`,i=c?JSON.stringify(c):`undefined`;return`<script type="module">
2
+ import{hydrateServerIsland}from"/@fs${t}";
3
+ hydrateServerIsland(${JSON.stringify(u)},${JSON.stringify(d)},${a(f)},${JSON.stringify(e)},${JSON.stringify(r)},${i});
4
+ <\/script>`}return o({islandId:u,componentSrc:d,framework:n.framework,condition:n.condition,conditionArg:n.conditionArg,propsJson:f})}function y(e){let t=e.context?.params;if(t?.componentId)return t.componentId;let n=b(e);return/\/_server-islands\/([^/?]+)/.exec(n)?.[1]}function b(e){if(e.url)return e.url.pathname;let t=e.req?.url;return t?new URL(t,`http://localhost`).pathname:`/`}function x(e){return e.req?.method??`GET`}async function S(e){let t=x(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 C(e.node.req);if(e._body!==void 0)return String(e._body)}}function C(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)})}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useavalon/avalon",
3
- "version": "0.3.0",
3
+ "version": "0.3.1-canary.3",
4
4
  "description": "Multi-framework islands architecture for the modern web",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -159,7 +159,7 @@
159
159
  }
160
160
  },
161
161
  "dependencies": {
162
- "@useavalon/core": "^0.3.0",
162
+ "@useavalon/core": "^0.3.1-canary.3",
163
163
  "@mdx-js/rollup": "^3.0.0",
164
164
  "h3": "^2.0.1-rc.16",
165
165
  "marked": "^17.0.4",