@useavalon/avalon 0.3.2-canary.16 → 0.3.2-canary.17
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/dist/src/build/island-client-bundler.js +1 -1
- package/dist/src/build/island-ssr-flag.d.ts +14 -0
- package/dist/src/build/island-ssr-flag.js +1 -0
- package/dist/src/build/mdx-island-transform.js +2 -2
- package/dist/src/build/page-island-transform.js +2 -2
- package/dist/src/client/hydrate-runtime.d.ts +3 -0
- package/dist/src/client/hydrate-runtime.js +1 -1
- package/dist/src/islands/framework-base-css.d.ts +5 -1
- package/dist/src/islands/framework-base-css.js +1 -1
- package/dist/src/islands/island.d.ts +4 -2
- package/dist/src/islands/island.js +1 -1
- package/dist/src/islands/per-island-script.js +1 -1
- package/dist/src/islands/types.d.ts +2 -0
- package/dist/src/post-build/isolated-island-builder.js +2 -2
- package/dist/src/types/island-prop.d.ts +13 -2
- package/dist/src/types/virtual-modules.d.ts +5 -0
- package/dist/src/vite-plugin/nitro-integration.js +1 -1
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import{existsSync as e,readdirSync as t,readFileSync as n,statSync as r}from"node:fs";import{dirname as i,relative as a,resolve as o}from"node:path";export function islandClientBundlerPlugin(e,t,n){let r=process.cwd(),i=new Map,a=!1,o=c(e,r);for(let e of o)d(e,r,i);let s=`\0avalon-island-entry:`,l=!1,u=[],f={},p=`dist`;return{name:`avalon:island-client-bundler`,enforce:`pre`,configResolved(e){l=e.command===`serve`,a=e.command===`build`,u=e.resolve?.alias??[],f=e.define??{},p=e.build?.outDir??`dist`},resolveId(e){return e.startsWith(s)?e:null},load(e){if(!e.startsWith(s))return null;let t=e.slice(21),n=JSON.stringify(t);if(t.includes(`.qwik.`))return a?[`export * from ${n};`,`export { _hW } from "@builder.io/qwik";`].join(`
|
|
2
2
|
`):[`export * from ${n};`,`export { _hW } from "@builder.io/qwik";`,`import __C from ${n};`,`export default __C;`,`if(typeof globalThis<"u")globalThis.__avalonIsland=__C;`].join(`
|
|
3
|
-
`);let r=t.includes(`.lit.`),i=t.includes(`.solid.`),o=t.includes(`.preact.`),c=t.includes(`.react.`),l=t.includes(`.vue.`)||t.endsWith(`.vue`),u=t.includes(`.svelte.`)||t.endsWith(`.svelte`),d=[];if(r&&a&&d.push(`import "@useavalon/lit/client";`),d.push(`import __C from ${n};`,`var Component = __C;`,`export { Component as default, Component };`,`if(typeof globalThis<"u")globalThis.__avalonIsland=Component;`),a){let e={solid:`@useavalon/solid/client`,preact:`@useavalon/preact/client`,react:`@useavalon/react/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`,lit:`@useavalon/lit/client`},t=i?`solid`:o?`preact`:c?`react`:l?`vue`:u?`svelte`:r?`lit`:null;t&&e[t]?d.push(`export { hydrate as __hydrateIsland } from ${JSON.stringify(e[t])};`):d.push(`export { loadIntegrationModule } from "virtual:avalon/integration-loader";`)}return d.join(`
|
|
3
|
+
`);let r=t.includes(`.lit.`),i=t.includes(`.solid.`),o=t.includes(`.preact.`),c=t.includes(`.react.`),l=t.includes(`.vue.`)||t.endsWith(`.vue`),u=t.includes(`.svelte.`)||t.endsWith(`.svelte`),d=[];if(r&&a&&d.push(`import "@useavalon/lit/client";`),d.push(`import __C from ${n};`,`var Component = __C;`,`export { Component as default, Component };`,`if(typeof globalThis<"u")globalThis.__avalonIsland=Component;`),a){let e={solid:`@useavalon/solid/client`,preact:`@useavalon/preact/client`,react:`@useavalon/react/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`,lit:`@useavalon/lit/client`},t=i?`solid`:o?`preact`:c?`react`:l?`vue`:u?`svelte`:r?`lit`:null;t&&e[t]?d.push(`export { hydrate as __hydrateIsland, mount as __mountIsland } from ${JSON.stringify(e[t])};`):d.push(`export { loadIntegrationModule } from "virtual:avalon/integration-loader";`)}return d.join(`
|
|
4
4
|
`)},async buildStart(){if(l)return;let t=this.environment;if(!(t&&t.name!==`client`)&&i.size>0){for(let[,e]of i)this.emitFile({type:`chunk`,id:s+e.filePath,fileName:`islands/${e.bundleKey}.js`,preserveSignature:`exports-only`});e.verbose&&console.log(`🏝️ Emitting ${i.size} island client bundles`)}},async closeBundle(){if(l||!a||i.size===0||globalThis.__avalonIslandsRebuilt)return;globalThis.__avalonIslandsRebuilt=!0;let{buildIsolatedIslands:e}=await import(`../post-build/isolated-island-builder.js`),t=new Map;for(let[e,n]of i){let r=n.filePath.includes(`.solid.`)?`solid`:n.filePath.includes(`.preact.`)?`preact`:n.filePath.includes(`.react.`)?`react`:n.filePath.includes(`.vue.`)||n.filePath.endsWith(`.vue`)?`vue`:n.filePath.includes(`.svelte.`)||n.filePath.endsWith(`.svelte`)?`svelte`:n.filePath.includes(`.lit.`)?`lit`:n.filePath.includes(`.qwik.`)?`qwik`:`preact`;t.set(e,{...n,framework:r})}await e(r,p,t,u,f,n?{treeshake:n}:void 0)}}}function c(n,r){let i=[];if(n.pagesDir){let t=o(r,n.pagesDir);e(t)&&i.push(t)}if(n.layoutsDir){let t=o(r,n.layoutsDir);e(t)&&i.push(t)}if(n.modules){let a=o(r,n.modules.dir);if(e(a))try{for(let n of t(a,{withFileTypes:!0}))if(n.isDirectory())for(let t of[`pages`,`layouts`,`components`]){let r=o(a,n.name,t);e(r)&&i.push(r)}}catch{}}return i}const l=[`.qwik.`];function u(e){return l.some(t=>e.includes(t))}function d(e,r,i){let a;try{a=t(e,{withFileTypes:!0})}catch{return}for(let t of a){let a=o(e,t.name);if(t.isDirectory()){d(a,r,i);continue}if(/\.(tsx?|jsx?|mdx?)$/.test(t.name))try{let e=n(a,`utf-8`),t=e.includes(`island=`)||e.includes(`island `),o=/import\s+\w+\s+from\s+['"][^'"]*\.qwik\.[^'"]*['"]/m.test(e);if(!t&&!o)continue;f(e,a,r,i)}catch{}}}function f(e,t,n,r){let i=/<([A-Z]\w*)\s[^>]*\bisland\s*[={]/g,o=/<([A-Z]\w*)\s[\s\S]{0,2000}?\bisland\s*[={]/g,s=new Set,c=null;for(c=i.exec(e);c!==null;c=i.exec(e))s.add(c[1]);for(c=o.exec(e);c!==null;c=o.exec(e))s.add(c[1]);let l=/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,d=new Set,f=[];for(c=l.exec(e);c!==null;c=l.exec(e))f.push([c[1],c[2]]),u(c[2])&&RegExp(`<${c[1]}[\\s/>]`).test(e)&&d.add(c[1]);if(!(s.size===0&&d.size===0))for(let[e,i]of f){if(!s.has(e)&&!d.has(e))continue;let o=p(i,t,n);if(!o)continue;let c=a(n,o).replaceAll(`\\`,`/`).replace(/\.(tsx?|jsx?)$/,``);r.has(o)||r.set(o,{filePath:o,bundleKey:c})}}function p(t,n,a,s=[]){let c;for(let n of s){let i=n.find;if(typeof i==`string`){if(t===i||t.startsWith(`${i}/`)){let s=t.slice(i.length);if(c=o(a,n.replacement+s),e(c)&&r(c).isFile())return c;for(let t of[`.tsx`,`.ts`,`.jsx`,`.js`,`.vue`,`.svelte`])if(e(c+t))return c+t}}else if(i instanceof RegExp&&i.test(t)){if(c=o(a,t.replace(i,n.replacement)),e(c)&&r(c).isFile())return c;for(let t of[`.tsx`,`.ts`,`.jsx`,`.js`,`.vue`,`.svelte`])if(e(c+t))return c+t}}if(t.startsWith(`@shared/`))c=o(a,`app/shared`,t.slice(8));else if(t.startsWith(`@modules/`))c=o(a,`app/modules`,t.slice(9));else if(t.startsWith(`@/`))c=o(a,`app`,t.slice(2));else if(t.startsWith(`.`))c=o(i(n),t);else return null;if(e(c)&&r(c).isFile())return c;for(let t of[`.tsx`,`.ts`,`.jsx`,`.js`,`.vue`,`.svelte`])if(e(c+t))return c+t;return null}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for computing the `ssr` flag from an `island` prop object.
|
|
3
|
+
* Used by the page and MDX transforms so the expression stays in one place.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Runtime expression evaluated against the island directive object.
|
|
7
|
+
* `clientOnly: true` wins, then an explicit `ssr`, otherwise page usage SSRs.
|
|
8
|
+
*/
|
|
9
|
+
export declare function islandSsrExpression(islandValue: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* True when the island prop is an object literal that statically opts out of SSR.
|
|
12
|
+
* Runtime-computed values (`island={opts}`) cannot be proven client-only.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isStaticallyClientOnly(islandProp: string | null | undefined): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function islandSsrExpression(e){return`((__i) => __i.clientOnly === true ? false : (__i.ssr !== undefined ? __i.ssr : true))(${e})`}export function isStaticallyClientOnly(e){if(!e)return!1;let t=e.trim();return t.startsWith(`{`)?/\bclientOnly\s*:\s*true\b/.test(t)||/\bssr\s*:\s*false\b/.test(t):!1}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{dirname as e}from"node:path";const
|
|
2
|
-
`+l.slice(t)}}for(let e of c){let
|
|
1
|
+
import{dirname as e}from"node:path";import{islandSsrExpression as t}from"./island-ssr-flag.js";const n=[/['"]\.\.\/islands\//,/['"]\.\/islands\//,/['"]\.\.\/\.\.\/islands\//,/['"]\$islands\//,/['"]@\/islands\//,/['"]\/src\/islands\//];function r(e){let t=new Map,n=/import\s+([A-Z]\w*)\s+from\s+(['"][^'"]+['"])/g;for(let r=n.exec(e);r!==null;r=n.exec(e)){let e=r[1],n=r[2].slice(1,-1);t.set(e,n)}return t}function i(e){let t=new Set,n=/<([A-Z]\w*)\s+[^>]*\bisland\s*[={]/g;for(let r=n.exec(e);r!==null;r=n.exec(e))t.add(r[1]);let r=/(?:_?jsxs?(?:DEV)?)\s*\(\s*([A-Z]\w*)\s*,\s*\{[^}]*\bisland\s*:/g;for(let n=r.exec(e);n!==null;n=r.exec(e))t.add(n[1]);return t}function a(e,t){let n=[],a=r(e),o=i(e);for(let[e,r]of a){let i=`"${r}"`,a=t.some(e=>e.test(i)),s=o.has(e);(a||s)&&n.push({localName:e,importPath:r,islandPropUsage:s})}return n}function o(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 t.includes(`/islands/`)?`/src/islands/${t.split(`/`).at(-1)}`:`/src/${t.split(`/`).pop()}`}function s(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.endsWith(`.tsx`)||e.endsWith(`.jsx`))return`preact`}function c(e,t){let n=t+1,r=1;for(;n<e.length&&r>0;){let t=e[n];t===`{`?(r++,n++):t===`}`?(r--,r>0&&n++):t===`'`||t===`"`||t==="`"?n=l(e,n):n++}return n<e.length?n+1:n}function l(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 u(e,t){let n=t,r=0;for(;n<e.length&&e[n]!==`(`;)n++;if(n>=e.length)return t;for(;n<e.length;){let t=e[n];if(t===`(`)r++,n++;else if(t===`)`){if(r--,n++,r===0)return n}else t===`{`?n=c(e,n):t===`'`||t===`"`||t==="`"?n=l(e,n):n++}return n}function d(e){let t=e.match(/\bisland\s*:\s*/);if(!t)return null;let n=t.index+t[0].length,r;if(e[n]===`{`)r=c(e,n);else{let t=n,i=0;for(;t<e.length;){let n=e[t];if(n===`{`||n===`[`||n===`(`)i++,t++;else if(n===`}`||n===`]`||n===`)`){if(i===0)break;i--,t++}else if(n===`,`&&i===0)break;else t++}r=t}let i=e.slice(n,r).trim(),a=e.slice(0,t.index).trim(),o=e.slice(r).trim(),s=a;return o.startsWith(`,`)?s+=o.slice(1):s+=o,s=s.replace(/,\s*}$/,`}`).replace(/{\s*,/,`{`),{islandValue:i,otherProps:s}}function f(e,n,r,i){let a=RegExp(`(_?jsxs?(?:DEV)?)\\s*\\(\\s*${n}\\s*,`,`g`),o=``,s=0;for(let n=a.exec(e);n!==null;n=a.exec(e)){let a=n.index;n[1];let l=u(e,a),f=e.slice(a,l);if(!f.includes(`island`)){o+=e.slice(s,l),s=l;continue}let p=f.indexOf(`{`);if(p===-1){o+=e.slice(s,l),s=l;continue}let m=c(f,p),h=d(f.slice(p,m));if(!h){o+=e.slice(s,l),s=l;continue}let{islandValue:g,otherProps:_}=h,v=`(await __AvalonRenderIsland({ src: "${r}", ${i?`framework: "${i}",`:``} ...(${g}), ${_.trim()!==`{}`&&_.trim()!==``?`props: ${_},`:``} ssr: ${t(g)} }))`;o+=e.slice(s,a)+v,s=l}return o+=e.slice(s),o}export function mdxIslandTransform(e={}){let{islandPathPatterns:t=n,verbose:r=!1}=e,i=[];return{name:`avalon:mdx-island-transform`,enforce:`post`,configResolved(e){i=e.resolve?.alias??[]},transform(e,n){if(!n.endsWith(`.mdx`)&&!n.includes(`.mdx?`))return null;let c=a(e,t);if(c.length===0)return null;if(r){console.log(`[mdx-island-transform] Found ${c.length} island import(s) in ${n}`);for(let e of c)console.log(` - ${e.localName} from ${e.importPath}${e.islandPropUsage?` (island prop)`:` (islands dir)`}`)}let l=e;if(!(l.includes(`from "@useavalon/avalon"`)||l.includes(`from '@useavalon/avalon'`))){let e=/^(import\s.+?from\s+.+?\n)/m.exec(l);if(e){let t=l.indexOf(e[0])+e[0].length;l=l.slice(0,t)+`import { renderIsland as __AvalonRenderIsland } from "@useavalon/avalon";
|
|
2
|
+
`+l.slice(t)}}for(let e of c){let t=o(e.importPath,n,i),r=s(t);l=f(l,e.localName,t,r)}for(let e of c){let t=RegExp(`import\\s+${e.localName}\\s+from\\s+(['"][^'"]+['"])`,`g`);l=l.replace(t,`import $1; // [mdx-island-transform] kept for CSS: ${e.localName}`)}return l=l.replace(/function\s+_createMdxContent\s*\(/g,`async function _createMdxContent(`),l=l.replace(/export\s+default\s+function\s+MDXContent\s*\(/g,`export default async function MDXContent(`),r&&console.log(`[mdx-island-transform] Transformed ${n}`),{code:l,map:null}}}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{dirname as e}from"node:path";import{addToManifest as t,generateComponentId as n}from"../server-islands/manifest.js";import{
|
|
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
|
|
1
|
+
import{dirname as e}from"node:path";import{addToManifest as t,generateComponentId as n}from"../server-islands/manifest.js";import{islandSsrExpression as r,isStaticallyClientOnly as i}from"./island-ssr-flag.js";import{ensureAwaitContextsAsync as a}from"./page-island-await.js";function o(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 s=[{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 c(e){return e.startsWith(`/`)?e:`/${e}`}function l(e,t){for(let{find:n,replacement:r}of t){let t=c(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 u(e){for(let{prefix:t,map:n}of s)if(e.startsWith(t))return n(e.slice(t.length));return null}function d(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 f(e,t,n=[]){return e.startsWith(`/`)?e:l(e,n)??u(e)??d(e,t)??`/src/${e.split(`/`).pop()}`}function p(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 m(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 h(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 g=new Set([`qwik`]);function _(e){let t=p(e);return t!==void 0&&g.has(t)}const v=String.raw`[\s][^>]*island[\s]*[={]`,y=String.raw`[\s][^>]*server[\s]*[={]`,b=String.raw`[\s/>]`;function x(e,t){return t.some(t=>RegExp(`<${t}${v}`).test(e))}function S(e,t){return t.some(t=>RegExp(`<${t}${y}`).test(e))}function C(e,t){return t.some(t=>_(t.importPath)?RegExp(`<${t.localName}${b}`).test(e):!1)}function w(e,r,i,a=[]){let o=new Map;for(let s of r){let r=f(s.importPath,i,a),c=p(r),l=RegExp(`<${s.localName}${v}`),u=RegExp(`<${s.localName}${y}`),d=l.test(e),m=u.test(e);if(d||m){o.set(s.localName,{srcPath:r,framework:c,importPath:s.importPath,autoIsland:!1,hasServerProp:m}),m&&t(n(r),r);continue}c&&g.has(c)&&RegExp(`<${s.localName}${b}`).test(e)&&o.set(s.localName,{srcPath:r,framework:c,importPath:s.importPath,autoIsland:!0,hasServerProp:!1})}return o}function T(e,t){for(;t<e.length&&/\s/.test(e[t]);)t++;return t}function E(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 D(e,t){for(t++;t<e.length&&e[t]!=="`";){if(e[t]===`\\`){t+=2;continue}if(e[t]===`$`&&e[t+1]===`{`){t=O(t+1,e);continue}t++}return t<e.length?t+1:t}function O(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=E(t,n):n++}return n<t.length?n+1:n}function k(e,t){let n=t+1,r=O(t,e);return{value:e.slice(n,r-1),endIdx:r}}function A(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 j(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=T(e,r),e[r]!==`=`)return{name:i,value:null,endIdx:r};if(r=T(e,r+1),e[r]===`{`){let t=k(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}if(e[r]===`"`||e[r]===`'`){let t=A(e,r);return{name:i,value:t.value,endIdx:t.endIdx}}return null}function M(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 N(e,t,n){let r=T(e,t+1+n.length),i=null,a=null,o=null,s=[];for(;r<e.length;){r=T(e,r);let t=M(e,r,n);if(t)return{endIdx:t.endIdx,islandProp:i,serverProp:a,keyProp:o,otherProps:s};let c=j(e,r);if(!c)return null;if(r=c.endIdx,c.name===`island`)i=c.value??`{}`;else if(c.name===`server`)a=c.value??`{}`;else if(c.name===`key`)o=c.value;else{let e=c.value===null?`${c.name}: true`:`${c.name}: ${c.value}`;s.push(e)}}return null}function P(e){return e.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`)}function F(e,t,n){let r=new RegExp(String.raw`\b${P(t)}\b`,`g`);for(let t=r.exec(e);t!==null;t=r.exec(e)){let e=t.index;if(!n.some(([t,n])=>e>=t&&e<n))return!0}return!1}function I(e,t){let n=new Set;for(let r of t){let t=L(e,r),a=t.filter(e=>e.islandProp&&!e.serverProp&&i(e.islandProp));if(a.length===0||t.filter(e=>e.islandProp).length!==a.length)continue;let o=a.map(e=>[e.start,e.end]),s=new RegExp(String.raw`^[ \t]*import\s+${P(r)}\s+from\s+['"][^'"]+['"];?`,`m`).exec(e);s&&o.push([s.index,s.index+s[0].length]),F(e,r,o)||n.add(r)}return n}function L(e,t){let n=`<${t}`,r=[],i=0;for(;i<e.length;){let a=V(e,i);if(a!==-1){i=a;continue}if(!U(e,i,n)){i++;continue}let o=N(e,i,t);if(!o){i++;continue}r.push({start:i,end:o.endIdx,islandProp:o.islandProp,serverProp:o.serverProp}),i=o.endIdx}return r}function R(e,t){let n=e;for(let e of t)n.includes(`component: ${e}`)||(n=n.replace(new RegExp(String.raw`^[ \t]*import\s+${P(e)}\s+from\s+['"][^'"]+['"];?\s*\n?`,`m`),``));return n}function z(e,t,n,i,a,o){let s=n?`, framework: "${n}"`:``,c=e.otherProps.length>0?`, props: { ${e.otherProps.join(`, `)} }`:``,l=e.keyProp==null?``:`, key: (${e.keyProp})`,u=o?``:`, component: ${a}`;if(i)return`await __pageRenderIsland({ src: "`+t+`"`+s+u+c+l+`, ssr: true, ssrOnly: true })`;if(e.serverProp){let n=`, server: (${e.serverProp})`,r=e.islandProp?`, island: (${e.islandProp})`:``;return`await __pageRenderIsland({ src: "`+t+`"`+s+u+n+r+c+l+` })`}let d=e.islandProp??``;return`await __pageRenderIsland({ src: "`+t+`"`+s+u+`, ...(`+d+`)`+c+`, ssr: `+r(d)+l+` })`}function B(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 V(e,t){let n=e.slice(t,t+2);if(e[t]==="`")return D(e,t);if(e[t]===`{`&&n===`{/`&&e[t+2]===`*`)return H(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 H(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 U(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 W(e,t,n,r,i,a){let o=`<${t}`,s=``,c=0;for(;c<e.length;){let l=V(e,c);if(l!==-1){s+=e.slice(c,l),c=l;continue}let{text:u,next:d}=G(e,c,{tag:o,componentName:t,srcPath:n,framework:r,autoIsland:i,omitComponent:a});s+=u,c=d}return s}function G(e,t,n){if(!U(e,t,n.tag))return{text:e[t],next:t+1};let r=N(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 a=z(r,n.srcPath,n.framework,n.autoIsland&&!r.islandProp,n.componentName,n.omitComponent&&i(r.islandProp));return{text:B(e,t)?`{${a}}`:a,next:r.endIdx}}function K(e){if(/\bexport\s+default\s+function\b(?!\s*\*)/.test(e))return e.replace(/\bexport\s+default\s+function\b(?!\s*\*)/,`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:r=null}=e,i=[];return{name:`avalon:page-island-transform`,enforce:`pre`,configResolved(e){i=e.resolve?.alias??[]},transform(e,s){let c=h(s,n,r);if(!m(s,t,r)&&!c)return null;let l=o(e);if(l.length===0)return null;let u=l.map(e=>e.localName);if(!x(e,u)&&!S(e,u)&&!C(e,l))return null;let d=w(e,l,s,i);if(d.size===0)return null;let f=I(e,[...d.keys()]),p=`import { renderIsland as __pageRenderIsland } from '@useavalon/avalon';\n${e}`;for(let[e,t]of d)p=W(p,e,t.srcPath,t.framework,t.autoIsland,f.has(e));return p=R(p,f),p.includes(`__pageRenderIsland(`)&&(p=K(p),p=a(p,s)),p.includes(`__pageKeyed(`)&&(p=p.replace(`import { renderIsland as __pageRenderIsland } from '@useavalon/avalon';`,`import { renderIsland as __pageRenderIsland, withListKey as __pageKeyed } from '@useavalon/avalon';`)),{code:p,map:null}}}}
|
|
@@ -8,12 +8,15 @@ export type HydrateFn = (el: HTMLElement, Component: unknown, props: Record<stri
|
|
|
8
8
|
export type UnmountFn = (el: HTMLElement) => void | Promise<void>;
|
|
9
9
|
export interface IntegrationModule {
|
|
10
10
|
hydrate?: HydrateFn;
|
|
11
|
+
mount?: HydrateFn;
|
|
11
12
|
unmount?: UnmountFn;
|
|
12
13
|
preLitHydration?: () => Promise<void>;
|
|
13
14
|
}
|
|
14
15
|
export interface HydrationLoader {
|
|
15
16
|
loadIntegrationModule: (framework: string) => Promise<IntegrationModule>;
|
|
16
17
|
preLitHydration?: () => Promise<void>;
|
|
18
|
+
/** Test seam — production uses dynamic `import(data-src)`. */
|
|
19
|
+
loadComponent?: (src: string) => Promise<Record<string, unknown>>;
|
|
17
20
|
}
|
|
18
21
|
/** Test seam — production uses `virtual:avalon/integration-loader`. */
|
|
19
22
|
export declare function setHydrationLoader(loader: HydrationLoader | null): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{executeCustomDirective as e,hasClientDirective as t}from"./custom-directives.js";const n=new WeakMap;let r=null;export function setHydrationLoader(e){r=e}async function a(){return r||import(`virtual:avalon/integration-loader`)}function o(e,t){let r=n.get(e)??[];r.push(t),n.set(e,r)}function s(e){let t=n.get(e);if(t){for(let e of t)try{e()}catch(e){console.warn(`[avalon] Island cleanup failed:`,e)}n.delete(e)}}function c(e,t){let n=e.default;if(!n){let t=Object.keys(e).filter(e=>e!==`default`);for(let r of t){let t=e[r];if(typeof t==`function`){n=t;break}}n||=e}if(!n)throw Error(`Component ${t} has no default export`);return n}async function l(e,t){if(e.dataset.hydrated)return;let n=e.dataset.src;if(!n){console.warn(`Island missing data-src attribute`);return}try{let r=e.dataset.props?JSON.parse(e.dataset.props):{},i=await a();t===`lit`&&await(i.preLitHydration?.()??i.loadIntegrationModule(`lit`).then(e=>e.preLitHydration?.()));let o=c(await import(n),n),s=await i.loadIntegrationModule(t)
|
|
1
|
+
import{executeCustomDirective as e,hasClientDirective as t}from"./custom-directives.js";const n=new WeakMap;let r=null;export function setHydrationLoader(e){r=e}async function a(){return r||import(`virtual:avalon/integration-loader`)}function o(e,t){let r=n.get(e)??[];r.push(t),n.set(e,r)}function s(e){let t=n.get(e);if(t){for(let e of t)try{e()}catch(e){console.warn(`[avalon] Island cleanup failed:`,e)}n.delete(e)}}function c(e,t){let n=e.default;if(!n){let t=Object.keys(e).filter(e=>e!==`default`);for(let r of t){let t=e[r];if(typeof t==`function`){n=t;break}}n||=e}if(!n)throw Error(`Component ${t} has no default export`);return n}async function l(e,t){if(e.dataset.hydrated)return;let n=e.dataset.src;if(!n){console.warn(`Island missing data-src attribute`);return}try{let r=e.dataset.props?JSON.parse(e.dataset.props):{},i=await a();t===`lit`&&await(i.preLitHydration?.()??i.loadIntegrationModule(`lit`).then(e=>e.preLitHydration?.()));let o=c(i.loadComponent?await i.loadComponent(n):await import(n),n),s=await i.loadIntegrationModule(t),l=e.dataset.renderStrategy===`client-only`,u=l?s.mount:s.hydrate,d=l?`mount`:`hydrate`;if(!u||typeof u!=`function`)throw Error(`Integration ${t} does not export a ${d} function`);await u(e,o,r),e.dataset.hydrated=`true`}catch(r){let i=r instanceof Error?r.message:String(r);console.error(`Hydration error for ${t} island ${n}:`,r),e.dataset.hydrationStatus=`failed`,e.dataset.renderStrategy=`ssr-only`,e.dispatchEvent(new CustomEvent(`hydration-error`,{detail:{framework:t,src:n,error:i,timestamp:Date.now()},bubbles:!0}))}}function u(e,t){try{let n=new IntersectionObserver(r=>{r[0]?.isIntersecting&&(l(e,t),n.disconnect())},{rootMargin:`50px`,threshold:0});n.observe(e.firstElementChild||e),o(e,()=>n.disconnect())}catch(n){console.error(`Failed to setup intersection observer:`,n),l(e,t)}}function d(e,t){let n=[`click`,`touchstart`,`mouseenter`,`focusin`],r=!1,i=e.firstElementChild||e,a=()=>{if(!r){r=!0;for(let e of n)i.removeEventListener(e,a);l(e,t)}};try{for(let e of n)i.addEventListener(e,a,{once:!0,passive:!0});o(e,()=>{r=!0;for(let e of n)i.removeEventListener(e,a)})}catch(n){console.error(`Failed to setup interaction observer:`,n),l(e,t)}}function f(e,t){try{if(`requestIdleCallback`in globalThis){let n=globalThis.requestIdleCallback(()=>{l(e,t)},{timeout:5e3});o(e,()=>globalThis.cancelIdleCallback?.(n));return}let n=setTimeout(()=>{l(e,t)},200);o(e,()=>clearTimeout(n))}catch(n){console.error(`Failed to setup idle callback:`,n),l(e,t)}}function p(e,t,n){try{let r=globalThis.matchMedia(n);if(r.matches){l(e,t);return}let i=n=>{n.matches&&(l(e,t),r.removeEventListener(`change`,i))};r.addEventListener(`change`,i),o(e,()=>r.removeEventListener(`change`,i))}catch(r){console.error(`Failed to setup media query:`,n,r),l(e,t)}}function m(n){let r=n.dataset.framework,i=n.dataset.condition||`on:client`,a=n.dataset.renderStrategy;if(r&&a!==`ssr-only`&&!n.dataset.hydrated){if(i===`on:client`){l(n,r);return}if(i===`on:visible`){u(n,r);return}if(i===`on:interaction`){d(n,r);return}if(i===`on:idle`){f(n,r);return}if(i.startsWith(`media:`)){p(n,r,i.slice(6));return}if(n.dataset.customDirective||n.dataset.directiveScript||t(i)){e(n,i,()=>{l(n,r)})||(console.warn(`[avalon] Unknown hydration condition: "${i}". Hydrating immediately.`),l(n,r));return}l(n,r)}}export function scanAndHydrate(e=document){let t=e.querySelectorAll(`[data-framework]`);for(let e of t)try{m(e)}catch(e){console.error(`Error processing island:`,e)}}export async function disposeIslands(e=document){let t=[...e.querySelectorAll(`[data-framework]`)],n=t.length>0?await a():null;for(let e of t){s(e);let t=e.dataset.framework;if(t&&n)try{let r=await n.loadIntegrationModule(t);typeof r.unmount==`function`&&await r.unmount(e)}catch(e){console.warn(`[avalon] Failed to unmount ${t} island:`,e)}delete e.dataset.hydrated,delete e.dataset.hydrationStatus,delete e.dataset.hydrationError,delete e.dataset.litHydrated}}
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* devtools. This mirrors the pattern Astro, Lit, and Elder.js use for
|
|
15
15
|
* island-style hydration anchors.
|
|
16
16
|
*
|
|
17
|
+
* Client-only islands have no SSR children, so `display: contents` would
|
|
18
|
+
* leave a host with no box. Those use `display: block` so deferred
|
|
19
|
+
* conditions (`on:visible`, `on:interaction`) can attach to the host.
|
|
20
|
+
*
|
|
17
21
|
* The CSS is intentionally low-specificity (single-element selectors)
|
|
18
22
|
* so any user CSS that sets a different `display` value naturally wins.
|
|
19
23
|
*/
|
|
@@ -33,4 +37,4 @@
|
|
|
33
37
|
*/
|
|
34
38
|
export declare function injectFrameworkBaseCSS(html: string): string;
|
|
35
39
|
/** Exposed for tests */
|
|
36
|
-
export declare const __FRAMEWORK_BASE_CSS = "avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}";
|
|
40
|
+
export declare const __FRAMEWORK_BASE_CSS = "avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}avalon-island[data-render-strategy=client-only]{display:block}";
|
|
@@ -1 +1 @@
|
|
|
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
|
+
const e=`avalon-island,avalon-page,avalon-page-content,avalon-server-island,[data-server-island-wrapper]{display:contents}avalon-island[data-render-strategy=client-only]{display:block}`,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;
|
|
@@ -32,6 +32,8 @@ export interface IslandProps {
|
|
|
32
32
|
children?: IslandChildren;
|
|
33
33
|
/** Whether to render server-side (default: true unless condition is 'on:client') */
|
|
34
34
|
ssr?: boolean;
|
|
35
|
+
/** Skip component SSR and mount on the client. Forces `ssr: false`. */
|
|
36
|
+
clientOnly?: boolean;
|
|
35
37
|
/** Framework hint for client hydration */
|
|
36
38
|
framework?: FrameworkId;
|
|
37
39
|
/** Force SSR-only rendering without hydration */
|
|
@@ -67,7 +69,7 @@ export declare function withListKey(key: unknown, vnode: JSX.Element): JSX.Eleme
|
|
|
67
69
|
* Uses custom elements instead of div wrappers for cleaner, more semantic markup.
|
|
68
70
|
* Supports intelligent rendering strategy detection to skip hydration for SSR-only components.
|
|
69
71
|
*/
|
|
70
|
-
export default function Island({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, hydrationData, id, persist, }: IslandProps): JSX.Element;
|
|
72
|
+
export default function Island({ src, condition, conditionArg, props, children, ssr: ssrProp, clientOnly, framework, ssrOnly, renderOptions, hydrationData, id, persist, }: IslandProps): JSX.Element;
|
|
71
73
|
/**
|
|
72
74
|
* Universal renderIsland function – auto-detects framework and handles SSR + hydration.
|
|
73
75
|
*
|
|
@@ -84,5 +86,5 @@ export default function Island({ src, condition, conditionArg, props, children,
|
|
|
84
86
|
* Error isolation: If SSR fails, returns an error placeholder instead of throwing,
|
|
85
87
|
* allowing the page to continue rendering other islands.
|
|
86
88
|
*/
|
|
87
|
-
export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, server, island, key, id, persist, }: IslandProps): Promise<JSX.Element>;
|
|
89
|
+
export declare function renderIsland({ src, condition, conditionArg, props, children, ssr: ssrProp, clientOnly, framework, ssrOnly, renderOptions, component: preloadedComponent, server, island, key, id, persist, }: IslandProps): Promise<JSX.Element>;
|
|
88
90
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getIslandBundlePath as e}from"../build/island-manifest.js";import{shellH as t,shellFragment as n}from"../render/shell-engine.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,r){return r==null?e:t(n(),{key:r},e)}export function withListKey(e,t){return S(t,e)}let w=0;function T(e,t){return t||`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}-${w++}`}function E(e,t){if(e===void 0)return;if(e===!0)return t;let n=e.trim();return n.length>0?n:void 0}function D(){return globalThis.__avalonHydrationMode===void 0?globalThis.__viteDevServer?!1:typeof __AVALON_PER_ISLAND__<`u`?__AVALON_PER_ISLAND__:!l():globalThis.__avalonHydrationMode===`per-island`}function O(r,i){if(!D()||i.shouldSkipHydration)return r;let a=e(i.src);i.condition===`on:client`&&v(a);let o=m(i.condition),s=o?h(i.condition):void 0,c=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});return t(n(),null,r,t(`div`,{dangerouslySetInnerHTML:{__html:c},"data-island-script":``}))}function k(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 A(t,n,r,i,a){let
|
|
1
|
+
import{getIslandBundlePath as e}from"../build/island-manifest.js";import{shellH as t,shellFragment as n}from"../render/shell-engine.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,r){return r==null?e:t(n(),{key:r},e)}export function withListKey(e,t){return S(t,e)}let w=0;function T(e,t){return t||`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}-${w++}`}function E(e,t){if(e===void 0)return;if(e===!0)return t;let n=e.trim();return n.length>0?n:void 0}function D(){return globalThis.__avalonHydrationMode===void 0?globalThis.__viteDevServer?!1:typeof __AVALON_PER_ISLAND__<`u`?__AVALON_PER_ISLAND__:!l():globalThis.__avalonHydrationMode===`per-island`}function O(r,i){if(!D()||i.shouldSkipHydration)return r;let a=e(i.src);i.condition===`on:client`&&v(a);let o=m(i.condition),s=o?h(i.condition):void 0,c=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});return t(n(),null,r,t(`div`,{dangerouslySetInnerHTML:{__html:c},"data-island-script":``}))}function k(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 A(e){return JSON.stringify(e)}function j(t,n,r,i,a,o=`hydrate`){let s={"data-condition":n,"data-src":e(t),"data-props":A(r),"data-render-strategy":o,...k(i)};if(m(n)){s[`data-custom-directive`]=n;let e=h(n);e&&(s[`data-directive-script`]=e)}return a&&(s[`data-condition-arg`]=a),s}function M(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 N(e){let t=/<style[^>]*>([\s\S]*?)<\/style>/i.exec(e);return t?t[1].trim():null}function P(e,t,n,r){if(e.css&&b(e.css,t,n,e.scopeId),e.head){let i=e.head.trim(),a=M(i);if(a===`style`){let a=N(i);a&&(s(`${r} Extracting CSS from head <style> tag`),b(a,t,n,e.scopeId));return}x(e.head,t,n,a)}}function F(e){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,conditionArg:c,props:l,hydrationData:u,children:d,persist:f}=e,p={id:n,"data-framework":r},m=E(f,a);m&&(p[`data-router-persist`]=m);let h=i?{"data-render-strategy":`ssr-only`}:j(a,o,l,u,c);r===`lit`&&s(`🔍 [Island Component] ${a} - Lit hydration data:`,{hydrationDataKeys:Object.keys(u),metadata:u.metadata});let g={...p,...h},_;return _=typeof d==`string`?t(`avalon-island`,{...g,dangerouslySetInnerHTML:{__html:d}}):t(`avalon-island`,g,d),O(_,{islandId:n,src:a,condition:o,conditionArg:c,props:l,framework:r,shouldSkipHydration:i})}function I(n){let{islandId:r,detectedFramework:i,shouldSkipHydration:a,src:o,condition:s,props:c,hydrationData:l,conditionArg:u,persist:d,clientOnly:f}=n,p=E(d,o);if(a)return t(`avalon-island`,{id:r,"data-render-strategy":`ssr-only`,"data-framework":i,...p?{"data-router-persist":p}:{}});let g={id:r,"data-condition":s,"data-src":e(o),"data-props":A(c),"data-render-strategy":f?`client-only`:`hydrate`,"data-framework":i,...k(l)};if(p&&(g[`data-router-persist`]=p),m(s)){g[`data-custom-directive`]=s;let e=h(s);e&&(g[`data-directive-script`]=e)}return u&&(g[`data-condition-arg`]=u),O(t(`avalon-island`,g),{islandId:r,src:o,condition:s,conditionArg:u,props:c,framework:i,shouldSkipHydration:a})}export default function L({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:a,clientOnly:o,framework:l,ssrOnly:u=!1,renderOptions:d={},hydrationData:f={},id:p,persist:m}){let h=o===!0?!1:a??t!==`on:client`,_=T(e,p),v=u||!!d.forceSSROnly,y=l||g(e),b=i!=null&&i!==``;return s(`🔍 [Island Component] ${e}`,{ssr:h,ssrOnly:u,hasChildren:b,framework:l,condition:t}),h&&b?F({islandId:_,detectedFramework:y,shouldSkipHydration:v,src:e,condition:t,conditionArg:n,props:r,hydrationData:f,children:i,persist:m}):(h&&!b&&v&&c(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),I({islandId:_,detectedFramework:y,shouldSkipHydration:v,src:e,condition:t,props:r,hydrationData:f,conditionArg:n,persist:m,clientOnly:!h}))}function R(n,r){let i=r instanceof Error?r.message:String(r);return o(`🚨 Island SSR failed for ${n}:`,r),r instanceof Error&&r.stack&&o(`Stack trace:`,r.stack),t(`avalon-island`,{id:T(n),"data-src":e(n),"data-ssr-error":i,"data-render-strategy":`client-only`})}async function z({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u,component:d,id:f,persist:p}){let m=`🏝️ [${e}]`;if(!a||i)return L({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:s,ssrOnly:c,renderOptions:u,id:f,persist:p});let h;try{h=await _(s)}catch(i){return o(`${m} Failed to load ${s} integration:`,i),L({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u,id:f,persist:p})}try{let i=await h.render({component:d??null,props:r,src:e,condition:t,ssrOnly:c,viteServer:globalThis.__viteDevServer,isDev:l()});return P(i,e,s,m),L({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,id:f,persist:p})}catch(i){return o(`${m} Fast path SSR failed:`,i),L({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u,id:f,persist:p})}}async function B(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 V(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 H(e){let{src:t,condition:n,props:r,ssrOnly:i,renderOptions:a,logPrefix:o,preloadedComponent:s,id:c,persist:u}=e,d=await V(t),f=d,p=await(await U(d,o)).render({component:s??null,props:r,src:t,condition:n,ssrOnly:i,viteServer:globalThis.__viteDevServer,isDev:l()});return P(p,t,d,o),L({src:t,condition:n,props:r,children:p.html,ssr:!0,framework:f,ssrOnly:i,renderOptions:a,hydrationData:i?void 0:p.hydrationData,id:c,persist:u})}async function U(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,clientOnly:m,framework:h,ssrOnly:_=!1,renderOptions:v={},component:y,server:b,island:x,key:C,id:w,persist:T}){let E=m===!0?!1:p??s!==`on:client`,D=l()?performance.now():0,O=`🏝️ [${o}]`;try{if(b){let s=i(o);r(s,o);let c=h||g(o),l=a(s,d,b,x,x?e(o):void 0,o,c);return S(t(n(),null,t(`div`,{dangerouslySetInnerHTML:{__html:l},"data-server-island-wrapper":``})),C)}return _&&!E&&m!==!0&&(E=!0),S(h?await z({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:E,framework:h,ssrOnly:_,renderOptions:v,component:y,id:w,persist:T}):await W({src:o,condition:s,conditionArg:c,props:d,children:f,ssr:E,ssrOnly:_,renderOptions:v,logPrefix:O,component:y,id:w,persist:T}),C)}catch(e){return S(R(o,e),C)}finally{l()&&u(o,performance.now()-D)}}async function W(e){let{src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,ssrOnly:l,renderOptions:u,logPrefix:d,component:f,id:p,persist:m}=e;if(s(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:c,ssrOnly:l,hasChildren:!!a,condition:n}),await B(t,l,u,d))return G({src:t,condition:n,props:i,children:a,ssr:c,renderOptions:u,logPrefix:d,id:p,persist:m});if(!c||a)return L({src:t,condition:n,conditionArg:r,props:i,children:a,ssr:c,renderOptions:u,id:p,persist:m});try{return await H({src:t,condition:n,props:i,ssrOnly:l,renderOptions:u,logPrefix:d,preloadedComponent:f,id:p,persist:m})}catch(e){let a=await V(t);return o(`${d} Framework rendering failed:`,e),L({src:t,condition:n,conditionArg:r,props:i,ssr:!1,framework:a,renderOptions:u,id:p,persist:m})}}function G(e){let{src:t,condition:n,props:r,children:i,ssr:a,renderOptions:s,logPrefix:c,id:l,persist:u}=e;return a&&!i?f({src:t,condition:n,props:r,renderOptions:s}).catch(e=>(o(`${c} SSR failed for SSR-only component:`,e),L({src:t,condition:n,props:r,ssr:!1,ssrOnly:!0,renderOptions:s,id:l,persist:u}))):L({src:t,condition:n,props:r,children:i,ssr:a,ssrOnly:!0,renderOptions:s,id:l,persist:u})}
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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;`,`var i;if(!m.__hydrateIsland&&!m.__mountIsland&&m.loadIntegrationModule){i=await m.loadIntegrationModule(${JSON.stringify(r)})}`,`var fn=e.dataset.renderStrategy==="client-only"?(m.__mountIsland||(i&&i.mount)):(m.__hydrateIsland||(i&&i.hydrate));`,`if(!fn)throw new Error("Integration does not export "+(e.dataset.renderStrategy==="client-only"?"mount":"hydrate"));`,`await fn(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();`}
|
|
@@ -23,6 +23,8 @@ export interface IslandProps {
|
|
|
23
23
|
children?: JSX.Element | JSX.Element[] | string;
|
|
24
24
|
/** Whether to render server-side (default: true unless condition is 'on:client') */
|
|
25
25
|
ssr?: boolean;
|
|
26
|
+
/** Skip component SSR and mount on the client. Forces `ssr: false`. */
|
|
27
|
+
clientOnly?: boolean;
|
|
26
28
|
/** Framework hint for client hydration */
|
|
27
29
|
framework?: "solid" | "vue" | "preact" | "react" | "svelte" | "lit" | "qwik";
|
|
28
30
|
/** Force SSR-only rendering without hydration */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{existsSync as e,statSync as t}from"node:fs";import{resolve as n}from"node:path";export const DEFAULT_TREESHAKE_CONFIG={annotations:!0,moduleSideEffects:!0,propertyReadSideEffects:!1,unknownGlobalSideEffects:!1,manualPureFunctions:[]};export function mergeTreeshakeConfig(e,t){return{annotations:t.annotations??e.annotations,moduleSideEffects:t.moduleSideEffects??e.moduleSideEffects,propertyReadSideEffects:t.propertyReadSideEffects??e.propertyReadSideEffects,unknownGlobalSideEffects:t.unknownGlobalSideEffects??e.unknownGlobalSideEffects,manualPureFunctions:[...e.manualPureFunctions,...t.manualPureFunctions??[]]}}const a={solid:`@useavalon/solid/client`,preact:`@useavalon/preact/client`,react:`@useavalon/react/client`,vue:`@useavalon/vue/client`,svelte:`@useavalon/svelte/client`};export function generateWrapperCode(e,t){let n=JSON.stringify(e);if(t===`qwik`)return[`export * from ${n};`,`export { _hW } from "@builder.io/qwik";`].join(`
|
|
2
|
-
`);let r=a[t],i=[`import __C from ${n};`,`var Component = __C;`,`export { Component as default, Component };`,`if(typeof globalThis<"u")globalThis.__avalonIsland=Component;`];return r&&i.push(`export { hydrate as __hydrateIsland } from ${JSON.stringify(r)};`),i.join(`
|
|
3
|
-
`)}export function generateIntegrationLoaderForFramework(e){let t=a[e];return t?`export { hydrate } from ${JSON.stringify(t)};`:`export {};`}async function c(e,t){let n=[],{resolve:r}=await import(`node:path`),{existsSync:i}=await import(`node:fs`),{pathToFileURL:a}=await import(`node:url`),o={solid:{pkg:`vite-plugin-solid`,esmEntry:`dist/esm/index.mjs`},vue:{pkg:`@vitejs/plugin-vue`,esmEntry:`dist/index.mjs`},svelte:{pkg:`@sveltejs/vite-plugin-svelte`,esmEntry:`src/index.js`}}[e];if(!o)return n;try{let s=null,c=t;for(let e=0;e<8;e++){let e=r(c,`node_modules`,o.pkg,o.esmEntry);if(i(e)){s=e;break}for(let e of[`solid`,`vue`,`svelte`,`preact`,`react`,`lit`]){let t=r(c,`packages`,`integrations`,e,`node_modules`,o.pkg,o.esmEntry);if(i(t)){s=t;break}}if(s)break;c=r(c,`..`)}if(!s)throw Error(`Cannot find package '${o.pkg}'`);let l=await import(a(s).href);switch(e){case`solid`:n.push((l.default??l)({ssr:!1,hot:!1}));break;case`vue`:n.push((l.default??l)());break;case`svelte`:n.push((l.svelte??l.default)());break}}catch(t){console.warn(` ⚠ Could not load ${e} plugin: ${t instanceof Error?t.message:t}`)}return n}async function l(e){let{createRequire:t}=await import(`node:module`),{existsSync:n}=await import(`node:fs`),r=t(`${e}/package.json`),i=e=>{try{let t=r.resolve(e);return t.endsWith(`.js`)&&n(t.replace(/\.js$/,`.mjs`))?t.replace(/\.js$/,`.mjs`):t}catch{return null}},a={preact:i(`preact`),"preact/hooks":i(`preact/hooks`),"preact/compat":i(`preact/compat`),"preact/compat/client":i(`preact/compat/client`),"preact/compat/server":i(`preact/compat/server`),"preact/jsx-runtime":i(`preact/jsx-runtime`),react:i(`preact/compat`),"react/jsx-runtime":i(`preact/jsx-runtime`),"react/jsx-dev-runtime":i(`preact/jsx-runtime`),"react-dom":i(`preact/compat`),"react-dom/client":i(`preact/compat/client`),"react-dom/server":i(`preact/compat/server`)};return{name:`avalon:isolated-preact-compat`,enforce:`pre`,resolveId(e){return a[e]??null}}}export async function buildIsolatedIslands(a,u,d,f,p,m){if(d.size===0)return[];let{build:h}=await import(`vite`),g=new Set([`qwik`,`lit`,`preact`,`react`]),_=[...d.values()].filter(e=>!g.has(e.framework));console.log(`🏝️ Building ${_.length} island(s) in isolation (${d.size-_.length} skipped)...`);let v=performance.now(),y=m?.treeshake?mergeTreeshakeConfig(DEFAULT_TREESHAKE_CONFIG,m.treeshake):DEFAULT_TREESHAKE_CONFIG,b=[];for(let[,r]of d){let{filePath:i,bundleKey:d,framework:m}=r,g=`islands/${d}.js`;if(m===`qwik`||m===`lit`||m===`preact`||m===`react`){b.push({island:g,success:!0,skipped:!0});continue}let _=generateWrapperCode(i,m),v=`\0isolated-island-entry`,x=`\0virtual:avalon/integration-loader`;try{let r=await c(m,a),i=await l(a),o=performance.now(),S=await h({configFile:!1,root:a,logLevel:`silent`,plugins:[{name:`avalon:isolated-island-virtual`,resolveId(e){return e===v?e:e===`virtual:avalon/integration-loader`||e===x?x:null},load(e){return e===v?_:e===x?generateIntegrationLoaderForFramework(m):null}},i,...r],build:{write:!0,outDir:n(a,u),emptyOutDir:!1,minify:`oxc`,target:`es2022`,rollupOptions:{input:v,output:{format:`es`,entryFileNames:g},preserveEntrySignatures:`exports-only`,treeshake:y}},resolve:{alias:f},define:{...p,__DEV__:`false`,__PROD__:`true`,"process.env.NODE_ENV":`"production"`}}),C=performance.now()-o,w=n(a,u,g),T=e(w)?t(w).size:0,E=[],D=Array.isArray(S)?S:[S];for(let e of D)if(e&&`output`in e)for(let t of e.output)t.type===`chunk`&&t.fileName!==g&&E.push(`/${t.fileName}`);b.push({island:g,success:!0,size:T,elapsedMs:C,deps:E}),console.log(` ✅ ${d} (${m}): ${(T/1024).toFixed(1)} KiB`),C>2e3&&console.warn(` ⚠ ${d} (${m}): build took ${C.toFixed(0)}ms (>2s)`)}catch(e){let t=e instanceof Error?e.message:String(e);console.error(` ❌ ${d} (${m}): ${t}`),b.push({island:g,success:!1,error:t})}}let x=((performance.now()-v)/1e3).toFixed(1),S=b.filter(e=>e.success&&!e.skipped).length,C=b.filter(e=>e.skipped).length,w=b.filter(e=>!e.success).length,T=`🏝️ Done in ${x}s: ${S} built`;C&&(T+=`, ${C} skipped`),w&&(T+=`, ${w} failed`),console.log(T);let E={};for(let e of b)e.success&&e.deps&&e.deps.length>0&&(E[`/${e.island}`]=e.deps);if(Object.keys(E).length>0){let e=n(a,u,`island-deps.json`),{writeFileSync:t}=await import(`node:fs`);t(e,JSON.stringify(E))}return b}
|
|
2
|
+
`);let r=a[t],i=[`import __C from ${n};`,`var Component = __C;`,`export { Component as default, Component };`,`if(typeof globalThis<"u")globalThis.__avalonIsland=Component;`];return r&&i.push(`export { hydrate as __hydrateIsland, mount as __mountIsland } from ${JSON.stringify(r)};`),i.join(`
|
|
3
|
+
`)}export function generateIntegrationLoaderForFramework(e){let t=a[e];return t?`export { hydrate, mount } from ${JSON.stringify(t)};`:`export {};`}async function c(e,t){let n=[],{resolve:r}=await import(`node:path`),{existsSync:i}=await import(`node:fs`),{pathToFileURL:a}=await import(`node:url`),o={solid:{pkg:`vite-plugin-solid`,esmEntry:`dist/esm/index.mjs`},vue:{pkg:`@vitejs/plugin-vue`,esmEntry:`dist/index.mjs`},svelte:{pkg:`@sveltejs/vite-plugin-svelte`,esmEntry:`src/index.js`}}[e];if(!o)return n;try{let s=null,c=t;for(let e=0;e<8;e++){let e=r(c,`node_modules`,o.pkg,o.esmEntry);if(i(e)){s=e;break}for(let e of[`solid`,`vue`,`svelte`,`preact`,`react`,`lit`]){let t=r(c,`packages`,`integrations`,e,`node_modules`,o.pkg,o.esmEntry);if(i(t)){s=t;break}}if(s)break;c=r(c,`..`)}if(!s)throw Error(`Cannot find package '${o.pkg}'`);let l=await import(a(s).href);switch(e){case`solid`:n.push((l.default??l)({ssr:!1,hot:!1}));break;case`vue`:n.push((l.default??l)());break;case`svelte`:n.push((l.svelte??l.default)());break}}catch(t){console.warn(` ⚠ Could not load ${e} plugin: ${t instanceof Error?t.message:t}`)}return n}async function l(e){let{createRequire:t}=await import(`node:module`),{existsSync:n}=await import(`node:fs`),r=t(`${e}/package.json`),i=e=>{try{let t=r.resolve(e);return t.endsWith(`.js`)&&n(t.replace(/\.js$/,`.mjs`))?t.replace(/\.js$/,`.mjs`):t}catch{return null}},a={preact:i(`preact`),"preact/hooks":i(`preact/hooks`),"preact/compat":i(`preact/compat`),"preact/compat/client":i(`preact/compat/client`),"preact/compat/server":i(`preact/compat/server`),"preact/jsx-runtime":i(`preact/jsx-runtime`),react:i(`preact/compat`),"react/jsx-runtime":i(`preact/jsx-runtime`),"react/jsx-dev-runtime":i(`preact/jsx-runtime`),"react-dom":i(`preact/compat`),"react-dom/client":i(`preact/compat/client`),"react-dom/server":i(`preact/compat/server`)};return{name:`avalon:isolated-preact-compat`,enforce:`pre`,resolveId(e){return a[e]??null}}}export async function buildIsolatedIslands(a,u,d,f,p,m){if(d.size===0)return[];let{build:h}=await import(`vite`),g=new Set([`qwik`,`lit`,`preact`,`react`]),_=[...d.values()].filter(e=>!g.has(e.framework));console.log(`🏝️ Building ${_.length} island(s) in isolation (${d.size-_.length} skipped)...`);let v=performance.now(),y=m?.treeshake?mergeTreeshakeConfig(DEFAULT_TREESHAKE_CONFIG,m.treeshake):DEFAULT_TREESHAKE_CONFIG,b=[];for(let[,r]of d){let{filePath:i,bundleKey:d,framework:m}=r,g=`islands/${d}.js`;if(m===`qwik`||m===`lit`||m===`preact`||m===`react`){b.push({island:g,success:!0,skipped:!0});continue}let _=generateWrapperCode(i,m),v=`\0isolated-island-entry`,x=`\0virtual:avalon/integration-loader`;try{let r=await c(m,a),i=await l(a),o=performance.now(),S=await h({configFile:!1,root:a,logLevel:`silent`,plugins:[{name:`avalon:isolated-island-virtual`,resolveId(e){return e===v?e:e===`virtual:avalon/integration-loader`||e===x?x:null},load(e){return e===v?_:e===x?generateIntegrationLoaderForFramework(m):null}},i,...r],build:{write:!0,outDir:n(a,u),emptyOutDir:!1,minify:`oxc`,target:`es2022`,rollupOptions:{input:v,output:{format:`es`,entryFileNames:g},preserveEntrySignatures:`exports-only`,treeshake:y}},resolve:{alias:f},define:{...p,__DEV__:`false`,__PROD__:`true`,"process.env.NODE_ENV":`"production"`}}),C=performance.now()-o,w=n(a,u,g),T=e(w)?t(w).size:0,E=[],D=Array.isArray(S)?S:[S];for(let e of D)if(e&&`output`in e)for(let t of e.output)t.type===`chunk`&&t.fileName!==g&&E.push(`/${t.fileName}`);b.push({island:g,success:!0,size:T,elapsedMs:C,deps:E}),console.log(` ✅ ${d} (${m}): ${(T/1024).toFixed(1)} KiB`),C>2e3&&console.warn(` ⚠ ${d} (${m}): build took ${C.toFixed(0)}ms (>2s)`)}catch(e){let t=e instanceof Error?e.message:String(e);console.error(` ❌ ${d} (${m}): ${t}`),b.push({island:g,success:!1,error:t})}}let x=((performance.now()-v)/1e3).toFixed(1),S=b.filter(e=>e.success&&!e.skipped).length,C=b.filter(e=>e.skipped).length,w=b.filter(e=>!e.success).length,T=`🏝️ Done in ${x}s: ${S} built`;C&&(T+=`, ${C} skipped`),w&&(T+=`, ${w} failed`),console.log(T);let E={};for(let e of b)e.success&&e.deps&&e.deps.length>0&&(E[`/${e.island}`]=e.deps);if(Object.keys(E).length>0){let e=n(a,u,`island-deps.json`),{writeFileSync:t}=await import(`node:fs`);t(e,JSON.stringify(E))}return b}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { ServerIslandProp } from "../server-islands/types.ts";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Type augmentation for the `island` prop on island components.
|
|
5
3
|
*
|
|
@@ -70,4 +68,17 @@ export type IslandDirective = {
|
|
|
70
68
|
* ```
|
|
71
69
|
*/
|
|
72
70
|
conditionArg?: string;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Skip server rendering of this component. Avalon emits an empty
|
|
74
|
+
* `<avalon-island>` placeholder and mounts the component in the browser.
|
|
75
|
+
* Props are still serialized. Combines with `condition` for when to mount.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* <Chart island={{ clientOnly: true }} userId={user.id} />
|
|
80
|
+
* <Chart island={{ clientOnly: true, condition: 'on:idle' }} />
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
clientOnly?: boolean;
|
|
73
84
|
};
|
|
@@ -69,6 +69,11 @@ declare module "virtual:avalon/integration-loader" {
|
|
|
69
69
|
component: unknown,
|
|
70
70
|
props: Record<string, unknown>,
|
|
71
71
|
) => void | Promise<void>;
|
|
72
|
+
mount?: (
|
|
73
|
+
el: HTMLElement,
|
|
74
|
+
component: unknown,
|
|
75
|
+
props: Record<string, unknown>,
|
|
76
|
+
) => void | Promise<void>;
|
|
72
77
|
unmount?: (el: HTMLElement) => void | Promise<void>;
|
|
73
78
|
preLitHydration?: () => Promise<void>;
|
|
74
79
|
}>;
|
|
@@ -20,7 +20,7 @@ export default islandManifest;
|
|
|
20
20
|
`)}function Le(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;`,String.raw` if (/\/index-[^/]+\.css$/.test(href)) return true;`,` return false;`,` })`,` .map(attr => '<link rel="stylesheet" href="' + attr.href + '">')`,String.raw` .join('\n');`,` const jsPreloads = (clientAssets?.js ?? [])`,` .map(attr => '<link rel="modulepreload" href="' + attr.href + '">')`,String.raw` .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>')) {`,String.raw` html = html.replace('</head>', cssLinks + '\n' + jsPreloads + '\n</head>');`,` }`,` if (html.includes('</body>')) {`,String.raw` html = html.replace('</body>', entryScript + '\n</body>');`,` }`,` return html;`,`}`,``,`export { clientAssets };`,`export default { injectAssets, clientAssets };`,``].join(`
|
|
21
21
|
`)}function Re(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});`)}let i=ye(e.core);return[`// Auto-generated by Avalon — do not edit`,...t.includes(`lit`)?[`import "${O}";`]:[],`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,``,...i,``,`// 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 || avalonConfig.clientRouter) ? "entry-client" : "per-island";`,`globalThis.__avalonIsDev = avalonConfig.isDev;`,``,`export default createNitroRenderer({`,` avalonConfig,`,` isDev: avalonConfig.isDev,`,` resolvePageRoute: async (pathname) => {`,` const match = loadPage(pathname);`,` if (!match || !('default' in match.module)) return null;`,` return { filePath: '[virtual:' + pathname + ']', pattern: pathname, params: match.params };`,` },`,` loadPageModule: async (filePath) => {`,String.raw` const m = filePath.match(/^\[virtual:(.+)\]$/);`,` const pathname = m ? m[1] : filePath;`,` const match = loadPage(pathname);`,` if (match) return match.module;`,` return { default: () => null, metadata: { title: 'Avalon' } };`,` },`,` wrapWithLayouts: (pageHtml, pageModule, context) =>`,` wrapWithLayouts(pageHtml, pageModule, context, injectAssets),`,`});`,``].join(`
|
|
22
22
|
`)}async function ze(e,t){let{readdir:n}=await import(`node:fs/promises`),{relative:r,join:i}=await import(`node:path`),a=[];for(let{dir:o}of e){let e;try{e=await n(o,{withFileTypes:!0})}catch{continue}for(let n of e){if(!n.isFile()||!n.name.endsWith(`.css`))continue;let e=r(t,i(o,n.name)).replaceAll(`\\`,`/`);a.push(e.startsWith(`/`)?e:`/${e}`)}}return a}function Be(e){if(e.length===0)return[];let t=[`// Layout CSS (auto-discovered)`],n=0;for(let r of e)r.includes(`.module.`)?(t.push(`import _lcss${n} from '${r}';`),n++):t.push(`import '${r}';`);return t}async function Ve(e,t){let{getAllLayoutDirs:n}=await import(`./module-discovery.js`),r=process.cwd(),i=await ze(await n(e.layoutsDir,e.modules,r),r),a=globalThis.__avalonConfig?.isDev??e.isDev,o=[`// Auto-generated by Avalon — do not edit`,`// Island hydration runtime`,...d(a,!!(globalThis.__avalonConfig?.clientRouter??e.clientRouter))];a&&o.push(`import '${VIRTUAL_MODULE_IDS.DEV_CSS_HMR}';`),o.push(``);let s=t.globalCSS??[];for(let e of s){let t=e.startsWith(`/`)?e:`/${e}`;o.push(`// Global CSS`,`import '${t}';`)}return s.length>0&&o.push(``),o.push(...Be(i),``),o.join(`
|
|
23
|
-
`)}function He(){return[`// --- Inlined Solid adapter (production) ---`,`// Eliminates a separate chunk + network request for the Solid client adapter
|
|
23
|
+
`)}function He(){return[`// --- Inlined Solid adapter (production) ---`,`// Eliminates a separate chunk + network request for the Solid client adapter.`,`function _ensureHydrationContext() {`,` if (!globalThis._$HY) {`,` globalThis._$HY = { events: [], completed: new WeakSet(), r: {}, fe() {} };`,` }`,`}`,``,`async function _solidHydrate(container, Component, props) {`,` if (!container) throw new Error("Container element is required for hydration");`,` if (!Component || typeof Component !== "function") {`,` throw new Error("Invalid Solid component: expected function, got " + typeof Component);`,` }`,` var el = container;`,` var renderId = el.dataset.solidRenderId || el.dataset.renderId;`,` var { hydrate: solidHydrate, createComponent } = await import("solid-js/web");`,` _ensureHydrationContext();`,` var dispose = solidHydrate(function() { return createComponent(Component, props || {}); }, el, { renderId: renderId || "" });`,` if (typeof dispose === "function") _solidDisposers.set(container, dispose);`,`}`,``,`async function _solidMount(container, Component, props) {`,` if (!container) throw new Error("Container element is required for mount");`,` if (!Component || typeof Component !== "function") {`,` throw new Error("Invalid Solid component: expected function, got " + typeof Component);`,` }`,` var { render: solidRender, createComponent } = await import("solid-js/web");`,` var dispose = solidRender(function() { return createComponent(Component, props || {}); }, container);`,` if (typeof dispose === "function") _solidDisposers.set(container, dispose);`,`}`,``,`function _solidUnmount(container) {`,` var dispose = _solidDisposers.get(container);`,` if (dispose) { dispose(); _solidDisposers.delete(container); }`,`}`,``,`var _solidDisposers = new WeakMap();`,`var _solidModule = { hydrate: _solidHydrate, mount: _solidMount, unmount: _solidUnmount };`,``]}function Ue(e,t,n){let r=[];for(let i of e){let e=t[i];e&&(i===`solid`&&!n?r.push(` case "solid":`,` return _solidModule;`):i===`react`?r.push(` case "react":`):r.push(` case "${i}":`,` return import("${e}");`))}return e.includes(`react`)&&!e.includes(`preact`)&&r.push(` return import("${t.react}");`),r}function We(e,t){let n=[];for(let r of e){let e=t[r];e&&n.push(` case "${r}":`,` return import("${e}").then(m => m.${r}Adapter);`)}return n}export function generateIntegrationLoaderModule(e){let t=(e.integrations??[]).map(e=>typeof e==`string`?e:e.name),n={preact:`@useavalon/preact/client`,react:be(e.core),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`,``];return a&&!o&&s.push(...He()),s.push(`// --- loadIntegrationModule ---`,`export async function loadIntegrationModule(framework) {`,` switch (framework) {`,...Ue(t,n,o),` default:`," throw new Error(`Unknown or unconfigured framework: ${framework}`);",` }`,`}`,``,`// --- Lit hydration pre-load (only if Lit is configured) ---`),i?s.push(`export async function preLitHydration() {`,` await import("@useavalon/lit/client");`,`}`):s.push(`export async function preLitHydration() {}`),s.push(``,`// --- HMR adapter loader ---`,`export async function loadHMRAdapter(framework) {`,` switch (framework) {`,...We(t,r),` default: return null;`,` }`,`}`),s.join(`
|
|
24
24
|
`)}export function getViteDevServer(){return globalThis.__viteDevServer}export function getAvalonConfig(){return globalThis.__avalonConfig}export function isDevelopmentMode(){return globalThis.__avalonConfig?.isDev??!0}const B=`<!--AVALON_STREAM_BOUNDARY-->`;let V=null,H=null;async function U(e,t){if(t===`react`){let e=await import(`react`),t=await import(`react-dom/server`),n=e.default??e,r=t.default??t;return{h:e.createElement??n.createElement,renderToString:t.renderToString??r.renderToString}}let n=await e.ssrLoadModule(`preact`),r=await e.ssrLoadModule(`preact-render-to-string`);return{h:n.h,renderToString:r.render??r.default}}function W(e){return e.split(`/`).pop()?.replace(/\.[^.]+$/,``)||``}async function G(e,t,n){let r=[];for(let n of t){let t=await e.ssrLoadModule(n);r.push({file:n,module:t})}for(let r of t){let t=await w(e,r);n.push(...t)}return r}async function K(e,t,n,r){let i=[],a=[];for(let o of e){let e=o.module.default;if(!(!e||typeof e!=`function`))try{let s=e({...t,children:n(`div`,null,`test`)}),c=r(s instanceof Promise?await s:s);c.trim().startsWith(`<html`)||c.includes(`<!DOCTYPE`)?i.push(o):a.push(o)}catch{a.push(o)}}return{shellLayouts:i,wrapperLayouts:a}}async function Ge(e,t,n,r){try{let i=e({...t,children:n(`div`,{dangerouslySetInnerHTML:{__html:B}})});return r(i instanceof Promise?await i:i)}catch{return null}}function Ke(e,t){let n=e;if(t.length>0){let e=`<style data-avalon-ssr-css>${t.join(`
|
|
25
25
|
`)}</style>`;n.includes(`</head>`)?n=n.replace(`</head>`,`${e}\n</head>`):n+=e}n.trim().toLowerCase().startsWith(`<!doctype`)||(n=`<!DOCTYPE html>\n${n}`);let r=m(!0);r&&n.includes(`</head>`)&&(n=n.replace(`</head>`,`${r}\n</head>`));let i=h(!0);return i&&n.includes(`</head>`)&&(n=n.replace(`</head>`,`${i}\n</head>`)),g(n)}async function q(e,t,n){try{let n=typeof e==`function`?e():e;return t(n instanceof Promise?await n:n)}catch(e){return console.error(`${n} Error rendering page component:`,e),`<div>Error rendering page</div>`}}async function J(e,t,n,r,i,a){let o=e;for(let{module:e}of t){let t=e.default;if(!(!t||typeof t!=`function`))try{let e=t({...n,children:r(`div`,{dangerouslySetInnerHTML:{__html:o}})});o=i(e instanceof Promise?await e:e)}catch(e){console.error(`${a} Error rendering wrapper layout:`,e)}}return o}async function qe(e,t,n,r,i){if(t.length===0)return e;let{module:a}=t.at(-1),o=a.default;if(!o||typeof o!=`function`)return e;try{let t=o({...n,children:r(`div`,{dangerouslySetInnerHTML:{__html:e}})});return i(t instanceof Promise?await t:t)}catch(t){return console.error(`[SSR] Error rendering shell layout:`,t),e}}function Je(e){if(e.includes(`/src/client/main.js`)||e.includes(`/@vite/client`))return e;let t=e.lastIndexOf(`</body>`);return t===-1?e:e.slice(0,t)+`
|
|
26
26
|
<script type="module" src="/@vite/client"><\/script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/avalon",
|
|
3
|
-
"version": "0.3.2-canary.
|
|
3
|
+
"version": "0.3.2-canary.17",
|
|
4
4
|
"description": "Multi-framework islands architecture for the modern web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
},
|
|
169
169
|
"dependencies": {
|
|
170
170
|
"@mdx-js/rollup": "^3.0.0",
|
|
171
|
-
"@useavalon/core": "^0.3.2-canary.
|
|
171
|
+
"@useavalon/core": "^0.3.2-canary.17",
|
|
172
172
|
"h3": "^2.0.1-rc.16",
|
|
173
173
|
"marked": "^17.0.4",
|
|
174
174
|
"oxc-parser": "^0.120.0",
|