@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
package/dist/mod.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export { addSvelteSSRCSS, getSvelteSSRCSS, getSvelteSSRCSSForHead, getSvelteSSRC
|
|
|
8
8
|
export { detectFramework, detectFrameworkFromSrc, resolveIslandPath } from './src/islands/framework-detection.ts';
|
|
9
9
|
export { analyzeComponentFile, renderComponentSSROnly } from './src/islands/component-analysis.ts';
|
|
10
10
|
export type { Framework, RenderParams, SvelteSSRCSSEntry } from './src/islands/types.ts';
|
|
11
|
+
export { registerHydrationDirective, unregisterHydrationDirective, isCustomDirective, getDirective, getRegisteredDirectives, } from './src/islands/hydration-directives.ts';
|
|
12
|
+
export type { HydrationDirectiveFn, HydrationDirectiveDefinition, } from './src/islands/hydration-directives.ts';
|
|
13
|
+
export { registerBuiltinDirectives } from './src/islands/builtin-directives.ts';
|
|
11
14
|
export { clearCache, clearIslandCache, invalidateCacheForPath, invalidateCacheForFile, getCacheStats, logCacheStats, configureCache, getCacheConfig, } from './src/islands/render-cache.ts';
|
|
12
15
|
export type { CacheConfig as IslandCacheConfig, CacheStats as IslandCacheStats } from './src/islands/render-cache.ts';
|
|
13
16
|
export { discoverIslandDirectories, discoverIslandsInDirectory, discoverAllIslands, isIslandsDirectory, getDefaultIslandsPath, hasDefaultIslandsDirectory, getQualifiedIslandName, parseQualifiedIslandName, IslandRegistry, createIslandRegistry, IslandResolver, createIslandResolver, IslandValidator, createIslandValidator, validateAllIslands, formatValidationError, formatValidationWarning, formatCircularDependency, formatValidationResult, IslandWatcher, createIslandWatcher, ISLAND_FILE_EXTENSIONS, DEFAULT_DISCOVERY_CONFIG, isSupportedIslandExtension, } from './src/islands/discovery/index.ts';
|
|
@@ -38,13 +41,9 @@ export type { EnhancedLayoutResolverOptions } from './src/core/layout/enhanced-l
|
|
|
38
41
|
export type { CacheEntry, CacheStats, CacheConfig } from './src/core/layout/layout-cache-manager.ts';
|
|
39
42
|
export type { LayoutProps, LayoutContext, LayoutData, LayoutRoute, LayoutHandler, LayoutDiscoveryOptions, LayoutConfig, RouteInfo, LayoutRule, LayoutLoader, ResolvedLayout, LayoutCache, LayoutErrorInfo } from './src/core/layout/layout-types.ts';
|
|
40
43
|
export type { EnhancedLayoutContext, IslandState, PersistentIslandProps, PersistentIslandContext, IslandStateSaver, IslandStateLoader, IslandStateClearer, LayoutErrorBoundaryProps, ErrorRecoveryStrategy, LayoutErrorHandler, LayoutRetryFunction, LayoutFallbackRenderer, StreamingLayoutProps, StreamingComponent, StreamingReadyCheck, LayoutMatcherFunction, } from './src/schemas/layout.ts';
|
|
41
|
-
export { IslandPersistence, defaultIslandPersistence } from './src/core/islands/island-persistence.ts';
|
|
42
|
-
export { IslandStateSerializer } from './src/core/islands/island-state-serializer.ts';
|
|
43
|
-
export { createPersistentIslandContext, usePersistentIslandContext, PersistentIslandProvider, } from './src/core/islands/persistent-island-context.tsx';
|
|
44
|
-
export { PersistentIsland } from './src/components/PersistentIsland.tsx';
|
|
45
|
-
export { usePersistentState } from './src/core/islands/use-persistent-state.ts';
|
|
46
|
-
export { LayoutErrorBoundary } from './src/components/LayoutErrorBoundary.tsx';
|
|
47
|
-
export { LayoutDataErrorBoundary } from './src/components/LayoutDataErrorBoundary.tsx';
|
|
48
|
-
export { IslandErrorBoundary, withIslandErrorBoundary } from './src/components/IslandErrorBoundary.tsx';
|
|
49
|
-
export { StreamingErrorBoundary, withStreamingErrorBoundary } from './src/components/StreamingErrorBoundary.tsx';
|
|
50
44
|
export type { ILayoutDiscovery, ILayoutMatcher, ILayoutComposer, IIslandPersistence, ILayoutErrorRecovery, ILayoutStreaming, IEnhancedLayoutResolver, ILayoutComponent, IPersistentIslandComponent, ILayoutErrorBoundaryComponent, IStreamingLayoutComponent, LayoutModule, PageModule, LayoutResolutionContext, LayoutPerformanceMetrics, LayoutDebugInfo, LayoutEventType, LayoutEventData, LayoutEventHandler, ILayoutEventEmitter, } from './src/types/layout.ts';
|
|
45
|
+
export { IslandErrorBoundary, withIslandErrorBoundary } from './src/components/IslandErrorBoundary.tsx';
|
|
46
|
+
export type { IslandErrorBoundaryProps } from './src/components/IslandErrorBoundary.tsx';
|
|
47
|
+
export { LayoutErrorBoundary } from './src/components/LayoutErrorBoundary.tsx';
|
|
48
|
+
export type { LayoutErrorBoundaryProps as LayoutErrorBoundaryComponentProps } from './src/components/LayoutErrorBoundary.tsx';
|
|
49
|
+
export { usePersistentState } from './src/persistence/use-persistent-state.ts';
|
package/dist/mod.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{avalon,getResolvedConfig,getPagesDir,getLayoutsDir,getNitroConfig,isNitroEnabled}from"./src/vite-plugin/plugin.js";export{createNitroIntegration,createNitroCoordinationPlugin,createVirtualModulesPlugin,getViteDevServer,getAvalonConfig,isDevelopmentMode,VIRTUAL_MODULE_IDS,RESOLVED_VIRTUAL_IDS}from"./src/vite-plugin/nitro-integration.js";export{renderToHtml}from"./src/render/ssr.js";export{default as Island,renderIsland}from"./src/islands/island.js";export{addSvelteSSRCSS,getSvelteSSRCSS,getSvelteSSRCSSForHead,getSvelteSSRCSSStats,getSvelteComponentCSS,clearSvelteComponentCSS,generateComponentScopeId}from"./src/islands/css-utils.js";export{detectFramework,detectFrameworkFromSrc,resolveIslandPath}from"./src/islands/framework-detection.js";export{analyzeComponentFile,renderComponentSSROnly}from"./src/islands/component-analysis.js";export{clearCache,clearIslandCache,invalidateCacheForPath,invalidateCacheForFile,getCacheStats,logCacheStats,configureCache,getCacheConfig}from"./src/islands/render-cache.js";export{discoverIslandDirectories,discoverIslandsInDirectory,discoverAllIslands,isIslandsDirectory,getDefaultIslandsPath,hasDefaultIslandsDirectory,getQualifiedIslandName,parseQualifiedIslandName,IslandRegistry,createIslandRegistry,IslandResolver,createIslandResolver,IslandValidator,createIslandValidator,validateAllIslands,formatValidationError,formatValidationWarning,formatCircularDependency,formatValidationResult,IslandWatcher,createIslandWatcher,ISLAND_FILE_EXTENSIONS,DEFAULT_DISCOVERY_CONFIG,isSupportedIslandExtension}from"./src/islands/discovery/index.js";export{loadIntegration,detectAndLoadIntegration,preloadIntegrations,detectFrameworksFromPageContent,DEFAULT_PRELOAD_FRAMEWORKS}from"./src/islands/integration-loader.js";export{registry as integrationRegistry}from"./src/core/integrations/registry.js";export{generateIslandManifest,loadIslandManifest,getIslandBundlePath}from"./src/build/island-manifest.js";export{mdxIslandTransform}from"./src/build/mdx-island-transform.js";export{pageIslandTransform}from"./src/build/page-island-transform.js";export{asIsland}from"./src/types/as-island.js";export{generateIslandTypes,watchAndGenerateTypes}from"./src/build/island-types-generator.js";export async function build(e){throw Error("avalon build() is not available in the published package. Use `vite build` or the Avalon CLI instead.")}export{discoverScopedMiddleware,executeScopedMiddleware,clearMiddlewareCache,invalidateMiddleware,getMatchingMiddleware,clearDiscoveryCache,hasContextValue,getContextValue,setContextValue,getMiddlewareCacheSize}from"./src/middleware/index.js";export*from"./src/layout-system.js";export{
|
|
1
|
+
export{avalon,getResolvedConfig,getPagesDir,getLayoutsDir,getNitroConfig,isNitroEnabled}from"./src/vite-plugin/plugin.js";export{createNitroIntegration,createNitroCoordinationPlugin,createVirtualModulesPlugin,getViteDevServer,getAvalonConfig,isDevelopmentMode,VIRTUAL_MODULE_IDS,RESOLVED_VIRTUAL_IDS}from"./src/vite-plugin/nitro-integration.js";export{renderToHtml}from"./src/render/ssr.js";export{default as Island,renderIsland}from"./src/islands/island.js";export{addSvelteSSRCSS,getSvelteSSRCSS,getSvelteSSRCSSForHead,getSvelteSSRCSSStats,getSvelteComponentCSS,clearSvelteComponentCSS,generateComponentScopeId}from"./src/islands/css-utils.js";export{detectFramework,detectFrameworkFromSrc,resolveIslandPath}from"./src/islands/framework-detection.js";export{analyzeComponentFile,renderComponentSSROnly}from"./src/islands/component-analysis.js";export{registerHydrationDirective,unregisterHydrationDirective,isCustomDirective,getDirective,getRegisteredDirectives}from"./src/islands/hydration-directives.js";export{registerBuiltinDirectives}from"./src/islands/builtin-directives.js";export{clearCache,clearIslandCache,invalidateCacheForPath,invalidateCacheForFile,getCacheStats,logCacheStats,configureCache,getCacheConfig}from"./src/islands/render-cache.js";export{discoverIslandDirectories,discoverIslandsInDirectory,discoverAllIslands,isIslandsDirectory,getDefaultIslandsPath,hasDefaultIslandsDirectory,getQualifiedIslandName,parseQualifiedIslandName,IslandRegistry,createIslandRegistry,IslandResolver,createIslandResolver,IslandValidator,createIslandValidator,validateAllIslands,formatValidationError,formatValidationWarning,formatCircularDependency,formatValidationResult,IslandWatcher,createIslandWatcher,ISLAND_FILE_EXTENSIONS,DEFAULT_DISCOVERY_CONFIG,isSupportedIslandExtension}from"./src/islands/discovery/index.js";export{loadIntegration,detectAndLoadIntegration,preloadIntegrations,detectFrameworksFromPageContent,DEFAULT_PRELOAD_FRAMEWORKS}from"./src/islands/integration-loader.js";export{registry as integrationRegistry}from"./src/core/integrations/registry.js";export{generateIslandManifest,loadIslandManifest,getIslandBundlePath}from"./src/build/island-manifest.js";export{mdxIslandTransform}from"./src/build/mdx-island-transform.js";export{pageIslandTransform}from"./src/build/page-island-transform.js";export{asIsland}from"./src/types/as-island.js";export{generateIslandTypes,watchAndGenerateTypes}from"./src/build/island-types-generator.js";export async function build(e){throw Error("avalon build() is not available in the published package. Use `vite build` or the Avalon CLI instead.")}export{discoverScopedMiddleware,executeScopedMiddleware,clearMiddlewareCache,invalidateMiddleware,getMatchingMiddleware,clearDiscoveryCache,hasContextValue,getContextValue,setContextValue,getMiddlewareCacheSize}from"./src/middleware/index.js";export*from"./src/layout-system.js";export{IslandErrorBoundary,withIslandErrorBoundary}from"./src/components/IslandErrorBoundary.js";export{LayoutErrorBoundary}from"./src/components/LayoutErrorBoundary.js";export{usePersistentState}from"./src/persistence/use-persistent-state.js";
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Client-safe component exports
|
|
2
|
+
* Client-safe component exports.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* you need framework components inside islands. The main entry point
|
|
6
|
-
* re-exports server-only code (nitro, h3, vite plugins) that can't
|
|
7
|
-
* be bundled for the browser.
|
|
4
|
+
* Image optimization, error boundaries, and persistent state for islands.
|
|
8
5
|
*/
|
|
9
|
-
export { PersistentIsland } from '../components/PersistentIsland.tsx';
|
|
10
|
-
export { usePersistentIslandContext, PersistentIslandProvider, createPersistentIslandContext, } from '../core/islands/persistent-island-context.tsx';
|
|
11
|
-
export { usePersistentState } from '../core/islands/use-persistent-state.ts';
|
|
12
|
-
export { IslandPersistence, defaultIslandPersistence } from '../core/islands/island-persistence.ts';
|
|
13
|
-
export { IslandStateSerializer } from '../core/islands/island-state-serializer.ts';
|
|
14
|
-
export { IslandErrorBoundary, withIslandErrorBoundary } from '../components/IslandErrorBoundary.tsx';
|
|
15
|
-
export { LayoutErrorBoundary } from '../components/LayoutErrorBoundary.tsx';
|
|
16
|
-
export { LayoutDataErrorBoundary } from '../components/LayoutDataErrorBoundary.tsx';
|
|
17
|
-
export { StreamingErrorBoundary, withStreamingErrorBoundary } from '../components/StreamingErrorBoundary.tsx';
|
|
18
|
-
export { StreamingLayout, StreamingSuspense, withStreaming, useStreamingState } from '../components/StreamingLayout.tsx';
|
|
19
6
|
export { Image } from '../components/Image.tsx';
|
|
20
7
|
export type { ImageProps } from '../components/Image.tsx';
|
|
21
|
-
export
|
|
8
|
+
export { IslandErrorBoundary, withIslandErrorBoundary } from '../components/IslandErrorBoundary.tsx';
|
|
9
|
+
export type { IslandErrorBoundaryProps } from '../components/IslandErrorBoundary.tsx';
|
|
10
|
+
export { LayoutErrorBoundary } from '../components/LayoutErrorBoundary.tsx';
|
|
11
|
+
export type { LayoutErrorBoundaryProps } from '../components/LayoutErrorBoundary.tsx';
|
|
12
|
+
export { usePersistentState } from '../persistence/use-persistent-state.ts';
|
|
13
|
+
export { registerClientDirective } from './custom-directives.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{PersistentIsland}from"../components/PersistentIsland.tsx";export{usePersistentIslandContext,PersistentIslandProvider,createPersistentIslandContext}from"../core/islands/persistent-island-context.tsx";export{usePersistentState}from"../core/islands/use-persistent-state.js";export{IslandPersistence,defaultIslandPersistence}from"../core/islands/island-persistence.js";export{IslandStateSerializer}from"../core/islands/island-state-serializer.js";export{IslandErrorBoundary,withIslandErrorBoundary}from"../components/IslandErrorBoundary.tsx";export{LayoutErrorBoundary}from"../components/LayoutErrorBoundary.tsx";export{LayoutDataErrorBoundary}from"../components/LayoutDataErrorBoundary.tsx";export{StreamingErrorBoundary,withStreamingErrorBoundary}from"../components/StreamingErrorBoundary.tsx";export{StreamingLayout,StreamingSuspense,withStreaming,useStreamingState}from"../components/StreamingLayout.tsx";export{Image}from"../components/Image.tsx";
|
|
1
|
+
export{PersistentIsland}from"../components/PersistentIsland.tsx";export{usePersistentIslandContext,PersistentIslandProvider,createPersistentIslandContext}from"../core/islands/persistent-island-context.tsx";export{usePersistentState}from"../core/islands/use-persistent-state.js";export{IslandPersistence,defaultIslandPersistence}from"../core/islands/island-persistence.js";export{IslandStateSerializer}from"../core/islands/island-state-serializer.js";export{IslandErrorBoundary,withIslandErrorBoundary}from"../components/IslandErrorBoundary.tsx";export{LayoutErrorBoundary}from"../components/LayoutErrorBoundary.tsx";export{LayoutDataErrorBoundary}from"../components/LayoutDataErrorBoundary.tsx";export{StreamingErrorBoundary,withStreamingErrorBoundary}from"../components/StreamingErrorBoundary.tsx";export{StreamingLayout,StreamingSuspense,withStreaming,useStreamingState}from"../components/StreamingLayout.tsx";export{Image}from"../components/Image.tsx";export{registerClientDirective}from"./custom-directives.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for the client-side custom directives module.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register a client-side directive function at runtime.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerClientDirective(
|
|
9
|
+
name: string,
|
|
10
|
+
fn: (el: HTMLElement, hydrate: () => void, arg?: string) => void,
|
|
11
|
+
): void;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a directive is registered on the client.
|
|
15
|
+
*/
|
|
16
|
+
export declare function hasClientDirective(name: string): boolean;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execute a custom directive for an island element.
|
|
20
|
+
*/
|
|
21
|
+
export declare function executeCustomDirective(
|
|
22
|
+
island: HTMLElement,
|
|
23
|
+
directiveName: string,
|
|
24
|
+
hydrateFn: () => void,
|
|
25
|
+
): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=new Map;export function registerClientDirective(t,n){e.set(t,n)}export function hasClientDirective(t){return e.has(t)}export function executeCustomDirective(t,n,r){let i=t.dataset.conditionArg||void 0;if(e.has(n)){let a=e.get(n);try{a(t,r,i)}catch(e){console.error(`[avalon] Custom directive "${n}" threw:`,e),r()}return!0}let a=t.dataset.directiveScript;if(a){try{let o=Function(`return (`+a+`)`)();e.set(n,o),o(t,r,i)}catch(e){console.error(`[avalon] Failed to execute inline directive "${n}":`,e),r()}return!0}return!1}typeof globalThis<`u`&&(globalThis.__avalon_registerDirective=registerClientDirective);
|
package/dist/src/client/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,
|
|
1
|
+
import{executeCustomDirective as e,hasClientDirective as t}from"./custom-directives.js";document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,n):n();function n(){let n=document.querySelectorAll(`[data-framework]`);n.length!==0&&n.forEach(n=>{try{let c=n.dataset.framework,l=n.dataset.condition||`on:client`;if(n.dataset.renderStrategy===`ssr-only`||!r(n,l))return;l===`on:client`?u(n,c):l===`on:visible`?i(n,c):l===`on:interaction`?a(n,c):l===`on:idle`?o(n,c):l.startsWith(`media:`)?s(n,c,l.slice(6)):n.dataset.customDirective||t(l)?e(n,l,()=>{u(n,c)})||(console.warn(`[avalon] Unknown hydration condition: "${l}". Hydrating immediately.`),u(n,c)):u(n,c)}catch(e){console.error(`Error processing island:`,e),d(n,n.dataset.framework||`unknown`,n.dataset.src||`unknown`,e)}})}function r(e,t){if(!t||t===`on:client`)return!0;if(t.startsWith(`media:`)){let e=t.slice(6);try{return globalThis.matchMedia(e).matches}catch(t){return console.error(`Invalid media query:`,e,t),!0}}return t===`on:visible`||t===`on:interaction`||t===`on:idle`||console.warn(`Unknown hydration condition:`,t),!0}function i(e,t){try{let n=new IntersectionObserver(r=>{r[0].isIntersecting&&(u(e,t),n.disconnect())},{rootMargin:`50px`,threshold:0});n.observe(e)}catch(n){console.error(`Failed to setup intersection observer:`,n),u(e,t)}}function a(e,t){let n=[`click`,`touchstart`,`mouseenter`,`focusin`],r=!1,i=()=>{r||(r=!0,n.forEach(t=>{e.removeEventListener(t,i)}),u(e,t))};try{n.forEach(t=>{e.addEventListener(t,i,{once:!0,passive:!0})})}catch(n){console.error(`Failed to setup interaction observer:`,n),u(e,t)}}function o(e,t){try{`requestIdleCallback`in globalThis?globalThis.requestIdleCallback(()=>{u(e,t)},{timeout:5e3}):document.readyState===`complete`?setTimeout(()=>{u(e,t)},200):globalThis.addEventListener(`load`,()=>{setTimeout(()=>{u(e,t)},200)},{once:!0})}catch(n){console.error(`Failed to setup idle callback:`,n),u(e,t)}}function s(e,t,n){try{let r=globalThis.matchMedia(n);if(r.matches){u(e,t);return}let i=n=>{n.matches&&(u(e,t),r.removeEventListener(`change`,i))};r.addEventListener(`change`,i)}catch(r){console.error(`Failed to setup media query:`,n,r),u(e,t)}}async function c(e){if(![`preact`,`react`,`vue`,`svelte`,`solid`,`lit`,`qwik`].includes(e))throw Error(`Unknown framework: ${e}`);if(import.meta.env?.DEV)return import(`/@useavalon/${e}/client`);switch(e){case`preact`:case`react`:return import(`@useavalon/preact/client`);case`vue`:return import(`@useavalon/vue/client`);case`svelte`:return import(`@useavalon/svelte/client`);case`solid`:return import(`@useavalon/solid/client`);case`lit`:return import(`@useavalon/lit/client`);case`qwik`:return import(`@useavalon/qwik/client`);default:throw Error(`Unknown framework: ${e}`)}}function l(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`&&t.prototype){n=t;break}}n||=e}if(!n)throw Error(`Component ${t} has no default export`);return n}async function u(e,t){if(e.dataset.hydrated)return;let n=e.dataset.src,r=e.dataset.props;if(!n){console.warn(`Island missing data-src attribute`);return}try{let i=r?JSON.parse(r):{};t===`lit`&&(import.meta.env?.DEV?await import(`/@useavalon/lit/client`):await import(`@useavalon/lit/client`));let a=l(await import(n),n);try{let n=await c(t);if(!n.hydrate||typeof n.hydrate!=`function`)throw Error(`Integration ${t} does not export a hydrate function`);await n.hydrate(e,a,i),e.dataset.hydrated=`true`}catch(r){import.meta.env?.DEV&&console.error(`Integration hydration failed for ${t}: ${n}`,r),e.dataset.hydrationStatus=`failed`,e.dataset.hydrationError=r.message,e.dispatchEvent(new CustomEvent(`hydration-error`,{detail:{framework:t,src:n,error:r.message,timestamp:Date.now(),hydrationType:`integration-level`},bubbles:!0}))}}catch(r){console.error(`❌ Critical error hydrating ${t} island ${n}:`,r),d(e,t,n,r)}}function d(e,t,n,r){console.error(`Hydration error for ${t} island:`,{src:n,error:r.message,stack:r.stack}),e.dataset.hydrationStatus=`failed`,e.dataset.renderStrategy=`ssr-only`,e.classList.add(`hydration-failed`),e.dispatchEvent(new CustomEvent(`hydration-error`,{detail:{framework:t,src:n,error:r.message,timestamp:Date.now()},bubbles:!0})),p()&&f(e,t,n,r)}function f(e,t,n,r){let i=document.createElement(`div`);i.className=`hydration-error-indicator`,i.style.cssText=`
|
|
2
2
|
position: absolute;
|
|
3
3
|
top: 0;
|
|
4
4
|
right: 0;
|
|
@@ -11,7 +11,7 @@ document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,e):
|
|
|
11
11
|
z-index: 9999;
|
|
12
12
|
cursor: pointer;
|
|
13
13
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
|
14
|
-
`,i.textContent=`❌ ${t}`,i.title=`Hydration failed: ${n}\n${r.message}\nClick for details`,i.addEventListener(`click`,()=>{alert(`Hydration Error\n\nFramework: ${t}\nComponent: ${n}\n\nError: ${r.message}\n\nStack:\n${r.stack}`)}),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.appendChild(i)}function
|
|
14
|
+
`,i.textContent=`❌ ${t}`,i.title=`Hydration failed: ${n}\n${r.message}\nClick for details`,i.addEventListener(`click`,()=>{alert(`Hydration Error\n\nFramework: ${t}\nComponent: ${n}\n\nError: ${r.message}\n\nStack:\n${r.stack}`)}),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.appendChild(i)}function p(){return import.meta.env?.DEV||import.meta.env?.MODE===`development`||globalThis.location?.hostname===`localhost`||globalThis.location?.hostname===`127.0.0.1`}function m(e){let t=e.dataset.framework,n=e.dataset.src;if(!n)return null;let r={framework:t,src:n,props:e.dataset.props,scrollPosition:{x:globalThis.scrollX,y:globalThis.scrollY},focusedElement:document.activeElement?.id||null};try{t===`vue`&&e.__vue__?r.vueData=structuredClone(e.__vue__.$data||{}):t===`svelte`&&e.__svelte__?r.svelteState=e.__svelte__:t===`lit`&&e.tagName?.includes(`-`)&&(e.querySelector(`[data-lit-element]`)||e)._$litElement$&&(r.litProperties={})}catch(e){console.warn(`Failed to preserve island state:`,e)}return r}function h(e,t){if(t)try{if(t.scrollPosition&&globalThis.scrollTo(t.scrollPosition.x,t.scrollPosition.y),t.focusedElement){let e=document.getElementById(t.focusedElement);e&&e.focus()}e.dataset.framework===`vue`&&t.vueData&&e.__vue__&&Object.assign(e.__vue__.$data,t.vueData)}catch(e){console.warn(`Failed to restore island state:`,e)}}async function g(e,t,n,r){let i=e.dataset.props,a=i?JSON.parse(i):{};t===`lit`&&(import.meta.env?.DEV?await import(`/@useavalon/lit/client`):await import(`@useavalon/lit/client`));let o=l(await import(n),r),s=await c(t);if(!s.hydrate||typeof s.hydrate!=`function`)throw Error(`Integration ${t} does not export a hydrate function`);s.hydrate(e,o,a),e.dataset.hydrated=`true`}function _(e,t,n,r){let i=e.querySelector(`.hmr-error-indicator`);i&&i.remove();let a=document.createElement(`div`);a.className=`hmr-error-indicator`,a.style.cssText=`
|
|
15
15
|
position: absolute;
|
|
16
16
|
top: 0;
|
|
17
17
|
left: 0;
|
|
@@ -36,4 +36,4 @@ document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,e):
|
|
|
36
36
|
cursor: pointer;
|
|
37
37
|
font-size: 14px;
|
|
38
38
|
line-height: 1;
|
|
39
|
-
`,c.onclick=()=>a.remove(),a.appendChild(o),a.appendChild(s),a.appendChild(c),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.insertBefore(a,e.firstChild)}import.meta.hot&&(import.meta.hot.accept(),import(`./hmr-coordinator.js`).then(async({initializeHMR:e,getHMRCoordinator:t})=>{e();let n=t(),r=new Set;document.querySelectorAll(`[data-framework]`).forEach(e=>{let t=e.dataset.framework;t&&r.add(t)});let i=e=>import(`/@useavalon/${e}/client/hmr`).then(t=>t[`${e}Adapter`]),a={react:()=>i(`react`),preact:()=>i(`preact`),vue:()=>i(`vue`),svelte:()=>i(`svelte`),solid:()=>i(`solid`),lit:()=>i(`lit`),qwik:()=>i(`qwik`)};for(let e of r){let t=a[e];if(t)try{let r=await t();n.registerAdapter(e,r)}catch(t){console.warn(`[HMR] Failed to load adapter for ${e}:`,t)}}}).catch(e=>{console.error(`[HMR] Failed to initialize:`,e)}),
|
|
39
|
+
`,c.onclick=()=>a.remove(),a.appendChild(o),a.appendChild(s),a.appendChild(c),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.insertBefore(a,e.firstChild)}import.meta.hot&&(import.meta.hot.accept(),import(`./hmr-coordinator.js`).then(async({initializeHMR:e,getHMRCoordinator:t})=>{e();let n=t(),r=new Set;document.querySelectorAll(`[data-framework]`).forEach(e=>{let t=e.dataset.framework;t&&r.add(t)});let i=e=>import(`/@useavalon/${e}/client/hmr`).then(t=>t[`${e}Adapter`]),a={react:()=>i(`react`),preact:()=>i(`preact`),vue:()=>i(`vue`),svelte:()=>i(`svelte`),solid:()=>i(`solid`),lit:()=>i(`lit`),qwik:()=>i(`qwik`)};for(let e of r){let t=a[e];if(t)try{let r=await t();n.registerAdapter(e,r)}catch(t){console.warn(`[HMR] Failed to load adapter for ${e}:`,t)}}}).catch(e=>{console.error(`[HMR] Failed to initialize:`,e)}),y());async function v(e,t,n,r){try{let{showHMRErrorOverlay:e}=await import(`./hmr-error-overlay.js`);e({framework:t,src:n,error:r,filePath:n})}catch{_(e,t,n,r)}}function y(){if(!import.meta.hot)return;let e=new Map;async function t(e){let t=e.replaceAll(`\\`,`/`),r=document.querySelectorAll(`[data-src*="${t}"], [data-src$="${t}"]`);if(r.length===0){let e=document.querySelectorAll(`[data-src]`);for(let r of e){let e=r.dataset.src;e&&(e.includes(t)||t.includes(e.replace(/^\//,``)))&&await n(r)}return}for(let e of r)await n(e)}async function n(t){let n=t.dataset.framework,r=t.dataset.src;if(!(!r||!n))try{let i=m(t);e.set(r,i),delete t.dataset.hydrated,delete t.dataset.hydrationStatus;let a=t.querySelector(`.hydration-error-indicator`);a&&a.remove();let o=Date.now();await g(t,n,r.includes(`?`)?`${r}&t=${o}`:`${r}?t=${o}`,r);let s=e.get(r);s&&(h(t,s),e.delete(r)),t.dispatchEvent(new CustomEvent(`hmr-update`,{detail:{framework:n,src:r,timestamp:Date.now(),success:!0},bubbles:!0}))}catch(e){console.error(`[HMR] Failed for ${n} island ${r}:`,e),t.dispatchEvent(new CustomEvent(`hmr-error`,{detail:{framework:n,src:r,error:e.message,timestamp:Date.now()},bubbles:!0})),p()&&v(t,n,r,e)}}import.meta.hot.on(`vite:beforeUpdate`,e=>{for(let n of e.updates||[]){let e=n.path||n.acceptedPath;e&&(e.includes(`/islands/`)||e.includes(`\\islands\\`))&&t(e)}}),import.meta.hot.on(`vite:beforeFullReload`,()=>{let e=document.querySelectorAll(`[data-hydrated="true"]`),t={};for(let n of e){let e=n.dataset.src;e&&(t[e]=m(n))}try{sessionStorage.setItem(`__avalon_hmr_states__`,JSON.stringify(t))}catch{}});try{let e=sessionStorage.getItem(`__avalon_hmr_states__`);if(e){let t=JSON.parse(e);sessionStorage.removeItem(`__avalon_hmr_states__`),setTimeout(()=>{for(let[e,n]of Object.entries(t)){let t=document.querySelector(`[data-src="${e}"]`);t&&n&&h(t,n)}},100)}}catch{}}
|
|
@@ -1,20 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
import type
|
|
1
|
+
/** @jsxImportSource preact */
|
|
2
|
+
import { Component, type ComponentChildren, type ComponentType } from 'preact';
|
|
3
|
+
import type { LayoutErrorInfo } from '../schemas/layout.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Props for {@link IslandErrorBoundary}.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <IslandErrorBoundary
|
|
10
|
+
* islandId="counter"
|
|
11
|
+
* isolateError
|
|
12
|
+
* onError={(err, info) => console.error(info.errorBoundary, err)}
|
|
13
|
+
* fallback={(err, id) => <p>Island "{id}" failed: {err.message}</p>}
|
|
14
|
+
* >
|
|
15
|
+
* <Counter />
|
|
16
|
+
* </IslandErrorBoundary>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
3
19
|
export interface IslandErrorBoundaryProps {
|
|
20
|
+
/** The content to render inside the error boundary. */
|
|
4
21
|
children: ComponentChildren;
|
|
22
|
+
/** Unique identifier for the island — used in error reports and DOM attributes. */
|
|
5
23
|
islandId: string;
|
|
24
|
+
/** Called when the island throws. Receives the error and structured error info. */
|
|
6
25
|
onError?: (error: Error, errorInfo: LayoutErrorInfo) => void;
|
|
26
|
+
/** Custom fallback UI. When omitted a default error card is shown. */
|
|
7
27
|
fallback?: (error: Error, islandId: string) => ComponentChildren;
|
|
28
|
+
/** When `true` (default for HOC), the error is caught and isolated. When `false`, it re-throws to parent boundaries. */
|
|
8
29
|
isolateError?: boolean;
|
|
9
30
|
}
|
|
10
|
-
|
|
31
|
+
interface IslandErrorBoundaryState {
|
|
11
32
|
hasError: boolean;
|
|
12
33
|
error: Error | null;
|
|
13
34
|
errorInfo: LayoutErrorInfo | null;
|
|
14
35
|
}
|
|
15
36
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
37
|
+
* Error boundary that wraps individual islands to prevent one broken island
|
|
38
|
+
* from taking down the entire page.
|
|
39
|
+
*
|
|
40
|
+
* Renders a default error card with "Reload" / "Remove" actions, or your
|
|
41
|
+
* custom `fallback` if provided. In development mode, a stack trace is shown.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* <IslandErrorBoundary islandId="cart" isolateError>
|
|
46
|
+
* <CartIsland />
|
|
47
|
+
* </IslandErrorBoundary>
|
|
48
|
+
* ```
|
|
18
49
|
*/
|
|
19
50
|
export declare class IslandErrorBoundary extends Component<IslandErrorBoundaryProps, IslandErrorBoundaryState> {
|
|
20
51
|
constructor(props: IslandErrorBoundaryProps);
|
|
@@ -22,16 +53,24 @@ export declare class IslandErrorBoundary extends Component<IslandErrorBoundaryPr
|
|
|
22
53
|
componentDidCatch(error: Error, errorInfo: {
|
|
23
54
|
componentStack?: string;
|
|
24
55
|
}): void;
|
|
25
|
-
private handleRemoveIsland;
|
|
26
|
-
private handleReloadIsland;
|
|
27
|
-
private renderFallback;
|
|
56
|
+
private readonly handleRemoveIsland;
|
|
57
|
+
private readonly handleReloadIsland;
|
|
28
58
|
render(): ComponentChildren;
|
|
29
59
|
}
|
|
30
60
|
/**
|
|
31
|
-
*
|
|
61
|
+
* HOC that wraps a component with {@link IslandErrorBoundary}.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* const SafeCounter = withIslandErrorBoundary(Counter, 'counter', {
|
|
66
|
+
* isolateError: true,
|
|
67
|
+
* fallback: (err) => <p>Oops: {err.message}</p>,
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
32
70
|
*/
|
|
33
71
|
export declare function withIslandErrorBoundary<P extends object>(WrappedComponent: ComponentType<P>, islandId: string, options?: {
|
|
34
72
|
fallback?: (error: Error, islandId: string) => ComponentChildren;
|
|
35
73
|
isolateError?: boolean;
|
|
36
74
|
onError?: (error: Error, errorInfo: LayoutErrorInfo) => void;
|
|
37
75
|
}): (props: P) => import("preact").JSX.Element;
|
|
76
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Component as e}from"preact";import{jsx as t,jsxs as n}from"preact/jsx-runtime";export class IslandErrorBoundary extends e{constructor(e){super(e),this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,t){let n={layoutPath:`island:${this.props.islandId}`,errorType:`island`,timestamp:Date.now(),componentStack:t.componentStack,errorBoundary:`IslandErrorBoundary`};if(this.setState({errorInfo:n}),this.props.onError
|
|
1
|
+
import{Component as e}from"preact";import{jsx as t,jsxs as n}from"preact/jsx-runtime";export class IslandErrorBoundary extends e{constructor(e){super(e),this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,t){let n={layoutPath:`island:${this.props.islandId}`,errorType:`island`,timestamp:Date.now(),componentStack:t.componentStack,errorBoundary:`IslandErrorBoundary`};if(this.setState({errorInfo:n}),this.props.onError?.(e,n),!this.props.isolateError)throw e}handleRemoveIsland=()=>{document.querySelector(`[data-island-id="${this.props.islandId}"]`)?.remove()};handleReloadIsland=()=>{this.setState({hasError:!1,error:null,errorInfo:null})};render(){if(!this.state.hasError)return this.props.children;let{error:e}=this.state,{fallback:r,islandId:i}=this.props;if(r&&e)return r(e,i);let a=typeof process<`u`&&process.env?.NODE_ENV===`development`;return t(`div`,{className:`island-error-boundary`,"data-island-error":i,children:n(`div`,{className:`island-error-container`,children:[n(`div`,{className:`island-error-header`,children:[t(`span`,{className:`island-error-icon`,children:`⚠️`}),t(`span`,{className:`island-error-title`,children:`Island Error`})]}),n(`p`,{className:`island-error-message`,children:[`An error occurred in island "`,i,`".`]}),n(`div`,{className:`island-error-actions`,children:[t(`button`,{onClick:this.handleReloadIsland,className:`island-reload-button`,children:`Reload Island`}),t(`button`,{onClick:this.handleRemoveIsland,className:`island-remove-button`,children:`Remove Island`})]}),a&&e&&n(`details`,{className:`island-error-details`,children:[t(`summary`,{children:`Error Details (Development)`}),n(`p`,{children:[t(`strong`,{children:`Island ID:`}),` `,i]}),n(`p`,{children:[t(`strong`,{children:`Error:`}),` `,e.message]}),t(`pre`,{className:`island-error-stack`,children:e.stack})]})]})})}}export function withIslandErrorBoundary(e,n,i){return function(a){return t(IslandErrorBoundary,{islandId:n,fallback:i?.fallback,isolateError:i?.isolateError??!0,onError:i?.onError,children:t(e,{...a})})}}
|
|
@@ -1,25 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
/** @jsxImportSource preact */
|
|
2
|
+
import { Component, type ComponentChildren } from 'preact';
|
|
3
|
+
import type { LayoutErrorInfo, LayoutData } from '../schemas/layout.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Props for {@link LayoutErrorBoundary}.
|
|
6
|
+
*
|
|
7
|
+
* @example Basic usage
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <LayoutErrorBoundary onError={(err) => logToSentry(err)}>
|
|
10
|
+
* <DashboardLayout />
|
|
11
|
+
* </LayoutErrorBoundary>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @example Custom fallback with retry
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <LayoutErrorBoundary
|
|
17
|
+
* fallback={(err, retry) => (
|
|
18
|
+
* <div>
|
|
19
|
+
* <p>Layout crashed: {err.message}</p>
|
|
20
|
+
* <button onClick={retry}>Retry</button>
|
|
21
|
+
* </div>
|
|
22
|
+
* )}
|
|
23
|
+
* >
|
|
24
|
+
* <DashboardLayout />
|
|
25
|
+
* </LayoutErrorBoundary>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example Data loading with async retry and cached fallback
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <LayoutErrorBoundary
|
|
31
|
+
* retryLoader={() => fetch('/api/blog').then(r => r.json())}
|
|
32
|
+
* fallbackData={{ posts: [] }}
|
|
33
|
+
* >
|
|
34
|
+
* <BlogLayout />
|
|
35
|
+
* </LayoutErrorBoundary>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
3
38
|
export interface LayoutErrorBoundaryProps {
|
|
39
|
+
/** The layout tree to protect. */
|
|
4
40
|
children: ComponentChildren;
|
|
41
|
+
/** Custom fallback UI. Receives the error and a retry callback. */
|
|
5
42
|
fallback?: (error: Error, retry: () => void) => ComponentChildren;
|
|
43
|
+
/** Called when the layout throws. */
|
|
6
44
|
onError?: (error: Error, errorInfo: LayoutErrorInfo) => void;
|
|
7
|
-
|
|
8
|
-
layoutPath?: string;
|
|
45
|
+
/** Categorises the error for structured logging. Defaults to `'component'`. */
|
|
9
46
|
errorType?: 'component' | 'loader' | 'rendering' | 'island';
|
|
47
|
+
/** Async function to re-attempt data loading. Enables the async retry button (up to 3 attempts). */
|
|
48
|
+
retryLoader?: () => Promise<LayoutData>;
|
|
49
|
+
/** Static data to offer as a "Use Cached Data" option when the loader fails. */
|
|
50
|
+
fallbackData?: LayoutData;
|
|
10
51
|
}
|
|
11
|
-
|
|
52
|
+
interface LayoutErrorBoundaryState {
|
|
12
53
|
hasError: boolean;
|
|
13
54
|
error: Error | null;
|
|
14
55
|
errorInfo: LayoutErrorInfo | null;
|
|
15
56
|
retryCount: number;
|
|
57
|
+
isRetrying: boolean;
|
|
58
|
+
fallbackData: LayoutData | null;
|
|
16
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Error boundary for Avalon layouts.
|
|
62
|
+
*
|
|
63
|
+
* Catches render and data-loading errors in the wrapped layout tree.
|
|
64
|
+
* Shows a default error card with up to 3 retries, or your custom
|
|
65
|
+
* `fallback`. When `retryLoader` is provided, retry is async. When
|
|
66
|
+
* `fallbackData` is provided, a "Use Cached Data" button appears.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* <LayoutErrorBoundary
|
|
71
|
+
* retryLoader={() => fetchDashboardData()}
|
|
72
|
+
* fallbackData={{ widgets: [] }}
|
|
73
|
+
* onError={(err) => logToSentry(err)}
|
|
74
|
+
* >
|
|
75
|
+
* <DashboardLayout />
|
|
76
|
+
* </LayoutErrorBoundary>
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
17
79
|
export declare class LayoutErrorBoundary extends Component<LayoutErrorBoundaryProps, LayoutErrorBoundaryState> {
|
|
18
|
-
private maxRetries;
|
|
80
|
+
private readonly maxRetries;
|
|
19
81
|
constructor(props: LayoutErrorBoundaryProps);
|
|
20
82
|
static getDerivedStateFromError(error: Error): Partial<LayoutErrorBoundaryState>;
|
|
21
|
-
componentDidCatch(error: Error, errorInfo:
|
|
22
|
-
|
|
23
|
-
|
|
83
|
+
componentDidCatch(error: Error, errorInfo: {
|
|
84
|
+
componentStack?: string;
|
|
85
|
+
}): void;
|
|
86
|
+
private readonly handleRetry;
|
|
87
|
+
private readonly handleUseFallback;
|
|
24
88
|
render(): ComponentChildren;
|
|
25
89
|
}
|
|
90
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Component as e}from"preact";import{
|
|
1
|
+
import{Component as e}from"preact";import{jsx as t,jsxs as n}from"preact/jsx-runtime";export class LayoutErrorBoundary 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={errorType:this.props.errorType??`component`,timestamp:Date.now(),componentStack:t.componentStack,errorBoundary:`LayoutErrorBoundary`,layoutPath:void 0};this.setState({errorInfo:n}),this.props.onError?.(e,n)}handleRetry=async()=>{if(!(this.state.retryCount>=this.maxRetries))if(this.props.retryLoader){this.setState({isRetrying:!0});try{let e=await this.props.retryLoader();this.setState(t=>({hasError:!1,error:null,errorInfo:null,retryCount:t.retryCount+1,isRetrying:!1,fallbackData:e}))}catch(e){this.setState(t=>({retryCount:t.retryCount+1,isRetrying:!1,error:e instanceof Error?e:Error(String(e))}))}}else this.setState(e=>({hasError:!1,error:null,errorInfo:null,retryCount:e.retryCount+1}))};handleUseFallback=()=>{this.state.fallbackData&&this.setState({hasError:!1,error:null,errorInfo:null})};render(){if(!this.state.hasError)return this.props.children;let{error:e,retryCount:r,isRetrying:i,fallbackData:a}=this.state;if(this.props.fallback&&e)return this.props.fallback(e,this.handleRetry);let o=r<this.maxRetries,s=typeof process<`u`&&process.env?.NODE_ENV===`development`;return t(`div`,{className:`layout-error-boundary`,children:n(`div`,{className:`error-container`,children:[t(`h2`,{children:`Something went wrong`}),t(`p`,{children:`An error occurred while rendering this layout.`}),n(`div`,{className:`error-actions`,children:[o&&t(`button`,{onClick:this.handleRetry,disabled:i,className:`retry-button`,children:i?`Retrying...`:`Try Again (${this.maxRetries-r} left)`}),a!==null&&t(`button`,{onClick:this.handleUseFallback,className:`fallback-button`,children:`Use Cached Data`})]}),s&&e&&n(`details`,{className:`error-details`,children:[t(`summary`,{children:`Error Details (Development)`}),n(`p`,{children:[t(`strong`,{children:`Error:`}),` `,e.message]}),this.state.errorInfo&&n(`p`,{children:[t(`strong`,{children:`Error Type:`}),` `,this.state.errorInfo.errorType]}),t(`pre`,{className:`error-stack`,children:e.stack})]})]})})}}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in custom hydration directives.
|
|
3
|
+
*
|
|
4
|
+
* These extend the core set (on:client, on:visible, on:interaction, on:idle, media:*)
|
|
5
|
+
* with additional strategies that users can opt into.
|
|
6
|
+
*
|
|
7
|
+
* Import and call `registerBuiltinDirectives()` in your server entry
|
|
8
|
+
* to make them available.
|
|
9
|
+
*
|
|
10
|
+
* @module islands/builtin-directives
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Register all built-in custom directives.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerBuiltinDirectives(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{registerHydrationDirective as e}from"./hydration-directives.js";export function registerBuiltinDirectives(){e(`on:delay`,{name:`on:delay`,script:(e,t,n)=>{setTimeout(t,Number.parseInt(n||`1000`,10))}}),e(`on:event`,{name:`on:event`,script:(e,t,n)=>{if(!n){t();return}let r=()=>{document.removeEventListener(n,r),t()};document.addEventListener(n,r,{once:!0})}}),e(`on:scroll`,{name:`on:scroll`,script:(e,t,n)=>{let r=Number.parseInt(n||`100`,10),i=()=>{globalThis.scrollY>=r&&(globalThis.removeEventListener(`scroll`,i),t())};globalThis.addEventListener(`scroll`,i,{passive:!0}),globalThis.scrollY>=r&&(globalThis.removeEventListener(`scroll`,i),t())}}),e(`on:match`,{name:`on:match`,script:(e,t,n)=>{if(!n){t();return}let r=globalThis.matchMedia(n);if(r.matches){t();return}let i=e=>{e.matches&&(r.removeEventListener(`change`,i),t())};r.addEventListener(`change`,i)}})}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Hydration Directives
|
|
3
|
+
*
|
|
4
|
+
* Allows users to define custom hydration strategies for islands.
|
|
5
|
+
* A directive controls _when_ an island hydrates on the client.
|
|
6
|
+
*
|
|
7
|
+
* Built-in directives (on:client, on:visible, on:interaction, on:idle, media:*)
|
|
8
|
+
* are handled natively in main.js. This module enables user-defined directives
|
|
9
|
+
* that extend the system with arbitrary trigger logic.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // Register a directive that hydrates after a delay
|
|
15
|
+
* registerHydrationDirective('on:delay', {
|
|
16
|
+
* name: 'on:delay',
|
|
17
|
+
* // Runs on the client — receives the island element and a hydrate callback
|
|
18
|
+
* script: (el, hydrate, arg) => {
|
|
19
|
+
* const ms = parseInt(arg || '1000', 10);
|
|
20
|
+
* setTimeout(hydrate, ms);
|
|
21
|
+
* },
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Use in a page via the island prop
|
|
25
|
+
* <Counter island={{ condition: 'on:delay', conditionArg: '2000' }} />
|
|
26
|
+
* ```
|
|
27
|
+
* @module islands/hydration-directives
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* A client-side hydration directive function.
|
|
31
|
+
*
|
|
32
|
+
* Called once per island element when the page initializes.
|
|
33
|
+
* The function must call `hydrate()` exactly once when the island
|
|
34
|
+
* should become interactive.
|
|
35
|
+
*
|
|
36
|
+
* @param el - The `<avalon-island>` DOM element
|
|
37
|
+
* @param hydrate - Callback that triggers hydration. Call it once.
|
|
38
|
+
* @param arg - Optional argument string from `conditionArg` prop
|
|
39
|
+
*/
|
|
40
|
+
export type HydrationDirectiveFn = (el: HTMLElement, hydrate: () => void, arg?: string) => void | (() => void);
|
|
41
|
+
/**
|
|
42
|
+
* Definition of a custom hydration directive.
|
|
43
|
+
*/
|
|
44
|
+
export interface HydrationDirectiveDefinition {
|
|
45
|
+
/** Directive name — must match the `condition` prop value (e.g. "on:delay") */
|
|
46
|
+
name: string;
|
|
47
|
+
/**
|
|
48
|
+
* Client-side script that controls when hydration fires.
|
|
49
|
+
*
|
|
50
|
+
* Can be either:
|
|
51
|
+
* - A function (will be serialized to the client)
|
|
52
|
+
* - A string of JavaScript (inlined directly)
|
|
53
|
+
*/
|
|
54
|
+
script: HydrationDirectiveFn | string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Register a custom hydration directive.
|
|
58
|
+
*
|
|
59
|
+
* @param name - Directive name (e.g. "on:delay", "on:event")
|
|
60
|
+
* @param definition - The directive definition
|
|
61
|
+
*/
|
|
62
|
+
export declare function registerHydrationDirective(name: string, definition: HydrationDirectiveDefinition): void;
|
|
63
|
+
/**
|
|
64
|
+
* Unregister a custom hydration directive.
|
|
65
|
+
*/
|
|
66
|
+
export declare function unregisterHydrationDirective(name: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Check whether a condition string maps to a registered custom directive.
|
|
69
|
+
*/
|
|
70
|
+
export declare function isCustomDirective(condition: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Get a registered directive definition by name.
|
|
73
|
+
*/
|
|
74
|
+
export declare function getDirective(name: string): HydrationDirectiveDefinition | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Get all registered custom directive names.
|
|
77
|
+
*/
|
|
78
|
+
export declare function getRegisteredDirectives(): string[];
|
|
79
|
+
/**
|
|
80
|
+
* Serialize a directive's script to an inline-safe string.
|
|
81
|
+
* Used by the SSR renderer to embed the directive logic in the HTML
|
|
82
|
+
* so the client can execute it without an extra network request.
|
|
83
|
+
*/
|
|
84
|
+
export declare function serializeDirectiveScript(name: string): string | null;
|
|
85
|
+
/**
|
|
86
|
+
* Clear all registered directives. Useful for testing.
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
export declare function clearDirectives(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=new Map;export function registerHydrationDirective(t,n){e.has(t)&&console.warn(`[avalon] Hydration directive "${t}" is already registered. Overwriting.`),e.set(t,n)}export function unregisterHydrationDirective(t){return e.delete(t)}export function isCustomDirective(t){return e.has(t)}export function getDirective(t){return e.get(t)}export function getRegisteredDirectives(){return[...e.keys()]}export function serializeDirectiveScript(t){let n=e.get(t);return n?typeof n.script==`string`?n.script:n.script.toString():null}export function clearDirectives(){e.clear()}
|
|
@@ -6,7 +6,7 @@ declare global {
|
|
|
6
6
|
var __viteDevServer: ViteDevServer | undefined;
|
|
7
7
|
}
|
|
8
8
|
/** Supported hydration conditions for island components */
|
|
9
|
-
export type HydrationCondition = 'on:visible' | 'on:interaction' | 'on:idle' | 'on:client' | `media:${string}`;
|
|
9
|
+
export type HydrationCondition = 'on:visible' | 'on:interaction' | 'on:idle' | 'on:client' | `media:${string}` | `on:${string}`;
|
|
10
10
|
/** Supported framework identifiers (without "unknown") */
|
|
11
11
|
export type FrameworkId = Exclude<Framework, 'unknown'>;
|
|
12
12
|
export interface IslandProps {
|
|
@@ -14,6 +14,8 @@ export interface IslandProps {
|
|
|
14
14
|
src: string;
|
|
15
15
|
/** Hydration condition */
|
|
16
16
|
condition?: HydrationCondition;
|
|
17
|
+
/** Optional argument passed to custom hydration directives */
|
|
18
|
+
conditionArg?: string;
|
|
17
19
|
/** Props to pass to the island component */
|
|
18
20
|
props?: Record<string, unknown>;
|
|
19
21
|
/** Children to render inside the island (for SSR) */
|
|
@@ -37,7 +39,7 @@ export interface IslandProps {
|
|
|
37
39
|
* Uses custom elements instead of div wrappers for cleaner, more semantic markup.
|
|
38
40
|
* Supports intelligent rendering strategy detection to skip hydration for SSR-only components.
|
|
39
41
|
*/
|
|
40
|
-
export default function Island({ src, condition, props, children, ssr, framework, ssrOnly, renderOptions, hydrationData, }: IslandProps): JSX.Element;
|
|
42
|
+
export default function Island({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, hydrationData, }: IslandProps): JSX.Element;
|
|
41
43
|
/**
|
|
42
44
|
* Universal renderIsland function – auto-detects framework and handles SSR + hydration.
|
|
43
45
|
*
|
|
@@ -54,4 +56,4 @@ export default function Island({ src, condition, props, children, ssr, framework
|
|
|
54
56
|
* Error isolation: If SSR fails, returns an error placeholder instead of throwing,
|
|
55
57
|
* allowing the page to continue rendering other islands.
|
|
56
58
|
*/
|
|
57
|
-
export declare function renderIsland({ src, condition, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, }: IslandProps): Promise<JSX.Element>;
|
|
59
|
+
export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, }: IslandProps): Promise<JSX.Element>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{h as e}from"preact";import{detectFramework as t}from"./framework-detection.js";import{analyzeComponentFile as n,renderComponentSSROnly as r}from"./component-analysis.js";import{loadIntegration as i,detectFrameworkFromPath as a}from"./integration-loader.js";import{addUniversalCSS as o}from"./universal-css-collector.js";import{addUniversalHead as s}from"./universal-head-collector.js";import{getIslandBundlePath as c}from"../build/island-manifest.js";import{isDev as l,devLog as u,devWarn as d,devError as f,logRenderTiming as p}from"../utils/dev-logger.js";function
|
|
1
|
+
import{h as e}from"preact";import{detectFramework as t}from"./framework-detection.js";import{analyzeComponentFile as n,renderComponentSSROnly as r}from"./component-analysis.js";import{loadIntegration as i,detectFrameworkFromPath as a}from"./integration-loader.js";import{addUniversalCSS as o}from"./universal-css-collector.js";import{addUniversalHead as s}from"./universal-head-collector.js";import{getIslandBundlePath as c}from"../build/island-manifest.js";import{isDev as l,devLog as u,devWarn as d,devError as f,logRenderTiming as p}from"../utils/dev-logger.js";import{isCustomDirective as m,serializeDirectiveScript as h}from"./hydration-directives.js";function g(e){return`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}`}function _(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 v(e,t,n,r,i){let a={"data-condition":t,"data-src":c(e),"data-props":JSON.stringify(n),"data-render-strategy":`hydrate`,..._(r)};if(m(t)){a[`data-custom-directive`]=t;let e=h(t);e&&(a[`data-directive-script`]=e)}return i&&(a[`data-condition-arg`]=i),a}function y(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 b(e){let t=e.match(/<style[^>]*>([\s\S]*?)<\/style>/i);return t?t[1].trim():null}function x(e,t,n,r){if(e.css&&o(e.css,t,n,e.scopeId),e.head){let i=e.head.trim(),a=y(i);if(a===`style`){let a=b(i);a&&(u(`${r} Extracting CSS from head <style> tag`),o(a,t,n,e.scopeId));return}s(e.head,t,n,a)}}function S(t){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,conditionArg:s,props:c,hydrationData:l,children:d}=t,f={id:n,"data-framework":r},p=i?{"data-render-strategy":`ssr-only`}:v(a,o,c,l,s);r===`lit`&&u(`🔍 [Island Component] ${a} - Lit hydration data:`,{hydrationDataKeys:Object.keys(l),metadata:l.metadata});let m={...f,...p};return typeof d==`string`?e(`avalon-island`,{...m,dangerouslySetInnerHTML:{__html:d}}):e(`avalon-island`,m,d)}function C(t){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,props:s,hydrationData:l,conditionArg:u}=t;if(i)return e(`avalon-island`,{id:n,"data-render-strategy":`ssr-only`,"data-framework":r});let d={id:n,"data-condition":o,"data-src":c(a),"data-props":JSON.stringify(s),"data-render-strategy":`hydrate`,"data-framework":r,..._(l)};if(m(o)){d[`data-custom-directive`]=o;let e=h(o);e&&(d[`data-directive-script`]=e)}return u&&(d[`data-condition-arg`]=u),e(`avalon-island`,d)}export default function w({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:o=t!==`on:client`,framework:s,ssrOnly:c=!1,renderOptions:l={},hydrationData:f={}}){let p=g(e),m=c||!!l.forceSSROnly,h=s||a(e),_=i!=null&&i!==``;return u(`🔍 [Island Component] ${e}`,{ssr:o,ssrOnly:c,hasChildren:_,framework:s,condition:t}),o&&_?S({islandId:p,detectedFramework:h,shouldSkipHydration:m,src:e,condition:t,conditionArg:n,props:r,hydrationData:f,children:i}):(o&&!_&&m&&d(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),C({islandId:p,detectedFramework:h,shouldSkipHydration:m,src:e,condition:t,props:r,hydrationData:f,conditionArg:n}))}function T(t,n){let r=n instanceof Error?n.message:String(n);return f(`🚨 Island SSR failed for ${t}:`,n),n instanceof Error&&n.stack&&f(`Stack trace:`,n.stack),e(`avalon-island`,{id:g(t),"data-src":c(t),"data-ssr-error":r,"data-render-strategy":`client-only`})}async function E({src:e,condition:t,conditionArg:n,props:r,children:a,ssr:o,framework:s,ssrOnly:c,renderOptions:u,component:d}){let p=`🏝️ [${e}]`;if(!o||a)return w({src:e,condition:t,conditionArg:n,props:r,children:a,ssr:o,framework:s,ssrOnly:c,renderOptions:u});let m;try{m=await i(s)}catch(i){return f(`${p} Failed to load ${s} integration:`,i),w({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}try{let i=await m.render({component:d??null,props:r,src:e,condition:t,ssrOnly:c,viteServer:globalThis.__viteDevServer,isDev:l()});return x(i,e,s,p),w({src:e,condition:t,conditionArg:n,props:r,children:i.html,ssr:!0,framework:s,ssrOnly:c,renderOptions:u,hydrationData:c?void 0:i.hydrationData})}catch(i){return f(`${p} Fast path SSR failed:`,i),w({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}}async function D(e,t,r,i){if(t||r.detectScripts===!1)return t;try{let t=await n(e,r);if(t.decision.warnings?.length)for(let e of t.decision.warnings)d(`${i} Analysis warning: ${e}`);return!t.decision.shouldHydrate}catch(e){return d(`${i} Component analysis failed:`,e),t}}async function O(e){return e.endsWith(`.vue`)?`vue`:e.endsWith(`.svelte`)?`svelte`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)||e.endsWith(`.ts`)||e.endsWith(`.js`)?t(e):`unknown`}async function k(e,t,n,r,i,a,o){let s=await O(e),c=s,u=await(await A(s,a)).render({component:o??null,props:n,src:e,condition:t,ssrOnly:r,viteServer:globalThis.__viteDevServer,isDev:l()});return x(u,e,s,a),w({src:e,condition:t,props:n,children:u.html,ssr:!0,framework:c,ssrOnly:r,renderOptions:i,hydrationData:r?void 0:u.hydrationData})}async function A(e,t){try{u(`${t} Loading integration for framework: ${e}`);let n=await i(e);return u(`${t} ✅ Integration loaded successfully`),n}catch(n){throw f(`${t} Failed to load ${e} integration:`,n),Error(`Failed to load integration for framework '${e}'. Make sure @useavalon/${e} is installed.\nInstall it with: deno add @useavalon/${e}`,{cause:n})}}export async function renderIsland({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:a=t!==`on:client`,framework:o,ssrOnly:s=!1,renderOptions:c={},component:u}){let d=l()?performance.now():0,f=`🏝️ [${e}]`;try{return s&&!a&&(a=!0),o?await E({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:o,ssrOnly:s,renderOptions:c,component:u}):await j({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,ssrOnly:s,renderOptions:c,logPrefix:f,component:u})}catch(t){return T(e,t)}finally{l()&&p(e,performance.now()-d)}}async function j(e){let{src:t,condition:n,conditionArg:r,props:i,children:a,ssr:o,ssrOnly:s,renderOptions:c,logPrefix:l,component:d}=e;if(u(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:o,ssrOnly:s,hasChildren:!!a,condition:n}),await D(t,s,c,l))return M(t,n,i,a,o,c,l);if(!o||a)return w({src:t,condition:n,conditionArg:r,props:i,children:a,ssr:o,renderOptions:c});try{return await k(t,n,i,s,c,l,d)}catch(e){let a=await O(t);return f(`${l} Framework rendering failed:`,e),w({src:t,condition:n,conditionArg:r,props:i,ssr:!1,framework:a,renderOptions:c})}}function M(e,t,n,i,a,o,s){return a&&!i?r({src:e,condition:t,props:n,renderOptions:o}).catch(r=>(f(`${s} SSR failed for SSR-only component:`,r),w({src:e,condition:t,props:n,ssr:!1,ssrOnly:!0,renderOptions:o}))):w({src:e,condition:t,props:n,children:i,ssr:a,ssrOnly:!0,renderOptions:o})}
|
|
@@ -12,8 +12,10 @@ export type Framework = "solid" | "vue" | "svelte" | "preact" | "react" | "lit"
|
|
|
12
12
|
export interface IslandProps {
|
|
13
13
|
/** Path to the island component (e.g., "/islands/Counter.tsx") */
|
|
14
14
|
src: string;
|
|
15
|
-
/** Hydration condition */
|
|
16
|
-
condition?: "on:visible" | "on:interaction" | "on:idle" | "on:client" | `media:${string}`;
|
|
15
|
+
/** Hydration condition (built-in or custom directive name) */
|
|
16
|
+
condition?: "on:visible" | "on:interaction" | "on:idle" | "on:client" | `media:${string}` | `on:${string}`;
|
|
17
|
+
/** Optional argument passed to custom hydration directives */
|
|
18
|
+
conditionArg?: string;
|
|
17
19
|
/** Props to pass to the island component */
|
|
18
20
|
props?: Record<string, unknown>;
|
|
19
21
|
/** Children to render inside the island (for SSR) */
|