@useavalon/avalon 0.1.47 → 0.1.48
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/mod.d.ts +8 -9
- package/dist/mod.js +1 -1
- package/dist/src/client/components.d.ts +8 -16
- package/dist/src/client/components.js +1 -1
- package/dist/src/client/custom-directives.d.ts +25 -0
- package/dist/src/client/custom-directives.js +1 -0
- package/dist/src/client/main.js +3 -3
- package/dist/src/components/IslandErrorBoundary.d.ts +48 -9
- package/dist/src/components/IslandErrorBoundary.js +1 -1
- package/dist/src/components/LayoutErrorBoundary.d.ts +74 -9
- package/dist/src/components/LayoutErrorBoundary.js +1 -1
- package/dist/src/islands/builtin-directives.d.ts +15 -0
- package/dist/src/islands/builtin-directives.js +1 -0
- package/dist/src/islands/hydration-directives.d.ts +89 -0
- package/dist/src/islands/hydration-directives.js +1 -0
- package/dist/src/islands/island.d.ts +5 -3
- package/dist/src/islands/island.js +1 -1
- package/dist/src/islands/types.d.ts +4 -2
- package/dist/src/layout-system.d.ts +0 -13
- package/dist/src/layout-system.js +1 -1
- package/dist/src/nitro/config.d.ts +3 -3
- package/dist/src/nitro/renderer.d.ts +14 -1
- package/dist/src/nitro/renderer.js +6 -6
- package/dist/src/persistence/island-state-serializer.d.ts +19 -0
- package/dist/src/persistence/island-state-serializer.js +1 -0
- package/dist/src/persistence/use-persistent-state.d.ts +31 -0
- package/dist/src/persistence/use-persistent-state.js +1 -0
- package/dist/src/prerender/index.d.ts +1 -1
- package/dist/src/prerender/prerender.d.ts +1 -1
- package/dist/src/prerender/prerender.js +1 -1
- package/dist/src/schemas/layout.d.ts +1 -1
- package/dist/src/schemas/layout.js +1 -1
- package/dist/src/types/island-prop.d.ts +4 -2
- package/dist/src/types/layout.d.ts +0 -8
- package/dist/src/types/layout.js +1 -1
- package/dist/src/vite-plugin/nitro-integration.d.ts +3 -3
- package/dist/src/vite-plugin/nitro-integration.js +14 -14
- package/package.json +2 -2
- package/dist/src/components/LayoutDataErrorBoundary.d.ts +0 -34
- package/dist/src/components/LayoutDataErrorBoundary.js +0 -1
- package/dist/src/components/PersistentIsland.d.ts +0 -36
- package/dist/src/components/PersistentIsland.js +0 -1
- package/dist/src/components/StreamingErrorBoundary.d.ts +0 -42
- package/dist/src/components/StreamingErrorBoundary.js +0 -1
- package/dist/src/components/StreamingLayout.d.ts +0 -83
- package/dist/src/components/StreamingLayout.js +0 -29
- package/dist/src/core/islands/island-persistence.d.ts +0 -74
- package/dist/src/core/islands/island-persistence.js +0 -1
- package/dist/src/core/islands/island-state-serializer.d.ts +0 -53
- package/dist/src/core/islands/island-state-serializer.js +0 -1
- package/dist/src/core/islands/persistent-island-context.d.ts +0 -36
- package/dist/src/core/islands/persistent-island-context.js +0 -1
- package/dist/src/core/islands/use-persistent-state.d.ts +0 -17
- package/dist/src/core/islands/use-persistent-state.js +0 -1
|
@@ -14,25 +14,12 @@ export { LayoutComposer } from './core/layout/layout-composer.ts';
|
|
|
14
14
|
export type { LayoutConfig } from './schemas/layout.ts';
|
|
15
15
|
export { EnhancedLayoutResolver, createEnhancedLayoutResolver, EnhancedLayoutResolverUtils, } from './core/layout/enhanced-layout-resolver.ts';
|
|
16
16
|
export type { EnhancedLayoutResolverOptions } from './core/layout/enhanced-layout-resolver.ts';
|
|
17
|
-
export { IslandPersistence, defaultIslandPersistence } from './core/islands/island-persistence.ts';
|
|
18
|
-
export { IslandStateSerializer } from './core/islands/island-state-serializer.ts';
|
|
19
|
-
export { createPersistentIslandContext, usePersistentIslandContext, PersistentIslandProvider, } from './core/islands/persistent-island-context.tsx';
|
|
20
|
-
export { PersistentIsland } from './components/PersistentIsland.tsx';
|
|
21
|
-
export type { IslandState, PersistentIslandProps, PersistentIslandContext } from './schemas/layout.ts';
|
|
22
|
-
export { LayoutErrorBoundary } from './components/LayoutErrorBoundary.tsx';
|
|
23
|
-
export { LayoutDataErrorBoundary } from './components/LayoutDataErrorBoundary.tsx';
|
|
24
|
-
export { IslandErrorBoundary, withIslandErrorBoundary } from './components/IslandErrorBoundary.tsx';
|
|
25
|
-
export { StreamingErrorBoundary, withStreamingErrorBoundary } from './components/StreamingErrorBoundary.tsx';
|
|
26
|
-
export type { LayoutErrorInfo, LayoutErrorBoundaryProps, ErrorRecoveryStrategy } from './schemas/layout.ts';
|
|
27
|
-
export { StreamingLayout, StreamingSuspense, withStreaming, useStreamingState } from './components/StreamingLayout.tsx';
|
|
28
|
-
export type { StreamingLayoutProps, StreamingComponent } from './schemas/layout.ts';
|
|
29
17
|
export { LayoutCacheManager } from './core/layout/layout-cache-manager.ts';
|
|
30
18
|
export type { CacheEntry, CacheStats, CacheConfig, } from './core/layout/layout-cache-manager.ts';
|
|
31
19
|
export type { LayoutContext, LayoutData, LayoutHandler, LayoutProps, LayoutLoader, ResolvedLayout, LayoutCache, EnhancedLayoutContext, LayoutMatcherFunction, LayoutErrorHandler, LayoutRetryFunction, LayoutFallbackRenderer, IslandStateSaver, IslandStateLoader, IslandStateClearer, StreamingReadyCheck, } from './schemas/layout.ts';
|
|
32
20
|
export type { ILayoutDiscovery, ILayoutMatcher, ILayoutComposer, IIslandPersistence, ILayoutErrorRecovery, ILayoutStreaming, IEnhancedLayoutResolver, ILayoutComponent, IPersistentIslandComponent, ILayoutErrorBoundaryComponent, IStreamingLayoutComponent, LayoutModule, PageModule, LayoutResolutionContext, LayoutPerformanceMetrics, LayoutDebugInfo, LayoutEventType, LayoutEventData, LayoutEventHandler, ILayoutEventEmitter, } from './types/layout.ts';
|
|
33
21
|
export { LayoutContextSchema, LayoutDataSchema, LayoutRouteSchema, LayoutHandlerSchema, LayoutPropsSchema, LayoutDiscoveryOptionsSchema, RouteInfoSchema, LayoutRuleSchema, LayoutConfigSchema, IslandStateSchema, PersistentIslandPropsSchema, PersistentIslandContextSchema, LayoutErrorInfoSchema, LayoutErrorBoundaryPropsSchema, ErrorRecoveryStrategySchema, StreamingLayoutPropsSchema, StreamingComponentSchema, ResolvedLayoutSchema, LayoutCacheSchema, EnhancedLayoutContextSchema, } from './schemas/layout.ts';
|
|
34
22
|
export { EnhancedLayoutResolver as LayoutSystem } from './core/layout/enhanced-layout-resolver.ts';
|
|
35
|
-
export { defaultIslandPersistence as defaultPersistence } from './core/islands/island-persistence.ts';
|
|
36
23
|
export { createEnhancedLayoutResolver as createLayoutSystem } from './core/layout/enhanced-layout-resolver.ts';
|
|
37
24
|
/**
|
|
38
25
|
* Layout System Version Information
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{LayoutDiscovery}from"./core/layout/layout-discovery.js";export{LayoutDataLoader,LayoutDataLoadingError,loadSingleLayoutData,mergeLayoutData,getParentLayoutData,defaultLayoutDataLoader}from"./core/layout/layout-data-loader.js";export{LayoutMatcher as LayoutMatcherClass,BuiltInLayoutRules}from"./core/layout/layout-matcher.js";export{LayoutComposer}from"./core/layout/layout-composer.js";export{EnhancedLayoutResolver,createEnhancedLayoutResolver,EnhancedLayoutResolverUtils}from"./core/layout/enhanced-layout-resolver.js";export{
|
|
1
|
+
export{LayoutDiscovery}from"./core/layout/layout-discovery.js";export{LayoutDataLoader,LayoutDataLoadingError,loadSingleLayoutData,mergeLayoutData,getParentLayoutData,defaultLayoutDataLoader}from"./core/layout/layout-data-loader.js";export{LayoutMatcher as LayoutMatcherClass,BuiltInLayoutRules}from"./core/layout/layout-matcher.js";export{LayoutComposer}from"./core/layout/layout-composer.js";export{EnhancedLayoutResolver,createEnhancedLayoutResolver,EnhancedLayoutResolverUtils}from"./core/layout/enhanced-layout-resolver.js";export{LayoutCacheManager}from"./core/layout/layout-cache-manager.js";export{LayoutContextSchema,LayoutDataSchema,LayoutRouteSchema,LayoutHandlerSchema,LayoutPropsSchema,LayoutDiscoveryOptionsSchema,RouteInfoSchema,LayoutRuleSchema,LayoutConfigSchema,IslandStateSchema,PersistentIslandPropsSchema,PersistentIslandContextSchema,LayoutErrorInfoSchema,LayoutErrorBoundaryPropsSchema,ErrorRecoveryStrategySchema,StreamingLayoutPropsSchema,StreamingComponentSchema,ResolvedLayoutSchema,LayoutCacheSchema,EnhancedLayoutContextSchema}from"./schemas/layout.js";export{EnhancedLayoutResolver as LayoutSystem}from"./core/layout/enhanced-layout-resolver.js";export{createEnhancedLayoutResolver as createLayoutSystem}from"./core/layout/enhanced-layout-resolver.js";export const LAYOUT_SYSTEM_VERSION=`1.0.0`;export const LAYOUT_SYSTEM_FEATURES={DISCOVERY:!0,DATA_LOADING:!0,CONDITIONAL_RENDERING:!0,COMPOSITION_CONTROL:!0,PERSISTENT_ISLANDS:!0,ERROR_BOUNDARIES:!0,STREAMING:!0,CACHING:!0,PERFORMANCE_MONITORING:!0,DEBUG_UTILITIES:!0};export const LAYOUT_SYSTEM_DEFAULTS={DISCOVERY:{baseDirectory:`src/pages`,filePattern:`_layout.tsx`,excludeDirectories:[`node_modules`,`.git`,`dist`],enableWatching:!1,developmentMode:!1},CACHING:{enabled:!0,ttl:3e5,maxSize:100,cleanupInterval:6e4},STREAMING:{enabled:!0,priority:`medium`,timeout:5e3},ERROR_BOUNDARIES:{enabled:!0,maxRetries:3,fallbackStrategy:`component`},PERFORMANCE:{monitoring:!0,thresholds:{discoveryTime:100,dataLoadingTime:500,renderingTime:200,totalTime:1e3}}};
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* - `/*.html`, `/*.css`: Short cache with revalidation
|
|
20
20
|
* - `/favicon.ico`: Medium cache (1 day)
|
|
21
21
|
*/
|
|
22
|
-
import type { ResolvedAvalonConfig } from
|
|
22
|
+
import type { ResolvedAvalonConfig } from "../vite-plugin/types.ts";
|
|
23
23
|
/**
|
|
24
24
|
* Cache configuration options for route rules
|
|
25
25
|
*/
|
|
@@ -229,7 +229,7 @@ export interface NitroConfigOutput {
|
|
|
229
229
|
/** Static assets configuration */
|
|
230
230
|
staticAssets?: StaticAssetsConfig;
|
|
231
231
|
/** Prerender configuration for SSG */
|
|
232
|
-
prerender?: AvalonNitroConfig[
|
|
232
|
+
prerender?: AvalonNitroConfig["prerender"];
|
|
233
233
|
}
|
|
234
234
|
/**
|
|
235
235
|
* Avalon-specific runtime configuration stored in Nitro's runtimeConfig
|
|
@@ -264,7 +264,7 @@ export declare function resolvePresetName(preset: string): string;
|
|
|
264
264
|
/**
|
|
265
265
|
* Default Nitro configuration values
|
|
266
266
|
*/
|
|
267
|
-
export declare const DEFAULT_NITRO_CONFIG: Required<Pick<AvalonNitroConfig,
|
|
267
|
+
export declare const DEFAULT_NITRO_CONFIG: Required<Pick<AvalonNitroConfig, "preset" | "serverDir" | "streaming">>;
|
|
268
268
|
/**
|
|
269
269
|
* Creates a Nitro configuration from Avalon plugin config and Nitro-specific options
|
|
270
270
|
*
|
|
@@ -68,6 +68,17 @@ export interface RenderHandlerOptions {
|
|
|
68
68
|
loadPageModule?: (filePath: string) => Promise<PageModule>;
|
|
69
69
|
/** Custom layout resolver */
|
|
70
70
|
resolveLayouts?: (routePath: string, config: AvalonRuntimeConfig) => Promise<string[]>;
|
|
71
|
+
/**
|
|
72
|
+
* Wrap rendered page HTML with layout components.
|
|
73
|
+
*
|
|
74
|
+
* Called after the page component is rendered to HTML. The function
|
|
75
|
+
* receives the page HTML, page module, and render context, and should
|
|
76
|
+
* return the full HTML document string (including `<!DOCTYPE html>`).
|
|
77
|
+
*
|
|
78
|
+
* When provided, the renderer skips its default HTML shell generation
|
|
79
|
+
* and uses the returned string directly.
|
|
80
|
+
*/
|
|
81
|
+
wrapWithLayouts?: (pageHtml: string, pageModule: PageModule, context: NitroRenderContext) => Promise<string> | string;
|
|
71
82
|
/**
|
|
72
83
|
* Enable custom error pages (404.tsx, 500.tsx, _error.tsx)
|
|
73
84
|
* When enabled, the renderer will look for custom error pages in the pages directory
|
|
@@ -197,7 +208,7 @@ export declare function processHydrationRequirements(html: string, isDev: boolea
|
|
|
197
208
|
* @param options - Render options
|
|
198
209
|
* @returns SSR render result
|
|
199
210
|
*/
|
|
200
|
-
export declare function renderPage(pageModule: PageModule, context: NitroRenderContext, options?: SSRRenderOptions): Promise<SSRRenderResult>;
|
|
211
|
+
export declare function renderPage(pageModule: PageModule, context: NitroRenderContext, options?: SSRRenderOptions, wrapWithLayouts?: RenderHandlerOptions['wrapWithLayouts']): Promise<SSRRenderResult>;
|
|
201
212
|
/**
|
|
202
213
|
* Extended streaming options with additional callbacks
|
|
203
214
|
*/
|
|
@@ -280,6 +291,8 @@ export interface NitroCatchAllOptions {
|
|
|
280
291
|
loadPageModule: (filePath: string) => Promise<PageModule>;
|
|
281
292
|
/** Optional layout resolver */
|
|
282
293
|
resolveLayouts?: (routePath: string, config: AvalonRuntimeConfig) => Promise<string[]>;
|
|
294
|
+
/** Wrap rendered page HTML with layout components (same as RenderHandlerOptions) */
|
|
295
|
+
wrapWithLayouts?: RenderHandlerOptions['wrapWithLayouts'];
|
|
283
296
|
/**
|
|
284
297
|
* Enable custom error pages (404.tsx, 500.tsx, _error.tsx)
|
|
285
298
|
* When enabled, the renderer will look for custom error pages in the pages directory
|
|
@@ -118,19 +118,19 @@ import{getRequestURL as e}from"h3";import{createNotFoundError as t,isHttpError a
|
|
|
118
118
|
</div>
|
|
119
119
|
</body>
|
|
120
120
|
</html>`}function _(e){return{400:`Bad Request`,401:`Unauthorized`,403:`Forbidden`,404:`Page Not Found`,405:`Method Not Allowed`,500:`Internal Server Error`,502:`Bad Gateway`,503:`Service Unavailable`}[e]||`Error`}function v(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`).replaceAll(`'`,`'`)}export function extractIslandMarkers(e){let t=[],n=/<[^>]*data-framework="([^"]+)"[^>]*>/g,r;for(;(r=n.exec(e))!==null;){let e=r[0],n=r[1],i=/data-src="([^"]+)"/.exec(e),a=i?i[1]:``,o=/data-props="([^"]*)"/.exec(e),s=o?o[1]:void 0,c=/data-hydrate="([^"]+)"/.exec(e),l=c?c[1]:void 0;t.push({framework:n,src:a,props:s,hydrate:l})}return t}export function ensureHydrationMarkers(e,t){let n=e;return t.framework&&!n.includes(`data-framework=`)&&(n=n.replace(/>/,` data-framework="${t.framework}">`)),t.src&&!n.includes(`data-src=`)&&(n=n.replace(/>/,` data-src="${t.src}">`)),t.props!==void 0&&!n.includes(`data-props=`)&&(n=n.replace(/>/,` data-props="${t.props}">`)),n}export function injectHydrationScript(e,t,n={}){if(!(e.includes(`data-framework=`)||e.includes(`data-src=`))&&!n.forceInject||[`/src/client/main.js`,`/dist/client.js`,`client/main.js`].some(t=>e.includes(t)))return e;let r=n.scriptPath||(t?`/src/client/main.js`:`/dist/client.js`),i=[];i.push(`<script type="module" src="${r}"><\/script>`),n.additionalScripts&&i.push(...n.additionalScripts);let a=i.join(`
|
|
121
|
-
`);return e.includes(`</body>`)?e.replace(`</body>`,`${a}\n</body>`):e+a}export function validateHydrationMarkers(e){let t=extractIslandMarkers(e),n=e.match(/data-framework="[^"]+"/g)||[],r=e.match(/data-src="[^"]+"/g)||[],i=e.match(/data-props="[^"]*"/g)||[],a=e.includes(`/src/client/main.js`)||e.includes(`/dist/client.js`)||e.includes(`client/main.js`),o=t.every(e=>e.framework&&e.src),s=t.length===0||o&&a;return{hasFrameworkAttr:n.length>0,hasSrcAttr:r.length>0,hasPropsAttr:i.length>0,islandCount:n.length,islands:t,hasClientScript:a,isValid:s}}export function processHydrationRequirements(e,t){let n=injectHydrationScript(e,t);return{html:n,validation:validateHydrationMarkers(n)}}export async function renderPage(e,t,n={}){try{let
|
|
121
|
+
`);return e.includes(`</body>`)?e.replace(`</body>`,`${a}\n</body>`):e+a}export function validateHydrationMarkers(e){let t=extractIslandMarkers(e),n=e.match(/data-framework="[^"]+"/g)||[],r=e.match(/data-src="[^"]+"/g)||[],i=e.match(/data-props="[^"]*"/g)||[],a=e.includes(`/src/client/main.js`)||e.includes(`/dist/client.js`)||e.includes(`client/main.js`),o=t.every(e=>e.framework&&e.src),s=t.length===0||o&&a;return{hasFrameworkAttr:n.length>0,hasSrcAttr:r.length>0,hasPropsAttr:i.length>0,islandCount:n.length,islands:t,hasClientScript:a,isValid:s}}export function processHydrationRequirements(e,t){let n=injectHydrationScript(e,t);return{html:n,validation:validateHydrationMarkers(n)}}export async function renderPage(e,t,n={},r){try{let i={};return e.getServerSideProps&&(i=await e.getServerSideProps(t)),{html:await y(e,i,t,n,r),statusCode:200,headers:{"Content-Type":`text/html; charset=utf-8`}}}catch(e){throw console.error(`[SSR Error]`,e),n.onError&&e instanceof Error&&n.onError(e),e}}async function y(e,t,n,r,i){let a=e.default,o=e.metadata||{},l=process.env.NODE_ENV!==`production`,u;try{let e=a(t);u=e instanceof Promise?await e:e}catch(e){console.error(`[renderer] Error calling page component:`,e),u=s(`div`,null,`Error rendering page`)}let d;try{d=c(u)}catch(e){console.error(`[renderer] Error in preactRenderToString:`,e),d=`<div>Error rendering page</div>`}if(i)return await i(d,e,n);let f=l?`
|
|
122
122
|
<script type="module" src="/src/client/main.js"><\/script>`:``;return`<!DOCTYPE html>
|
|
123
123
|
<html lang="en">
|
|
124
124
|
<head>
|
|
125
125
|
<meta charset="utf-8">
|
|
126
126
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
127
|
-
<title>${v(String(
|
|
128
|
-
${
|
|
127
|
+
<title>${v(String(o.title||`Avalon App`))}</title>
|
|
128
|
+
${o.description?`<meta name="description" content="${v(String(o.description))}">`:``}
|
|
129
129
|
</head>
|
|
130
130
|
<body>
|
|
131
131
|
<div id="app">
|
|
132
|
-
${
|
|
133
|
-
</div>${
|
|
132
|
+
${d}
|
|
133
|
+
</div>${f}
|
|
134
134
|
</body>
|
|
135
135
|
</html>`}export async function renderPageStream(e,t,n={}){let r=new TextEncoder,i=null,a={shellSent:!1,contentSent:!1,closed:!1,error:null},o=n.shellReadyTimeout,s=n.allReadyTimeout,l=null,u=null,d=()=>{l&&=(clearTimeout(l),null),u&&=(clearTimeout(u),null)};function f(e,t,n,r,i,a,o){if(n.error=e,a(),console.error(`[Streaming Error]`,{message:e.message,stack:e.stack,shellSent:n.shellSent,isShellError:t,timestamp:new Date().toISOString()}),t&&o.onShellError&&o.onShellError(e),o.onError&&o.onError(e),!n.closed&&r){if(n.shellSent){let t=C(e);r.enqueue(i.encode(t));let n=S();r.enqueue(i.encode(n))}else{let t=h(e,500);r.enqueue(i.encode(t))}n.closed=!0,r.close()}}async function p(o){i=o;let p={};e.getServerSideProps&&(p=await e.getServerSideProps(t));let m=b(e.metadata||{},t);if(a.closed||(o.enqueue(r.encode(m)),a.shellSent=!0,l&&=(clearTimeout(l),null),n.onShellReady&&n.onShellReady()),s&&s>0&&(u=setTimeout(()=>{!a.contentSent&&!a.closed&&f(Error(`All ready timeout after ${s}ms`),!1,a,i,r,d,n)},s)),!a.closed){let t=e.default;if(t&&typeof t==`function`){let n=t(p);if(n&&typeof n.then==`function`)try{let e=c(await n);o.enqueue(r.encode(` <div id="app">${e}</div>\n`))}catch(t){console.error(`[streaming] Async component error:`,t);let n=x(e,p);o.enqueue(r.encode(n))}else{let t=x(e,p);o.enqueue(r.encode(t))}}else{let t=x(e,p);o.enqueue(r.encode(t))}a.contentSent=!0}if(!a.closed){let e=S();o.enqueue(r.encode(e))}d(),n.onAllReady&&!a.closed&&n.onAllReady(),a.closed||(a.closed=!0,o.close())}return new ReadableStream({async start(e){i=e,o&&o>0&&(l=setTimeout(()=>{!a.shellSent&&!a.closed&&f(Error(`Shell ready timeout after ${o}ms`),!0,a,i,r,d,n)},o));try{await p(e)}catch(e){f(e instanceof Error?e:Error(String(e)),!a.shellSent,a,i,r,d,n)}},cancel(){if(d(),!a.closed&&i){a.closed=!0;try{i.close()}catch{}}}})}function b(e,t){return`<!DOCTYPE html>
|
|
136
136
|
<html lang="en">
|
|
@@ -181,4 +181,4 @@ import{getRequestURL as e}from"h3";import{createNotFoundError as t,isHttpError a
|
|
|
181
181
|
</details>
|
|
182
182
|
`:``}
|
|
183
183
|
</div>
|
|
184
|
-
`}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function w(e,t,n){return async()=>(e.value??=await r({baseDir:t,devMode:n}),e.value)}function T(e,t,n){return async(r,i)=>e?a(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:n,isDev:r=!1,enableCustomErrorPages:a=!0}=e,s={isDev:r,avalonConfig:n,loadPageModule:e.loadPageModule,pagesDir:n.pagesDir};a&&o(s).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let c=w({value:null},n.srcDir||`src`,r),d=T(a,s,r);return async function(a){let o=getRequestURL(a).pathname;try{let s=await i(a,await c(),{devMode:r});if(s)return r&&console.log(`[renderer] Middleware terminated request for ${o}`),s;let u=a.context.route,f=null;if(f=u||(e.resolvePageRoute?await e.resolvePageRoute(o,n.pagesDir):await E(o,n.pagesDir)),!f)return d(t(`Page not found: ${o}`),a);let m=e.loadPageModule?await e.loadPageModule(f.filePath):await D(f.filePath),h=createRenderContext(a,a.context.params||f.params);if(e.resolveLayouts&&(h.layoutContext={layouts:await e.resolveLayouts(o,n)}),n.streaming){let e=await renderPageStream(m,h,{onShellReady:()=>{setResponseHeader(a,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let
|
|
184
|
+
`}export function createStreamingResponse(e,t={}){let n=new Headers({"Content-Type":`text/html; charset=utf-8`,"Transfer-Encoding":`chunked`,...t.headers});return new Response(e,{status:t.status||200,headers:n})}function w(e,t,n){return async()=>(e.value??=await r({baseDir:t,devMode:n}),e.value)}function T(e,t,n){return async(r,i)=>e?a(r,i,t):createErrorResponse(r,n)}export function createNitroRenderer(e){let{avalonConfig:n,isDev:r=!1,enableCustomErrorPages:a=!0}=e,s={isDev:r,avalonConfig:n,loadPageModule:e.loadPageModule,pagesDir:n.pagesDir};a&&o(s).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let c=w({value:null},n.srcDir||`src`,r),d=T(a,s,r);return async function(a){let o=getRequestURL(a).pathname;try{let s=await i(a,await c(),{devMode:r});if(s)return r&&console.log(`[renderer] Middleware terminated request for ${o}`),s;let u=a.context.route,f=null;if(f=u||(e.resolvePageRoute?await e.resolvePageRoute(o,n.pagesDir):await E(o,n.pagesDir)),!f)return d(t(`Page not found: ${o}`),a);let m=e.loadPageModule?await e.loadPageModule(f.filePath):await D(f.filePath),h=createRenderContext(a,a.context.params||f.params);if(e.resolveLayouts&&(h.layoutContext={layouts:await e.resolveLayouts(o,n)}),n.streaming){let e=await renderPageStream(m,h,{onShellReady:()=>{setResponseHeader(a,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(m,h,{},e.wrapWithLayouts),n=injectHydrationScript(t.html,r);return new Response(n,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Renderer Error]`,e),d(e instanceof Error?e:Error(String(e)),a)}}}async function E(e,t){return e===`/`||e===``?{filePath:`src/pages/index.tsx`,pattern:`/`,params:{}}:{filePath:`src/pages/${e.replace(/^\//,``).replace(/\/$/,``)}.tsx`,pattern:e,params:{}}}async function D(e){return{default:()=>null,metadata:{title:`Avalon Page`}}}export function createNitroCatchAllRenderer(e){let{avalonConfig:n,isDev:r=!1,loadPageModule:a,resolveLayouts:s,enableCustomErrorPages:c=!0}=e,d={isDev:r,avalonConfig:n,loadPageModule:a,pagesDir:n.pagesDir};c&&o(d).catch(e=>{console.warn(`[renderer] Failed to discover error pages:`,e)});let f=w({value:null},n.srcDir||`src`,r),m=T(c,d,r);return async function(o){let c=getRequestURL(o).pathname;try{let u=await i(o,await f(),{devMode:r});if(u)return r&&console.log(`[renderer] Middleware terminated request for ${c}`),u;let d=o.context.params||{},h=d.slug||c.replace(/^\//,``)||`index`,g=`${n.pagesDir}/${h}.tsx`,_;try{_=await a(g)}catch(e){try{_=await a(`${n.pagesDir}/${h}/index.tsx`)}catch(i){return r&&(console.debug(`[renderer] Page not found: ${g}`,e),console.debug(`[renderer] Index fallback not found: ${n.pagesDir}/${h}/index.tsx`,i)),m(t(`Page not found: ${c}`),o)}}let v=createRenderContext(o,d);if(s&&(v.layoutContext={layouts:await s(c,n)}),n.streaming){let e=await renderPageStream(_,v,{onShellReady:()=>{setResponseHeader(o,`Content-Type`,`text/html; charset=utf-8`)}});return new Response(e,{headers:{"Content-Type":`text/html; charset=utf-8`}})}else{let t=await renderPage(_,v,{},e.wrapWithLayouts),n=injectHydrationScript(t.html,r);return new Response(n,{status:t.statusCode,headers:t.headers})}}catch(e){return console.error(`[Nitro Catch-All Renderer Error]`,e),m(e instanceof Error?e:Error(String(e)),o)}}}export{clearMiddlewareCache as clearRendererMiddlewareCache}from"../middleware/index.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IslandState } from '../schemas/layout.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Serializes and deserializes island state for browser storage.
|
|
4
|
+
*
|
|
5
|
+
* Handles complex types that `JSON.stringify` drops: Date, RegExp, Map, and Set.
|
|
6
|
+
* Functions are silently stripped.
|
|
7
|
+
*/
|
|
8
|
+
export declare class IslandStateSerializer {
|
|
9
|
+
static serialize(state: IslandState): string;
|
|
10
|
+
static deserialize(serializedState: string): IslandState;
|
|
11
|
+
private static transformForSerialization;
|
|
12
|
+
private static reviver;
|
|
13
|
+
static validate(state: IslandState): {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
};
|
|
17
|
+
static clone(state: IslandState): IslandState;
|
|
18
|
+
static equals(state1: IslandState, state2: IslandState): boolean;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class IslandStateSerializer{static serialize(t){try{let n=IslandStateSerializer.transformForSerialization(t);return JSON.stringify(n)}catch(e){throw Error(`State serialization failed: ${e instanceof Error?e.message:String(e)}`)}}static deserialize(t){try{return JSON.parse(t,IslandStateSerializer.reviver)}catch(e){throw Error(`State deserialization failed: ${e instanceof Error?e.message:String(e)}`)}}static transformForSerialization(t){if(t==null)return null;if(t instanceof Date)return{__type:`Date`,__value:t.toISOString()};if(t instanceof RegExp)return{__type:`RegExp`,__value:{source:t.source,flags:t.flags}};if(t instanceof Map)return{__type:`Map`,__value:Array.from(t.entries()).map(([t,n])=>[IslandStateSerializer.transformForSerialization(t),IslandStateSerializer.transformForSerialization(n)])};if(t instanceof Set)return{__type:`Set`,__value:Array.from(t.values()).map(t=>IslandStateSerializer.transformForSerialization(t))};if(typeof t==`function`)return null;if(Array.isArray(t))return t.map(t=>IslandStateSerializer.transformForSerialization(t));if(typeof t==`object`){let n={},r=t;for(let t in r)Object.hasOwn(r,t)&&(n[t]=IslandStateSerializer.transformForSerialization(r[t]));return n}return t}static reviver(e,t){if(t&&typeof t==`object`){let e=t;if(e.__type&&e.__value!==void 0)switch(e.__type){case`Date`:return new Date(e.__value);case`RegExp`:{let t=e.__value;return new RegExp(t.source,t.flags)}case`Map`:return new Map(e.__value);case`Set`:return new Set(e.__value);default:return e.__value}}return t}static validate(e){let t=[];try{let n=this.serialize(e),r=new Blob([n]).size;r>5*1024*1024&&t.push(`Serialized state size (${Math.round(r/1024)}KB) exceeds 5MB limit`),this.deserialize(n)}catch(e){t.push(e instanceof Error?e.message:String(e))}return{valid:t.length===0,errors:t}}static clone(e){try{return this.deserialize(this.serialize(e))}catch{return{...e}}}static equals(e,t){try{return this.serialize(e)===this.serialize(t)}catch{return!1}}}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useState` that survives page navigations.
|
|
3
|
+
*
|
|
4
|
+
* State is serialized to sessionStorage (or localStorage) via
|
|
5
|
+
* {@link IslandStateSerializer}, so Date, Map, Set, and RegExp are preserved.
|
|
6
|
+
* On the server or when storage is unavailable, falls back to in-memory state.
|
|
7
|
+
*
|
|
8
|
+
* @param id - Unique key for this piece of state.
|
|
9
|
+
* @param initialValue - Default value when nothing is stored yet.
|
|
10
|
+
* @param options - Optional `{ storage: 'session' | 'local' }`. Defaults to `'session'`.
|
|
11
|
+
* @returns `[value, setValue, clearValue]` — same shape as `useState` plus a clear function.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { usePersistentState } from '@useavalon/avalon';
|
|
16
|
+
*
|
|
17
|
+
* function Counter() {
|
|
18
|
+
* const [count, setCount, clearCount] = usePersistentState('my-counter', 0);
|
|
19
|
+
* return (
|
|
20
|
+
* <div>
|
|
21
|
+
* <p>{count}</p>
|
|
22
|
+
* <button onClick={() => setCount(c => c + 1)}>+1</button>
|
|
23
|
+
* <button onClick={clearCount}>Reset</button>
|
|
24
|
+
* </div>
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function usePersistentState<T>(id: string, initialValue: T, options?: {
|
|
30
|
+
storage?: 'session' | 'local';
|
|
31
|
+
}): [T, (value: T | ((prev: T) => T)) => void, () => void];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useState as e,useEffect as t,useCallback as n}from"preact/hooks";import{IslandStateSerializer as r}from"./island-state-serializer.js";export function usePersistentState(i,a,o){let s=o?.storage??`session`,c=`avalon-island:${i}`,[l,u]=e(()=>{if(globalThis.window===void 0)return a;try{let e=(s===`local`?localStorage:sessionStorage).getItem(c);return e===null?a:r.deserialize(e).v??a}catch{return a}});return t(()=>{if(globalThis.window!==void 0)try{(s===`local`?localStorage:sessionStorage).setItem(c,r.serialize({v:l}))}catch{}},[l,c,s]),[l,n(e=>{u(t=>typeof e==`function`?e(t):e)},[]),n(()=>{if(u(a),globalThis.window!==void 0)try{(s===`local`?localStorage:sessionStorage).removeItem(c)}catch{}},[a,c,s])]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* This approach avoids dependency issues (e.g. undici, platform-specific
|
|
8
8
|
* modules) that occur when importing the server bundle directly.
|
|
9
9
|
*/
|
|
10
|
-
import type { PrerenderConfig } from
|
|
10
|
+
import type { PrerenderConfig } from "./index.ts";
|
|
11
11
|
/**
|
|
12
12
|
* Prerender routes by spawning the built server and fetching each route via HTTP.
|
|
13
13
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{spawn as e}from"node:child_process";import{existsSync as t,mkdirSync as n,writeFileSync as r}from"node:fs";import{dirname as i,join as a,resolve as o}from"node:path";function s(e){let t=[],n=/<a\s[^>]*href=["']([^"'#?]+)/gi,r=n.exec(e);for(;r!==null;){let i=r[1];if(i.startsWith(`/`)&&!i.startsWith(`//`)){if(i.startsWith(`/assets/`)||i.startsWith(`/islands/`)||i.startsWith(`/chunks/`)||i.startsWith(`/_`)||i.match(/\.\w{2,5}$/))continue;t.push(i)}r=n.exec(e)}return[...new Set(t)]}function c(e,t){for(let n of t)if(typeof n==`string`){if(e===n||n.endsWith(`/**`)&&e.startsWith(n.slice(0,-2)))return!0}else if(n instanceof RegExp){if(n.test(e))return!0}else if(typeof n==`function`&&n(e))return!0;return!1}async function l(e,t=15e3){let n=Date.now();for(;Date.now()-n<t;){try{let t=await fetch(`${e}/`);if(t.ok||t.status<500)return}catch{}await new Promise(e=>setTimeout(e,200))}throw Error(`[prerender] Server did not become ready within ${t}ms`)}export async function prerenderRoutes(u){let{serverEntryPath:d,outputDir:f,routes:p=[`/`],crawlLinks:m=!1,ignore:h=[],concurrency:g=4,failOnError:_=!1,autoSubfolderIndex:v=!0,retry:y=3,retryDelay:b=500,port:x=13172}=u,S=o(d);if(!t(S))throw Error(`[prerender] Server entry not found: ${S}`);let C=o(f),w=`http://localhost:${x}`;console.log(`[prerender] Spawning server from ${d} on port ${x}...`);let T=null;try{T=e(`node`,[S],{env:{...process.env,PORT:String(x),NITRO_PORT:String(x),HOST:`127.0.0.1`,NITRO_HOST:`127.0.0.1`,NODE_ENV:`production`},stdio:[`ignore`,`pipe`,`pipe`]}),T.stdout?.on(`data`,e=>{let t=e.toString().trim();t&&console.log(`[prerender:server] ${t}`)}),T.stderr?.on(`data`,e=>{let t=e.toString().trim();t&&console.error(`[prerender:server:err] ${t}`)}),await l(w),console.log(`[prerender] Server ready at ${w}`)}catch(e){T?.kill(`SIGKILL`);let t=e instanceof Error?e.message:String(e);throw Error(`[prerender] Failed to start server: ${t}`)}let E=[],D=[],O=new Set,k=[...p];console.log(`[prerender] Starting with ${k.length} route(s), crawlLinks=${m}`);async function A(e){for(let t=1;t<=y;t++)try{let t=await fetch(`${w}${e}`);return{html:await t.text(),status:t.status}}catch(n){let r=n instanceof Error?n.message:String(n);if(t<y)console.warn(`[prerender] Retry ${t}/${y} for ${e}: ${r}`),await new Promise(e=>setTimeout(e,b));else return console.error(`[prerender] All ${y} attempts failed for ${e}: ${r}`),null}return null}try{for(;k.length>0;){let e=k.splice(0,g);await Promise.all(e.map(async e=>{let t=e.endsWith(`/`)&&e!==`/`?e.slice(0,-1):e;if(O.has(t))return;if(O.add(t),c(t,h)){console.log(`[prerender] ⏭ ${t} (ignored)`);return}let o=await A(t);if(!o){let e=`Failed to fetch ${t} after ${y} attempts`;if(console.error(`[prerender] ❌ ${e}`),D.push({route:t,error:e}),_)throw Error(e);return}if(o.status>=400){let e=`${t} returned ${o.status}`;if(console.error(`[prerender] ❌ ${e}`),D.push({route:t,error:e}),_)throw Error(e);return}let l=v?a(t,`index.html`):`${t}.html`,u=a(C,l);if(n(i(u),{recursive:!0}),r(u,o.html),E.push(t),console.log(`[prerender] ✅ ${t} → ${l}`),m){let e=s(o.html);for(let t of e){let e=t.endsWith(`/`)&&t!==`/`?t.slice(0,-1):t;!O.has(e)&&!c(e,h)&&k.push(e)}}}))}}finally{T&&(console.log(`[prerender] Shutting down server...`),T.kill(`SIGKILL`))}return console.log(`[prerender] Done: ${E.length} page(s) prerendered`+(D.length>0?`, ${D.length} error(s)`:``)),{prerenderedRoutes:E,errors:D}}
|
|
@@ -108,7 +108,7 @@ export declare const PersistentIslandContextSchema: z.ZodObject<{
|
|
|
108
108
|
* Layout Error Info Schema
|
|
109
109
|
*/
|
|
110
110
|
export declare const LayoutErrorInfoSchema: z.ZodObject<{
|
|
111
|
-
layoutPath: z.ZodString
|
|
111
|
+
layoutPath: z.ZodOptional<z.ZodString>;
|
|
112
112
|
errorType: z.ZodEnum<{
|
|
113
113
|
island: "island";
|
|
114
114
|
component: "component";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as e}from"zod";export const LayoutContextSchema=e.object({request:e.instanceof(Request),params:e.record(e.string(),e.string()),query:e.instanceof(URLSearchParams),state:e.instanceof(Map),middlewareContext:e.any().optional()});export const LayoutDataSchema=e.record(e.string(),e.unknown());export const LayoutRouteSchema=e.object({pattern:e.instanceof(URLPattern),layoutPath:e.string().min(1),priority:e.number().int().min(0),type:e.enum([`root`,`nested`]),depth:e.number().int().min(0)});export const LayoutHandlerSchema=e.object({component:e.any(),loader:e.any().optional(),path:e.string().min(1),priority:e.number().int().min(0)});export const LayoutPropsSchema=e.object({children:e.any(),data:LayoutDataSchema,frontmatter:e.record(e.string(),e.any()).optional(),route:e.object({path:e.string(),params:e.record(e.string(),e.string()),query:e.instanceof(URLSearchParams)})});export const LayoutDiscoveryOptionsSchema=e.object({baseDirectory:e.string().min(1),filePattern:e.string().min(1).optional().default(`_layout.tsx`),excludeDirectories:e.array(e.string()).optional().default([]),enableWatching:e.boolean().optional().default(!1),developmentMode:e.boolean().optional().default(!1)});export const RouteInfoSchema=e.object({path:e.string(),params:e.record(e.string(),e.string()),method:e.string(),headers:e.instanceof(Headers)});export const LayoutRuleSchema=e.object({matches:e.any(),apply:e.boolean(),priority:e.number().int()});export const LayoutConfigSchema=e.object({skipLayouts:e.array(e.string()).optional(),replaceLayout:e.boolean().optional(),onlyLayouts:e.array(e.string()).optional(),customLayout:e.string().optional()});export const IslandStateSchema=e.record(e.string(),e.unknown());export const PersistentIslandPropsSchema=e.object({persistentId:e.string().min(1),children:e.any()});export const PersistentIslandContextSchema=e.object({saveState:e.any(),loadState:e.any(),clearState:e.any()});export const LayoutErrorInfoSchema=e.object({layoutPath:e.string(),errorType:e.enum([`component`,`loader`,`rendering`,`island`]),timestamp:e.number().int().positive(),componentStack:e.string().optional(),errorBoundary:e.string().optional()});export const LayoutErrorBoundaryPropsSchema=e.object({children:e.any(),fallback:e.any(),onError:e.any().optional()});export const ErrorRecoveryStrategySchema=e.object({type:e.enum([`retry`,`fallback`,`skip`,`redirect`]),maxRetries:e.number().int().positive().optional(),fallbackComponent:e.any().optional(),redirectUrl:e.url().optional()});export const StreamingLayoutPropsSchema=e.object({children:e.any(),fallback:e.any().optional(),priority:e.enum([`high`,`medium`,`low`]).default(`medium`)});export const StreamingComponentSchema=e.object({component:e.any(),fallback:e.any(),priority:e.number().int().min(0),isReady:e.any()});export const ResolvedLayoutSchema=e.object({handlers:e.array(LayoutHandlerSchema),dataLoaders:e.array(e.any()),errorBoundaries:e.array(e.any()),streamingComponents:e.array(StreamingComponentSchema),metadata:e.object({totalLayouts:e.number().int().min(0),resolutionTime:e.number().positive(),cacheHit:e.boolean()})});export const LayoutCacheSchema=e.object({resolved:e.instanceof(Map),handlers:e.instanceof(Map),data:e.instanceof(Map),ttl:e.instanceof(Map)});export const EnhancedLayoutContextSchema=LayoutContextSchema.extend({layouts:e.array(LayoutHandlerSchema),parentData:e.array(LayoutDataSchema),islandStates:e.instanceof(Map),streamingEnabled:e.boolean(),errorBoundaries:e.array(e.any())});
|
|
1
|
+
import{z as e}from"zod";export const LayoutContextSchema=e.object({request:e.instanceof(Request),params:e.record(e.string(),e.string()),query:e.instanceof(URLSearchParams),state:e.instanceof(Map),middlewareContext:e.any().optional()});export const LayoutDataSchema=e.record(e.string(),e.unknown());export const LayoutRouteSchema=e.object({pattern:e.instanceof(URLPattern),layoutPath:e.string().min(1),priority:e.number().int().min(0),type:e.enum([`root`,`nested`]),depth:e.number().int().min(0)});export const LayoutHandlerSchema=e.object({component:e.any(),loader:e.any().optional(),path:e.string().min(1),priority:e.number().int().min(0)});export const LayoutPropsSchema=e.object({children:e.any(),data:LayoutDataSchema,frontmatter:e.record(e.string(),e.any()).optional(),route:e.object({path:e.string(),params:e.record(e.string(),e.string()),query:e.instanceof(URLSearchParams)})});export const LayoutDiscoveryOptionsSchema=e.object({baseDirectory:e.string().min(1),filePattern:e.string().min(1).optional().default(`_layout.tsx`),excludeDirectories:e.array(e.string()).optional().default([]),enableWatching:e.boolean().optional().default(!1),developmentMode:e.boolean().optional().default(!1)});export const RouteInfoSchema=e.object({path:e.string(),params:e.record(e.string(),e.string()),method:e.string(),headers:e.instanceof(Headers)});export const LayoutRuleSchema=e.object({matches:e.any(),apply:e.boolean(),priority:e.number().int()});export const LayoutConfigSchema=e.object({skipLayouts:e.array(e.string()).optional(),replaceLayout:e.boolean().optional(),onlyLayouts:e.array(e.string()).optional(),customLayout:e.string().optional()});export const IslandStateSchema=e.record(e.string(),e.unknown());export const PersistentIslandPropsSchema=e.object({persistentId:e.string().min(1),children:e.any()});export const PersistentIslandContextSchema=e.object({saveState:e.any(),loadState:e.any(),clearState:e.any()});export const LayoutErrorInfoSchema=e.object({layoutPath:e.string().optional(),errorType:e.enum([`component`,`loader`,`rendering`,`island`]),timestamp:e.number().int().positive(),componentStack:e.string().optional(),errorBoundary:e.string().optional()});export const LayoutErrorBoundaryPropsSchema=e.object({children:e.any(),fallback:e.any(),onError:e.any().optional()});export const ErrorRecoveryStrategySchema=e.object({type:e.enum([`retry`,`fallback`,`skip`,`redirect`]),maxRetries:e.number().int().positive().optional(),fallbackComponent:e.any().optional(),redirectUrl:e.url().optional()});export const StreamingLayoutPropsSchema=e.object({children:e.any(),fallback:e.any().optional(),priority:e.enum([`high`,`medium`,`low`]).default(`medium`)});export const StreamingComponentSchema=e.object({component:e.any(),fallback:e.any(),priority:e.number().int().min(0),isReady:e.any()});export const ResolvedLayoutSchema=e.object({handlers:e.array(LayoutHandlerSchema),dataLoaders:e.array(e.any()),errorBoundaries:e.array(e.any()),streamingComponents:e.array(StreamingComponentSchema),metadata:e.object({totalLayouts:e.number().int().min(0),resolutionTime:e.number().positive(),cacheHit:e.boolean()})});export const LayoutCacheSchema=e.object({resolved:e.instanceof(Map),handlers:e.instanceof(Map),data:e.instanceof(Map),ttl:e.instanceof(Map)});export const EnhancedLayoutContextSchema=LayoutContextSchema.extend({layouts:e.array(LayoutHandlerSchema),parentData:e.array(LayoutDataSchema),islandStates:e.instanceof(Map),streamingEnabled:e.boolean(),errorBoundaries:e.array(e.any())});
|
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
export interface IslandDirective {
|
|
14
|
-
/** Hydration condition */
|
|
15
|
-
condition?: 'on:visible' | 'on:interaction' | 'on:idle' | 'on:client' | `media:${string}`;
|
|
14
|
+
/** Hydration condition (built-in or custom directive name) */
|
|
15
|
+
condition?: 'on:visible' | 'on:interaction' | 'on:idle' | 'on:client' | `media:${string}` | `on:${string}`;
|
|
16
|
+
/** Optional argument passed to custom hydration directives */
|
|
17
|
+
conditionArg?: string;
|
|
16
18
|
/** Force SSR-only rendering without client hydration */
|
|
17
19
|
ssrOnly?: boolean;
|
|
18
20
|
/** Whether to render server-side (default: true) */
|
|
@@ -6,14 +6,6 @@ export { LayoutDiscovery } from '../core/layout/layout-discovery.ts';
|
|
|
6
6
|
export { LayoutDataLoader } from '../core/layout/layout-data-loader.ts';
|
|
7
7
|
export { LayoutMatcher as LayoutMatcherClass } from '../core/layout/layout-matcher.ts';
|
|
8
8
|
export { LayoutComposer } from '../core/layout/layout-composer.ts';
|
|
9
|
-
export { IslandPersistence, defaultIslandPersistence } from '../core/islands/island-persistence.ts';
|
|
10
|
-
export { IslandStateSerializer } from '../core/islands/island-state-serializer.ts';
|
|
11
|
-
export { createPersistentIslandContext, usePersistentIslandContext, PersistentIslandProvider, } from '../core/islands/persistent-island-context.tsx';
|
|
12
|
-
export { PersistentIsland } from '../components/PersistentIsland.tsx';
|
|
13
|
-
export { LayoutErrorBoundary } from '../components/LayoutErrorBoundary.tsx';
|
|
14
|
-
export { LayoutDataErrorBoundary } from '../components/LayoutDataErrorBoundary.tsx';
|
|
15
|
-
export { IslandErrorBoundary, withIslandErrorBoundary } from '../components/IslandErrorBoundary.tsx';
|
|
16
|
-
export { StreamingLayout, StreamingSuspense, withStreaming, useStreamingState, } from '../components/StreamingLayout.tsx';
|
|
17
9
|
export { EnhancedLayoutResolver, createEnhancedLayoutResolver, EnhancedLayoutResolverUtils, type EnhancedLayoutResolverOptions, } from '../core/layout/enhanced-layout-resolver.ts';
|
|
18
10
|
export { LayoutCacheManager, type CacheEntry, type CacheStats, type CacheConfig, } from '../core/layout/layout-cache-manager.ts';
|
|
19
11
|
/**
|
package/dist/src/types/layout.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{LayoutDiscovery}from"../core/layout/layout-discovery.js";export{LayoutDataLoader}from"../core/layout/layout-data-loader.js";export{LayoutMatcher as LayoutMatcherClass}from"../core/layout/layout-matcher.js";export{LayoutComposer}from"../core/layout/layout-composer.js";export{
|
|
1
|
+
export{LayoutDiscovery}from"../core/layout/layout-discovery.js";export{LayoutDataLoader}from"../core/layout/layout-data-loader.js";export{LayoutMatcher as LayoutMatcherClass}from"../core/layout/layout-matcher.js";export{LayoutComposer}from"../core/layout/layout-composer.js";export{EnhancedLayoutResolver,createEnhancedLayoutResolver,EnhancedLayoutResolverUtils}from"../core/layout/enhanced-layout-resolver.js";export{LayoutCacheManager}from"../core/layout/layout-cache-manager.js";
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* - Page routes: Virtual module for SSR page component discovery
|
|
7
7
|
* - Middleware: Auto-discovered by Nitro from `middleware/` directory
|
|
8
8
|
*/
|
|
9
|
-
import type { Plugin, ViteDevServer } from
|
|
10
|
-
import type
|
|
11
|
-
import
|
|
9
|
+
import type { Plugin, ViteDevServer } from "vite";
|
|
10
|
+
import { type AvalonNitroConfig, type NitroConfigOutput } from "../nitro/config.ts";
|
|
11
|
+
import type { ResolvedAvalonConfig } from "./types.ts";
|
|
12
12
|
export declare const VIRTUAL_MODULE_IDS: {
|
|
13
13
|
readonly PAGE_ROUTES: "virtual:avalon/page-routes";
|
|
14
14
|
readonly PAGE_LOADER: "virtual:avalon/page-loader";
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{existsSync as e}from"node:fs";import{stat as t}from"node:fs/promises";import{createRequire as n}from"node:module";import{dirname as r,join as i}from"node:path";import{nitro as a}from"nitro/vite";import{isRunnableDevEnvironment as o}from"vite";import{getUniversalCSSForHead as s}from"../islands/universal-css-collector.js";import{getUniversalHeadForInjection as c}from"../islands/universal-head-collector.js";import{clearMiddlewareCache as l,discoverScopedMiddleware as u,executeScopedMiddleware as d}from"../middleware/index.js";import{createNitroConfig as f}from"../nitro/config.js";import{createIslandManifestPlugin as p,createNitroBuildPlugin as m,createSourceMapConfig as h,createSourceMapPlugin as g}from"../nitro/index.js";import{collectCssFromModuleGraph as _,injectSsrCss as v}from"../render/collect-css.js";import{generateErrorPage as y,generateFallback404 as b}from"../render/error-pages.js";function x(t){let a=i(r(n(import.meta.url).resolve(`@useavalon/avalon`)),t);if(t.endsWith(`.ts`)&&!e(a)){let t=a.replace(/\.ts$/,`.js`);if(e(t))return t}return a}function S(t,a){let o=i(r(n(i(process.cwd(),`package.json`)).resolve(`@useavalon/${t}`)),a);if(a.endsWith(`.ts`)&&!e(o)){let t=o.replace(/\.ts$/,`.js`);if(e(t))return t}return o}export const VIRTUAL_MODULE_IDS={PAGE_ROUTES:`virtual:avalon/page-routes`,PAGE_LOADER:`virtual:avalon/page-loader`,ISLAND_MANIFEST:`virtual:avalon/island-manifest`,RUNTIME_CONFIG:`virtual:avalon/runtime-config`,CONFIG:`virtual:avalon/config`};export const RESOLVED_VIRTUAL_IDS={PAGE_ROUTES:`\0`+VIRTUAL_MODULE_IDS.PAGE_ROUTES,PAGE_LOADER:`\0`+VIRTUAL_MODULE_IDS.PAGE_LOADER,ISLAND_MANIFEST:`\0`+VIRTUAL_MODULE_IDS.ISLAND_MANIFEST,RUNTIME_CONFIG:`\0`+VIRTUAL_MODULE_IDS.RUNTIME_CONFIG,CONFIG:`\0`+VIRTUAL_MODULE_IDS.CONFIG};export function createNitroIntegration(e,t={}){let n=f(t,e),r={preset:n.preset,serverDir:t.serverDir??n.serverDir??`./server`,routeRules:n.routeRules,runtimeConfig:n.runtimeConfig,compatibilityDate:n.compatibilityDate,scanDirs:[`.`],noExternals:[`estree-walker`,/^@useavalon\//,/^estree-util/]};t.renderer===!1?r.renderer=!1:n.renderer&&(r.renderer=n.renderer),n.publicRuntimeConfig&&(r.publicRuntimeConfig=n.publicRuntimeConfig),n.publicAssets&&(r.publicAssets=n.publicAssets),n.compressPublicAssets&&(r.compressPublicAssets=n.compressPublicAssets),n.serverEntry&&(r.serverEntry=n.serverEntry);let i=n.traceDeps??[];r.traceDeps=[...new Set([`undici`,...i])],n.prerender&&(r.prerender={routes:[],crawlLinks:!1});let o=a(r),s=createNitroCoordinationPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),c=createVirtualModulesPlugin({avalonConfig:e,nitroConfig:t,verbose:e.verbose}),l=m(e,t),u=p(e,{verbose:e.verbose,generatePreloadHints:!0}),d=g(h(t.preset??`node_server`,e.isDev));return{nitroOptions:n,plugins:[...Array.isArray(o)?o:[o],s,c,l,u,d]}}export function createNitroCoordinationPlugin(e){let{avalonConfig:t,verbose:n}=e;return{name:`avalon:nitro-coordination`,enforce:`pre`,configResolved(e){globalThis.__avalonConfig=t},configureServer(e){globalThis.__viteDevServer=e;let r=null;async function i(){return r||=await u({baseDir:`${e.config.root||process.cwd()}/src`,devMode:!1}),r}function a(){r=null}j(e,t,n,a),i().catch(e=>{console.warn(`[middleware] Failed to discover middleware:`,e)});let s=e.environments?.ssr,c=!!s&&o(s);c&&k(e,t.integrations,n).catch(e=>{console.error(`[prewarm] Core modules pre-warm failed:`,e)}),e.middlewares.use(async(r,a,o)=>{if(!c)return o();let s=r.url||`/`;if(s.endsWith(`.html`)&&(s=s.slice(0,-5)||`/`),s===`/index`&&(s=`/`),s.startsWith(`/@`)||s.startsWith(`/__`)||s.startsWith(`/node_modules/`)||s.startsWith(`/src/client/`)||s.startsWith(`/packages/`)||s.includes(`.`)&&!s.endsWith(`/`)||s.startsWith(`/api/`))return o();try{if(await D(e,s,r,a,i,n)||await z(e,s,t,a))return;let o=await B(e,s,t);if(o){a.statusCode=200,a.setHeader(`Content-Type`,`text/html`),a.end(o);return}await O(e,s,a,t)}catch(e){console.error(`[SSR Error]`,e),a.statusCode=500,a.setHeader(`Content-Type`,`text/html`),a.end(y(e))}})},buildStart(){}}}async function D(e,t,n,r,i,a){let o=performance.now(),s=await i();if(s.length===0)return!1;let c={};for(let[e,t]of Object.entries(n.headers))typeof t==`string`?c[e]=t:Array.isArray(t)&&(c[e]=t.join(`, `));let l=`http://${n.headers.host||`localhost`}${t}`,u=await d({url:l,method:n.method||`GET`,path:t,node:{req:n,res:r},req:new Request(l,{method:n.method||`GET`,headers:c}),context:{}},s,{devMode:!1}),f=performance.now()-o;return f>100&&console.warn(`⚠️ Slow middleware: ${f.toFixed(0)}ms for ${t}`),u?(r.statusCode=u.status,u.headers.forEach((e,t)=>{r.setHeader(t,e)}),r.end(await u.text()),!0):!1}async function O(e,t,n,r){try{let{discoverErrorPages:i,getErrorPageModule:a,generateDefaultErrorPage:o}=await import(`../nitro/error-handler.js`),s=a(404,await i({isDev:r.isDev,pagesDir:r.pagesDir,loadPageModule:async t=>await e.ssrLoadModule(t)}));if(s?.default&&typeof s.default==`function`){let{renderToHtml:e}=await import(`../render/ssr.js`),r=s.default,i=await e({component:()=>r({statusCode:404,message:`Page not found: ${t}`,url:t})},{});n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(i);return}let c=o(404,`Page not found: ${t}`,r.isDev);n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(c)}catch{n.statusCode=404,n.setHeader(`Content-Type`,`text/html`),n.end(b(t))}}async function k(e,t,n){let r=performance.now(),i=[{path:x(`src/render/ssr.ts`),assignTo:`ssr`},{path:x(`src/core/layout/enhanced-layout-resolver.ts`),assignTo:`layout`},{path:x(`src/middleware/index.ts`),assignTo:null},...t.map(e=>({path:S(e,`server/renderer.ts`),assignTo:null}))],a=(await Promise.allSettled(i.map(async({path:t,assignTo:n})=>{let r=await e.ssrLoadModule(t);n===`ssr`&&(L=r),n===`layout`&&(R=r)}))).filter(e=>e.status===`fulfilled`).length,o=performance.now()-r;n&&a>0&&console.log(`🔥 SSR ready in ${o.toFixed(0)}ms (${a}/${i.length} core modules)`)}export function createVirtualModulesPlugin(e){let{avalonConfig:t,nitroConfig:n,verbose:r}=e;return{name:`avalon:nitro-virtual-modules`,enforce:`pre`,resolveId(e){return e===VIRTUAL_MODULE_IDS.PAGE_ROUTES?RESOLVED_VIRTUAL_IDS.PAGE_ROUTES:e===VIRTUAL_MODULE_IDS.PAGE_LOADER?RESOLVED_VIRTUAL_IDS.PAGE_LOADER:e===VIRTUAL_MODULE_IDS.ISLAND_MANIFEST?RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST:e===VIRTUAL_MODULE_IDS.RUNTIME_CONFIG?RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG:e===VIRTUAL_MODULE_IDS.CONFIG?RESOLVED_VIRTUAL_IDS.CONFIG:null},async load(e){return e===RESOLVED_VIRTUAL_IDS.PAGE_ROUTES?await M(t,r):e===RESOLVED_VIRTUAL_IDS.PAGE_LOADER?await N(t,r):e===RESOLVED_VIRTUAL_IDS.ISLAND_MANIFEST?P():e===RESOLVED_VIRTUAL_IDS.RUNTIME_CONFIG?F(t,n):e===RESOLVED_VIRTUAL_IDS.CONFIG?generateConfigModule(t,n):null},handleHotUpdate({file:e,server:n}){if(e.includes(t.pagesDir)){let e=n.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.PAGE_ROUTES);e&&n.moduleGraph.invalidateModule(e)}if(e.includes(`vite.config`)||e.includes(`avalon.config`)||e.includes(`nitro.config`)){let e=n.moduleGraph.getModuleById(RESOLVED_VIRTUAL_IDS.CONFIG);e&&n.moduleGraph.invalidateModule(e)}}}}function j(e,t,n,r){e.watcher.on(`change`,e=>{e.includes(`_middleware`)&&(l(),r?.()),(e.includes(`/render/`)||e.includes(`/layout/`)||e.includes(`/islands/`))&&(L=null,R=null),(e.includes(`/layouts/`)||e.includes(`_layout`))&&globalThis.__avalonLayoutResolver?.clearCache?.()}),e.watcher.on(`add`,e=>{e.includes(`_middleware`)&&(l(),r?.())}),e.watcher.on(`unlink`,e=>{e.includes(`_middleware`)&&(l(),r?.())})}async function M(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),r=await n(await t(e.pagesDir,e.modules,process.cwd()),{developmentMode:e.isDev});return`export const pageRoutes = ${JSON.stringify(r,null,2)};\nexport default pageRoutes;\n`}catch{return`export const pageRoutes = [];
|
|
2
2
|
export default pageRoutes;
|
|
3
|
-
`}}async function
|
|
3
|
+
`}}async function N(e,t){try{let{getAllPageDirs:t}=await import(`./module-discovery.js`),{discoverPageRoutesFromMultipleDirs:n}=await import(`../nitro/route-discovery.js`),{relative:r}=await import(`node:path`),i=process.cwd(),a=await n(await t(e.pagesDir,e.modules,i),{developmentMode:e.isDev}),o=[],s=[];for(let e=0;e<a.length;e++){let t=a[e],n=`page_${e}`,c=r(i,t.filePath).replaceAll(`\\`,`/`),l=c.startsWith(`/`)?c:`/`+c;o.push(`import * as ${n} from '${l}';`),s.push(` { pattern: ${JSON.stringify(t.pattern)}, params: ${JSON.stringify(t.params)}, module: ${n} }`)}return[...o,``,`const routes = [`,s.join(`,
|
|
4
4
|
`),`];`,``,`/**`,` * Match a pathname against discovered routes and return the page module.`,` * Uses the same pattern matching as Avalon's route discovery.`,` */`,`export function loadPage(pathname) {`,` const cleanPath = pathname.split('?')[0];`,` for (const route of routes) {`,` if (matchRoute(cleanPath, route.pattern, route.params)) {`,` return route.module;`,` }`,` }`,` return null;`,`}`,``,`function matchRoute(pathname, pattern, paramNames) {`,` // Exact match`,` if (pattern === pathname) return true;`,` // Normalize trailing slashes`,` const normPath = pathname === '/' ? '/' : pathname.replace(/\\/$/, '');`,` const normPattern = pattern === '/' ? '/' : pattern.replace(/\\/$/, '');`,` if (normPath === normPattern) return true;`,` // Dynamic segments: /users/:id matches /users/123`,` if (paramNames.length > 0) {`,` const patternParts = normPattern.split('/');`,` const pathParts = normPath.split('/');`,` if (patternParts.length !== pathParts.length) return false;`,` return patternParts.every((part, i) => part.startsWith(':') || part === pathParts[i]);`,` }`,` return false;`,`}`,``,`export default { loadPage, routes };`,``].join(`
|
|
5
5
|
`)}catch(e){return console.error(`[page-loader] Failed to generate page loader:`,e),`export function loadPage() { return null; }
|
|
6
6
|
export default { loadPage, routes: [] };
|
|
7
|
-
`}}function
|
|
7
|
+
`}}function P(){return`export const islandManifest = { islands: {}, clientEntry: "", css: [] };
|
|
8
8
|
export default islandManifest;
|
|
9
|
-
`}function
|
|
9
|
+
`}function F(e,t){let n={avalon:{streaming:t.streaming??!0,pagesDir:e.pagesDir,layoutsDir:e.layoutsDir,isDev:e.isDev},...t.runtimeConfig};return`export const runtimeConfig = ${JSON.stringify(n,null,2)};\nexport function useRuntimeConfig() { return runtimeConfig; }\nexport default runtimeConfig;\n`}export function generateConfigModule(e,t){let n={streaming:t.streaming??!0,pagesDir:e.pagesDir,layoutsDir:e.layoutsDir,isDev:e.isDev,...t.runtimeConfig};return`const config = ${JSON.stringify(n,null,2)};\nexport function useAvalonConfig() { return config; }\nexport default config;\n`}export function getViteDevServer(){return globalThis.__viteDevServer}export function getAvalonConfig(){return globalThis.__avalonConfig}export function isDevelopmentMode(){return globalThis.__avalonConfig?.isDev??!0}const I=`<!--AVALON_STREAM_BOUNDARY-->`;let L=null,R=null;async function z(e,t,n,r){if(!n.modules)return!1;let i=t.split(`?`)[0],a=await W(i,n,e);if(!a)return!1;try{let t=await e.ssrLoadModule(a),n=t.default;if(!n)return!1;let o=t.layoutConfig,l=await _(e,a),u=await U(i,e),d=[];for(let t of u){let n=await e.ssrLoadModule(t);d.push({file:t,module:n})}for(let t of u){let n=await _(e,t);l.push(...n)}if(d.length===0)return!1;let{render:f}=await e.ssrLoadModule(`preact-render-to-string`),{h:p}=await e.ssrLoadModule(`preact`),m=o?.skipLayouts||[],h=d.filter(({file:e})=>{let t=e.split(`/`).pop()?.replace(/\.[^.]+$/,``)||``;return!m.includes(t)}),g=t.frontmatter,v=t.metadata,y={children:null,frontmatter:{...g,...v,currentPath:i},params:{},url:i},b=[],x=[];for(let e of h){let t=e.module.default;if(!(!t||typeof t!=`function`))try{let n=t({...y,children:p(`div`,null,`test`)}),r=f(n instanceof Promise?await n:n);r.trim().startsWith(`<html`)||r.includes(`<!DOCTYPE`)?b.push(e):x.push(e)}catch{x.push(e)}}if(b.length===0)return!1;let{module:S}=b[b.length-1],C=S.default;if(!C||typeof C!=`function`)return!1;let w;try{let e=C({...y,children:p(`div`,{dangerouslySetInnerHTML:{__html:I}})});w=f(e instanceof Promise?await e:e)}catch{return!1}let T=w.indexOf(I);if(T===-1)return!1;let E=w.slice(0,T),D=w.slice(T+29),O=E;if(l.length>0){let e=`<style data-avalon-ssr-css>${l.join(`
|
|
10
10
|
`)}</style>`;O=E.includes(`</head>`)?E.replace(`</head>`,`${e}\n</head>`):E+e}O.trim().toLowerCase().startsWith(`<!doctype`)||(O=`<!DOCTYPE html>
|
|
11
|
-
`+O);let k=
|
|
11
|
+
`+O);let k=s(!0);k&&O.includes(`</head>`)&&(O=O.replace(`</head>`,`${k}\n</head>`));let A=c(!0);A&&O.includes(`</head>`)&&(O=O.replace(`</head>`,`${A}\n</head>`)),r.statusCode=200,r.setHeader(`Content-Type`,`text/html; charset=utf-8`),r.setHeader(`Transfer-Encoding`,`chunked`),r.setHeader(`X-Avalon-Streaming`,`1`),r.flushHeaders(),r.write(O);let j;try{let e=typeof n==`function`?n():n;j=f(e instanceof Promise?await e:e)}catch(e){console.error(`[SSR Streaming] Error rendering page component:`,e),j=`<div>Error rendering page</div>`}if(j.trim().startsWith(`<!DOCTYPE html>`)||j.trim().startsWith(`<html`))return r.end(j),!0;let M=j;for(let{module:e}of x){let t=e.default;if(!(!t||typeof t!=`function`))try{let e=t({...y,children:p(`div`,{dangerouslySetInnerHTML:{__html:M}})});M=f(e instanceof Promise?await e:e)}catch(e){console.error(`[SSR Streaming] Error rendering wrapper layout:`,e)}}let N=M+D;if(!N.includes(`/src/client/main.js`)&&!N.includes(`/@vite/client`)){let e=N.lastIndexOf(`</body>`);e!==-1&&(N=N.slice(0,e)+`
|
|
12
12
|
<script type="module" src="/@vite/client"><\/script>
|
|
13
13
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
14
|
-
`+N.slice(e))}return r.end(N),!0}catch(e){return r.headersSent?(r.end(`<div>Streaming SSR error: ${e.message}</div></body></html>`),!0):!1}}async function
|
|
14
|
+
`+N.slice(e))}return r.end(N),!0}catch(e){return r.headersSent?(r.end(`<div>Streaming SSR error: ${e.message}</div></body></html>`),!0):!1}}async function B(e,t,n){let r=t.split(`?`)[0],i=await W(r,n,e);if(!i)return null;try{let t=await e.ssrLoadModule(i),a=t.default;if(!a)return console.warn(`[SSR] Page ${i} has no default export`),null;let o=await _(e,i),s=await U(r,e),c=[];for(let t of s){let n=await e.ssrLoadModule(t);c.push({file:t,module:n})}for(let t of s){let n=await _(e,t);o.push(...n)}let l;return l=n.modules&&c.length>0?await V(a,t,c,r,n,e):await G(a,t,r,n,e),o.length>0&&(l=v(l,o)),l}catch(e){throw console.error(`[SSR] Error rendering ${i}:`,e),e}}async function V(e,t,n,r,i,a){let{render:o}=await a.ssrLoadModule(`preact-render-to-string`),{h:s}=await a.ssrLoadModule(`preact`),c=t.layoutConfig?.skipLayouts||[],l=n.filter(({file:e})=>{let t=e.split(`/`).pop()?.replace(/\.[^.]+$/,``)||``;return!c.includes(t)}),u;try{let t=typeof e==`function`?e():e;u=o(t instanceof Promise?await t:t)}catch(e){console.error(`[SSR] Error rendering page component:`,e),u=`<div>Error rendering page</div>`}if(u.trim().startsWith(`<!DOCTYPE html>`)||u.trim().startsWith(`<html`))return H(u);let d=t.frontmatter,f=t.metadata,p={children:null,frontmatter:{...d,...f,currentPath:r},params:{},url:r},m=[],h=[];for(let e of l){let t=e.module.default;if(!(!t||typeof t!=`function`))try{let n=t({...p,children:s(`div`,null,`test`)}),r=o(n instanceof Promise?await n:n);r.trim().startsWith(`<html`)||r.includes(`<!DOCTYPE`)?m.push(e):h.push(e)}catch{h.push(e)}}let g=u;for(let{module:e}of h){let t=e.default;if(!(!t||typeof t!=`function`))try{let e=t({...p,children:s(`div`,{dangerouslySetInnerHTML:{__html:g}})});g=o(e instanceof Promise?await e:e)}catch(e){console.error(`[SSR] Error rendering wrapper layout:`,e)}}if(m.length>0){let{module:e}=m[m.length-1],t=e.default;if(t&&typeof t==`function`)try{let e=t({...p,children:s(`div`,{dangerouslySetInnerHTML:{__html:g}})});g=o(e instanceof Promise?await e:e)}catch(e){console.error(`[SSR] Error rendering shell layout:`,e)}}if(g.trim().startsWith(`<!DOCTYPE html>`)||g.trim().startsWith(`<html`))return H(g);let _=t.metadata||{},v=_.title||`Avalon App`,y=_.description||``;return`<!DOCTYPE html>
|
|
15
15
|
<html lang="en">
|
|
16
16
|
<head>
|
|
17
17
|
<meta charset="utf-8">
|
|
18
18
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
19
|
-
<title>${
|
|
20
|
-
${y?`<meta name="description" content="${
|
|
19
|
+
<title>${K(v)}</title>
|
|
20
|
+
${y?`<meta name="description" content="${K(y)}">`:``}
|
|
21
21
|
<script type="module" src="/@vite/client"><\/script>
|
|
22
22
|
</head>
|
|
23
23
|
<body>
|
|
24
24
|
${g}
|
|
25
25
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
26
26
|
</body>
|
|
27
|
-
</html>`}function
|
|
28
|
-
`+t),!t.includes(`data-universal-ssr="true"`)){let e=
|
|
27
|
+
</html>`}function H(e){let t=e;if(t.trim().toLowerCase().startsWith(`<!doctype`)||(t=`<!DOCTYPE html>
|
|
28
|
+
`+t),!t.includes(`data-universal-ssr="true"`)){let e=s(!0);e&&t.includes(`</head>`)&&(t=t.replace(`</head>`,`${e}\n</head>`))}let n=c(!0);if(n&&t.includes(`</head>`)&&(t=t.replace(`</head>`,`${n}\n</head>`)),t.includes(`/src/client/main.js`)||t.includes(`/@vite/client`))return t;let r=t.lastIndexOf(`</body>`);return r===-1?t+`
|
|
29
29
|
<script type="module" src="/@vite/client"><\/script>
|
|
30
30
|
<script type="module" src="/src/client/main.js"><\/script>`:t.slice(0,r)+`
|
|
31
31
|
<script type="module" src="/@vite/client"><\/script>
|
|
32
32
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
33
|
-
`+t.slice(r)}async function
|
|
33
|
+
`+t.slice(r)}async function U(e,n){let r=n.config.root||process.cwd(),i=globalThis.__avalonConfig,a=`_layout.tsx`,o=[],s=e.split(`/`).filter(Boolean),c=[``];for(let e=0;e<s.length;e++)c.push(`/`+s.slice(0,e+1).join(`/`));async function l(e){try{if((await t(e)).isFile()){let t=e.slice(r.length);o.includes(t)||o.push(t)}}catch{}}if(i?.layoutsDir&&await l(`${`${r}/${i.layoutsDir}`}/${a}`),i?.modules){let e=`${r}/${i.modules.dir}`,t=i.modules.layoutsDirName,n=s[0]||``,o=[`home`,`root`,`main`,`index`];if(!n||o.includes(n.toLowerCase()))for(let n of o)await l(`${e}/${n}/${t}/${a}`);else await l(`${e}/${n}/${t}/${a}`)}let u=`${r}/src/layouts`;for(let e of c)await l(e===``?`${u}/${a}`:`${u}${e}/${a}`);return o}async function W(e,n,r){let i=e;i.endsWith(`/`)&&i!==`/`&&(i=i.slice(0,-1)),i===`/`&&(i=`/index`);let a=[`.tsx`,`.ts`,`.jsx`,`.js`,`.mdx`,`.md`],o=r.config.root||process.cwd();async function s(e){try{if((await t(`${o}/${e}`)).isFile())return`/${e}`}catch{}return null}if(n.modules){let t=n.modules.dir,r=n.modules.pagesDirName,o=e.split(`/`).filter(Boolean),c=o[0]||``,l=[`home`,`root`,`main`,`index`],u,d;if(!c||l.includes(c.toLowerCase()))u=`home`,d=i;else{u=c;let e=o.slice(1);d=e.length>0?`/`+e.join(`/`):`/index`}for(let e of a){let n=await s(`${t}/${u}/${r}${d}${e}`);if(n)return n}if(!d.endsWith(`/index`))for(let e of a){let n=await s(`${t}/${u}/${r}${d}/index${e}`);if(n)return n}}let c=n.pagesDir;for(let e of a){let t=await s(`${c}${i}${e}`);if(t)return t}if(!i.endsWith(`/index`))for(let e of a){let t=await s(`${c}${i}/index${e}`);if(t)return t}return null}async function G(e,t,n,r,i){let a=t.metadata||{};try{L||=await i.ssrLoadModule(x(`src/render/ssr.ts`));let o=L;R||=await i.ssrLoadModule(x(`src/core/layout/enhanced-layout-resolver.ts`));let s=R,c={component:()=>typeof e==`function`?e():e,options:{title:a.title||`Avalon App`},frontmatter:t.frontmatter};if(o.renderToHtmlWithLayouts&&s.EnhancedLayoutResolver&&s.EnhancedLayoutResolverUtils)try{let e=i.config.root||process.cwd();if(!globalThis.__avalonLayoutResolver){let t=s.EnhancedLayoutResolver,n=r.layoutsDir||`src/layouts`;globalThis.__avalonLayoutResolver=new t({baseDirectory:`${e}/${n}`,filePattern:`_layout.tsx`,excludeDirectories:[`node_modules`,`.git`,`dist`,`build`],enableWatching:!0,developmentMode:!1,enableCaching:!0,cacheTTL:60*1e3,maxCacheSize:100,enableStreaming:!0,enableErrorBoundaries:!0,enableMetrics:!1,enableDebugInfo:!1,modulesDir:r.modules?`${e}/${r.modules.dir}`:void 0,modulesLayoutsDirName:r.modules?.layoutsDirName})}let t=`http://localhost${n}`,l={params:{},query:{},url:t,request:{method:`GET`,url:t,headers:new Headers}};return await o.renderToHtmlWithLayouts(c,globalThis.__avalonLayoutResolver,l,n,{title:a.title||`Avalon App`},void 0,{suppressWarnings:!0})}catch{}if(o.renderToHtml)return await o.renderToHtml(c,{title:a.title||`Avalon App`},void 0,{suppressWarnings:!0})}catch{}let o=a.title||`Avalon App`,s=a.description||``,c=``;try{let t=await i.ssrLoadModule(`preact-render-to-string`);t.render&&typeof e==`function`&&(c=t.render(e()))}catch{c=`<p>Loading page: ${K(n)}</p>`}return`<!DOCTYPE html>
|
|
34
34
|
<html lang="en">
|
|
35
35
|
<head>
|
|
36
36
|
<meta charset="utf-8">
|
|
37
37
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
38
|
-
<title>${
|
|
39
|
-
${s?`<meta name="description" content="${
|
|
38
|
+
<title>${K(o)}</title>
|
|
39
|
+
${s?`<meta name="description" content="${K(s)}">`:``}
|
|
40
40
|
<script type="module" src="/@vite/client"><\/script>
|
|
41
41
|
</head>
|
|
42
42
|
<body>
|
|
43
43
|
<div id="app">${c}</div>
|
|
44
44
|
<script type="module" src="/src/client/main.js"><\/script>
|
|
45
45
|
</body>
|
|
46
|
-
</html>`}function
|
|
46
|
+
</html>`}function K(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`).replaceAll(`'`,`'`)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/avalon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.48",
|
|
4
4
|
"description": "Multi-framework islands architecture for the modern web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
}
|
|
129
129
|
},
|
|
130
130
|
"dependencies": {
|
|
131
|
-
"@useavalon/core": "
|
|
131
|
+
"@useavalon/core": "^0.1.6",
|
|
132
132
|
"@mdx-js/rollup": "^3.0.0",
|
|
133
133
|
"h3": "^2.0.1-rc.16",
|
|
134
134
|
"marked": "^17.0.4",
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { Component, ComponentChildren } from 'preact';
|
|
2
|
-
import type { LayoutErrorInfo, LayoutContext, LayoutData } from '../types/layout.ts';
|
|
3
|
-
export interface LayoutDataErrorBoundaryProps {
|
|
4
|
-
children: ComponentChildren;
|
|
5
|
-
layoutPath: string;
|
|
6
|
-
context: LayoutContext;
|
|
7
|
-
onError?: (error: Error, errorInfo: LayoutErrorInfo) => void;
|
|
8
|
-
fallbackData?: LayoutData;
|
|
9
|
-
retryLoader?: () => Promise<LayoutData>;
|
|
10
|
-
}
|
|
11
|
-
export interface LayoutDataErrorBoundaryState {
|
|
12
|
-
hasError: boolean;
|
|
13
|
-
error: Error | null;
|
|
14
|
-
errorInfo: LayoutErrorInfo | null;
|
|
15
|
-
retryCount: number;
|
|
16
|
-
isRetrying: boolean;
|
|
17
|
-
fallbackData: LayoutData | null;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Specialized error boundary for layout data loading errors
|
|
21
|
-
* Provides specific handling for data loader failures with retry and fallback mechanisms
|
|
22
|
-
*/
|
|
23
|
-
export declare class LayoutDataErrorBoundary extends Component<LayoutDataErrorBoundaryProps, LayoutDataErrorBoundaryState> {
|
|
24
|
-
private maxRetries;
|
|
25
|
-
constructor(props: LayoutDataErrorBoundaryProps);
|
|
26
|
-
static getDerivedStateFromError(error: Error): Partial<LayoutDataErrorBoundaryState>;
|
|
27
|
-
componentDidCatch(error: Error, errorInfo: {
|
|
28
|
-
componentStack?: string;
|
|
29
|
-
}): void;
|
|
30
|
-
private handleRetry;
|
|
31
|
-
private handleUseFallback;
|
|
32
|
-
private renderErrorUI;
|
|
33
|
-
render(): ComponentChildren;
|
|
34
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Component as e}from"preact";import{jsx as t,jsxs as n}from"preact/jsx-runtime";export class LayoutDataErrorBoundary extends e{maxRetries=3;constructor(e){super(e),this.state={hasError:!1,error:null,errorInfo:null,retryCount:0,isRetrying:!1,fallbackData:e.fallbackData||null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,t){let n={layoutPath:this.props.layoutPath,errorType:`loader`,timestamp:Date.now(),componentStack:t.componentStack,errorBoundary:`LayoutDataErrorBoundary`};this.setState({errorInfo:n}),this.props.onError&&this.props.onError(e,n)}handleRetry=async()=>{if(!(this.state.retryCount>=this.maxRetries||!this.props.retryLoader)){this.setState({isRetrying:!0});try{let e=await this.props.retryLoader();this.setState({hasError:!1,error:null,errorInfo:null,retryCount:this.state.retryCount+1,isRetrying:!1,fallbackData:e})}catch(e){this.setState({retryCount:this.state.retryCount+1,isRetrying:!1,error:e instanceof Error?e:Error(String(e))})}}};handleUseFallback=()=>{this.state.fallbackData&&this.setState({hasError:!1,error:null,errorInfo:null})};renderErrorUI(){let{error:e,retryCount:r,isRetrying:i,fallbackData:a}=this.state,o=r<this.maxRetries&&this.props.retryLoader,s=a!==null,c=typeof process<`u`&&process.env?.NODE_ENV===`development`;return t(`div`,{class:`layout-data-error-boundary`,children:n(`div`,{class:`error-container`,children:[t(`h3`,{children:`Data Loading Error`}),n(`p`,{children:[`Failed to load data for layout: `,this.props.layoutPath]}),n(`div`,{class:`error-actions`,children:[o&&t(`button`,{onClick:this.handleRetry,disabled:i,class:`retry-button`,children:i?`Retrying...`:`Retry (${this.maxRetries-r} left)`}),s&&t(`button`,{onClick:this.handleUseFallback,class:`fallback-button`,children:`Use Cached Data`})]}),c&&e&&n(`details`,{class:`error-details`,children:[t(`summary`,{children:`Error Details (Development)`}),n(`div`,{class:`error-info`,children:[n(`p`,{children:[t(`strong`,{children:`Error:`}),` `,e.message]}),n(`p`,{children:[t(`strong`,{children:`Layout:`}),` `,this.props.layoutPath]}),n(`p`,{children:[t(`strong`,{children:`Retry Count:`}),` `,r]})]}),t(`pre`,{class:`error-stack`,children:e.stack})]})]})})}render(){return this.state.hasError?this.renderErrorUI():this.props.children}}
|