@useavalon/avalon 0.1.47 → 0.1.49
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 +48 -49
- package/dist/mod.js +1 -1
- package/dist/src/client/components.d.ts +10 -18
- 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 +75 -10
- 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/integration-loader.d.ts +2 -2
- package/dist/src/islands/island.d.ts +11 -9
- package/dist/src/islands/island.js +1 -1
- package/dist/src/islands/types.d.ts +4 -2
- package/dist/src/layout-system.d.ts +14 -30
- package/dist/src/layout-system.js +1 -1
- package/dist/src/nitro/config.d.ts +3 -3
- package/dist/src/nitro/renderer.d.ts +18 -5
- package/dist/src/nitro/renderer.js +11 -11
- package/dist/src/persistence/island-state-serializer.d.ts +9 -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/core.d.ts +2 -2
- package/dist/src/schemas/layout.d.ts +5 -5
- package/dist/src/schemas/layout.js +1 -1
- package/dist/src/schemas/routing/index.d.ts +2 -2
- package/dist/src/schemas/routing.d.ts +4 -4
- package/dist/src/types/island-prop.d.ts +14 -6
- package/dist/src/types/layout.d.ts +11 -19
- 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
|
@@ -4,9 +4,9 @@ import type { ComponentType } from 'preact/compat';
|
|
|
4
4
|
* Route Type Schema - Defines the different types of routes supported
|
|
5
5
|
*/
|
|
6
6
|
export declare const RouteTypeSchema: z.ZodEnum<{
|
|
7
|
+
group: "group";
|
|
7
8
|
static: "static";
|
|
8
9
|
index: "index";
|
|
9
|
-
group: "group";
|
|
10
10
|
dynamic: "dynamic";
|
|
11
11
|
"catch-all": "catch-all";
|
|
12
12
|
}>;
|
|
@@ -17,9 +17,9 @@ export declare const FileSystemRouteSchema: z.ZodObject<{
|
|
|
17
17
|
pattern: z.ZodAny;
|
|
18
18
|
filePath: z.ZodString;
|
|
19
19
|
routeType: z.ZodEnum<{
|
|
20
|
+
group: "group";
|
|
20
21
|
static: "static";
|
|
21
22
|
index: "index";
|
|
22
|
-
group: "group";
|
|
23
23
|
dynamic: "dynamic";
|
|
24
24
|
"catch-all": "catch-all";
|
|
25
25
|
}>;
|
|
@@ -266,9 +266,9 @@ export declare const RouteHandlerSchema: z.ZodObject<{
|
|
|
266
266
|
metadata: z.ZodObject<{
|
|
267
267
|
filePath: z.ZodString;
|
|
268
268
|
routeType: z.ZodEnum<{
|
|
269
|
+
group: "group";
|
|
269
270
|
static: "static";
|
|
270
271
|
index: "index";
|
|
271
|
-
group: "group";
|
|
272
272
|
dynamic: "dynamic";
|
|
273
273
|
"catch-all": "catch-all";
|
|
274
274
|
}>;
|
|
@@ -284,9 +284,9 @@ export declare const RouteCacheEntrySchema: z.ZodObject<{
|
|
|
284
284
|
pattern: z.ZodAny;
|
|
285
285
|
filePath: z.ZodString;
|
|
286
286
|
routeType: z.ZodEnum<{
|
|
287
|
+
group: "group";
|
|
287
288
|
static: "static";
|
|
288
289
|
index: "index";
|
|
289
|
-
group: "group";
|
|
290
290
|
dynamic: "dynamic";
|
|
291
291
|
"catch-all": "catch-all";
|
|
292
292
|
}>;
|
|
@@ -11,10 +11,18 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
export interface IslandDirective {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
/** Hydration condition (built-in or custom directive name) */
|
|
15
|
+
condition?:
|
|
16
|
+
| "on:visible"
|
|
17
|
+
| "on:interaction"
|
|
18
|
+
| "on:idle"
|
|
19
|
+
| "on:client"
|
|
20
|
+
| `media:${string}`
|
|
21
|
+
| `on:${string}`;
|
|
22
|
+
/** Optional argument passed to custom hydration directives */
|
|
23
|
+
conditionArg?: string;
|
|
24
|
+
/** Force SSR-only rendering without client hydration */
|
|
25
|
+
ssrOnly?: boolean;
|
|
26
|
+
/** Whether to render server-side (default: true) */
|
|
27
|
+
ssr?: boolean;
|
|
20
28
|
}
|
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
export type {
|
|
4
|
-
export type
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
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
|
-
export { EnhancedLayoutResolver, createEnhancedLayoutResolver, EnhancedLayoutResolverUtils, type EnhancedLayoutResolverOptions, } from '../core/layout/enhanced-layout-resolver.ts';
|
|
18
|
-
export { LayoutCacheManager, type CacheEntry, type CacheStats, type CacheConfig, } from '../core/layout/layout-cache-manager.ts';
|
|
1
|
+
import type { ComponentChildren, ComponentType } from "preact";
|
|
2
|
+
import type { ErrorRecoveryStrategy, IslandState, LayoutConfig, LayoutContext, LayoutDiscoveryOptions, LayoutErrorBoundaryProps, LayoutErrorInfo, LayoutHandler, LayoutLoader, LayoutProps, LayoutRoute, LayoutRule, PersistentIslandProps, ResolvedLayout, RouteInfo, StreamingComponent, StreamingLayoutProps } from "../schemas/layout.ts";
|
|
3
|
+
export type { ComponentChildren, ComponentType } from "preact";
|
|
4
|
+
export { createEnhancedLayoutResolver, EnhancedLayoutResolver, type EnhancedLayoutResolverOptions, EnhancedLayoutResolverUtils, } from "../core/layout/enhanced-layout-resolver.ts";
|
|
5
|
+
export { type CacheConfig, type CacheEntry, type CacheStats, LayoutCacheManager, } from "../core/layout/layout-cache-manager.ts";
|
|
6
|
+
export { LayoutComposer } from "../core/layout/layout-composer.ts";
|
|
7
|
+
export { LayoutDataLoader } from "../core/layout/layout-data-loader.ts";
|
|
8
|
+
export { LayoutDiscovery } from "../core/layout/layout-discovery.ts";
|
|
9
|
+
export { LayoutMatcher as LayoutMatcherClass } from "../core/layout/layout-matcher.ts";
|
|
10
|
+
export type { EnhancedLayoutContext, ErrorRecoveryStrategy, IslandState, IslandStateClearer, IslandStateLoader, IslandStateSaver, LayoutCache, LayoutConfig, LayoutContext, LayoutData, LayoutDiscoveryOptions, LayoutErrorBoundaryProps, LayoutErrorHandler, LayoutErrorInfo, LayoutFallbackRenderer, LayoutHandler, LayoutLoader, LayoutMatcherFunction, LayoutProps, LayoutRetryFunction, LayoutRoute, LayoutRule, PersistentIslandContext, PersistentIslandProps, ResolvedLayout, RouteInfo, StreamingComponent, StreamingLayoutProps, StreamingReadyCheck, } from "../schemas/layout.ts";
|
|
19
11
|
/**
|
|
20
12
|
* Layout Discovery Interface
|
|
21
13
|
*/
|
|
@@ -159,7 +151,7 @@ export interface LayoutDebugInfo {
|
|
|
159
151
|
size: number;
|
|
160
152
|
};
|
|
161
153
|
}
|
|
162
|
-
export type LayoutEventType =
|
|
154
|
+
export type LayoutEventType = "layout-discovered" | "layout-loaded" | "layout-rendered" | "layout-error" | "layout-cached" | "island-state-saved" | "island-state-loaded" | "streaming-started" | "streaming-completed";
|
|
163
155
|
export interface LayoutEventData {
|
|
164
156
|
type: LayoutEventType;
|
|
165
157
|
timestamp: number;
|
package/dist/src/types/layout.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{
|
|
1
|
+
export{createEnhancedLayoutResolver,EnhancedLayoutResolver,EnhancedLayoutResolverUtils}from"../core/layout/enhanced-layout-resolver.js";export{LayoutCacheManager}from"../core/layout/layout-cache-manager.js";export{LayoutComposer}from"../core/layout/layout-composer.js";export{LayoutDataLoader}from"../core/layout/layout-data-loader.js";export{LayoutDiscovery}from"../core/layout/layout-discovery.js";export{LayoutMatcher as LayoutMatcherClass}from"../core/layout/layout-matcher.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.49",
|
|
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}}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource preact */
|
|
2
|
-
import type { ComponentChildren } from 'preact';
|
|
3
|
-
import { type IslandPersistence } from '../core/islands/island-persistence.ts';
|
|
4
|
-
interface PersistentIslandProps {
|
|
5
|
-
/** Unique ID used as the storage key for this island's state */
|
|
6
|
-
persistentId: string;
|
|
7
|
-
/** The island component(s) to wrap with persistence context */
|
|
8
|
-
children: ComponentChildren;
|
|
9
|
-
/** Custom persistence instance (defaults to sessionStorage-backed) */
|
|
10
|
-
persistence?: IslandPersistence;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* PersistentIsland — provides automatic state persistence context to child islands.
|
|
14
|
-
*
|
|
15
|
-
* Wrap any island with this component and use `usePersistentIslandContext()` inside
|
|
16
|
-
* the island to get `saveState`, `loadState`, and `clearState` functions.
|
|
17
|
-
*
|
|
18
|
-
* Usage in a page:
|
|
19
|
-
* ```tsx
|
|
20
|
-
* <PersistentIsland persistentId="my-counter" island={{ condition: 'on:client' }}>
|
|
21
|
-
* <MyCounter />
|
|
22
|
-
* </PersistentIsland>
|
|
23
|
-
* ```
|
|
24
|
-
*
|
|
25
|
-
* Usage inside the island:
|
|
26
|
-
* ```tsx
|
|
27
|
-
* import { usePersistentIslandContext } from '@useavalon/avalon';
|
|
28
|
-
*
|
|
29
|
-
* function MyCounter() {
|
|
30
|
-
* const { saveState, loadState, clearState } = usePersistentIslandContext();
|
|
31
|
-
* // ...
|
|
32
|
-
* }
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
export declare function PersistentIsland({ persistentId, children, persistence, }: Readonly<PersistentIslandProps>): import("preact").JSX.Element;
|
|
36
|
-
export default PersistentIsland;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{PersistentIslandProvider as e}from"../core/islands/persistent-island-context.js";import{defaultIslandPersistence as t}from"../core/islands/island-persistence.js";import{jsx as n}from"preact/jsx-runtime";export function PersistentIsland({persistentId:r,children:i,persistence:a=t}){return n(e,{persistentId:r,persistence:a,children:n(`div`,{"data-persistent-id":r,children:i})})}export default PersistentIsland;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StreamingErrorBoundary - Error boundary component for streaming contexts
|
|
3
|
-
*
|
|
4
|
-
* This component provides error isolation for Suspense boundaries in streaming SSR.
|
|
5
|
-
* It ensures that errors in one component don't break the entire page.
|
|
6
|
-
*/
|
|
7
|
-
import { Component, type ComponentChildren } from 'preact';
|
|
8
|
-
export interface StreamingErrorBoundaryProps {
|
|
9
|
-
children: ComponentChildren;
|
|
10
|
-
fallback?: (error: Error, retry: () => void) => ComponentChildren;
|
|
11
|
-
onError?: (error: Error, errorInfo: any) => void;
|
|
12
|
-
componentId?: string;
|
|
13
|
-
isolateError?: boolean;
|
|
14
|
-
}
|
|
15
|
-
export interface StreamingErrorBoundaryState {
|
|
16
|
-
hasError: boolean;
|
|
17
|
-
error: Error | null;
|
|
18
|
-
errorInfo: any;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Error boundary component for streaming contexts
|
|
22
|
-
*
|
|
23
|
-
* Wraps Suspense boundaries to provide error isolation and recovery.
|
|
24
|
-
* Prevents errors in one component from breaking the entire page.
|
|
25
|
-
*/
|
|
26
|
-
export declare class StreamingErrorBoundary extends Component<StreamingErrorBoundaryProps, StreamingErrorBoundaryState> {
|
|
27
|
-
constructor(props: StreamingErrorBoundaryProps);
|
|
28
|
-
static getDerivedStateFromError(error: Error): Partial<StreamingErrorBoundaryState>;
|
|
29
|
-
componentDidCatch(error: Error, errorInfo: any): void;
|
|
30
|
-
private handleRetry;
|
|
31
|
-
private renderFallback;
|
|
32
|
-
render(): ComponentChildren;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Higher-order component to wrap components with streaming error boundaries
|
|
36
|
-
*/
|
|
37
|
-
export declare function withStreamingErrorBoundary<P extends object>(WrappedComponent: (props: P) => ComponentChildren, options?: {
|
|
38
|
-
fallback?: (error: Error, retry: () => void) => ComponentChildren;
|
|
39
|
-
componentId?: string;
|
|
40
|
-
isolateError?: boolean;
|
|
41
|
-
onError?: (error: Error, errorInfo: any) => void;
|
|
42
|
-
}): (props: P) => import("preact").JSX.Element;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Component as e}from"preact";import{jsx as t,jsxs as n}from"preact/jsx-runtime";export class StreamingErrorBoundary 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){if(this.setState({errorInfo:t}),console.error(`[StreamingErrorBoundary] Caught error:`,{componentId:this.props.componentId,error:e.message,stack:e.stack,componentStack:t.componentStack}),this.props.onError&&this.props.onError(e,t),!this.props.isolateError)throw e}handleRetry=()=>{this.setState({hasError:!1,error:null,errorInfo:null})};renderFallback(){let{error:e}=this.state,{fallback:r,componentId:i}=this.props;if(r&&e)return r(e,this.handleRetry);let a=typeof process<`u`&&process.env?.NODE_ENV!==`production`;return n(`div`,{class:`streaming-error-boundary`,"data-error-boundary":`true`,"data-component-id":i,style:{background:`#fff3cd`,border:`2px solid #ffc107`,borderRadius:`8px`,padding:`20px`,margin:`20px 0`,fontFamily:`system-ui, -apple-system, sans-serif`},children:[n(`div`,{class:`error-boundary-header`,style:{display:`flex`,alignItems:`center`,gap:`10px`,marginBottom:`10px`},children:[t(`span`,{style:{fontSize:`24px`},children:`⚠️`}),t(`h3`,{style:{margin:0,color:`#856404`},children:`Component Error`})]}),t(`p`,{style:{margin:`10px 0`,color:`#856404`},children:`An error occurred while rendering this component. The rest of the page should work normally.`}),t(`button`,{onClick:this.handleRetry,style:{background:`#ffc107`,border:`none`,borderRadius:`4px`,padding:`8px 16px`,cursor:`pointer`,fontWeight:`bold`,color:`#856404`,marginTop:`10px`},children:`Retry`}),a&&e&&n(`details`,{style:{marginTop:`15px`},children:[t(`summary`,{style:{cursor:`pointer`,color:`#856404`,fontWeight:`bold`},children:`Error Details (Development Mode)`}),n(`div`,{style:{marginTop:`10px`},children:[i&&n(`p`,{children:[t(`strong`,{children:`Component ID:`}),` `,i]}),n(`p`,{children:[t(`strong`,{children:`Error:`}),` `,e.message]}),e.stack&&t(`pre`,{style:{background:`#f5f5f5`,padding:`10px`,borderRadius:`4px`,overflowX:`auto`,fontSize:`12px`,marginTop:`10px`},children:e.stack}),this.state.errorInfo?.componentStack&&n(`div`,{children:[t(`p`,{children:t(`strong`,{children:`Component Stack:`})}),t(`pre`,{style:{background:`#f5f5f5`,padding:`10px`,borderRadius:`4px`,overflowX:`auto`,fontSize:`12px`,marginTop:`10px`},children:this.state.errorInfo.componentStack})]})]})]})]})}render(){return this.state.hasError?this.renderFallback():this.props.children}}export function withStreamingErrorBoundary(e,n){return function(i){return t(StreamingErrorBoundary,{componentId:n?.componentId,fallback:n?.fallback,isolateError:n?.isolateError??!0,onError:n?.onError,children:t(e,{...i})})}}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { ComponentChildren, ComponentType } from 'preact';
|
|
2
|
-
import type { StreamingLayoutProps } from '../types/layout.ts';
|
|
3
|
-
/**
|
|
4
|
-
* Streaming Layout Component Props
|
|
5
|
-
*/
|
|
6
|
-
export interface StreamingLayoutComponentProps extends StreamingLayoutProps {
|
|
7
|
-
/**
|
|
8
|
-
* Component to render when ready
|
|
9
|
-
*/
|
|
10
|
-
component: ComponentType<any>;
|
|
11
|
-
/**
|
|
12
|
-
* Props to pass to the component
|
|
13
|
-
*/
|
|
14
|
-
componentProps?: any;
|
|
15
|
-
/**
|
|
16
|
-
* Function to check if component is ready
|
|
17
|
-
*/
|
|
18
|
-
isReady?: () => Promise<boolean>;
|
|
19
|
-
/**
|
|
20
|
-
* Timeout for loading (ms)
|
|
21
|
-
*/
|
|
22
|
-
timeout?: number;
|
|
23
|
-
/**
|
|
24
|
-
* Error boundary fallback
|
|
25
|
-
*/
|
|
26
|
-
onError?: (error: Error) => ComponentChildren;
|
|
27
|
-
/**
|
|
28
|
-
* Loading state callback
|
|
29
|
-
*/
|
|
30
|
-
onLoadingChange?: (isLoading: boolean) => void;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Streaming Layout Component with Suspense-like behavior
|
|
34
|
-
*/
|
|
35
|
-
export declare function StreamingLayout(props: StreamingLayoutComponentProps): ComponentChildren;
|
|
36
|
-
/**
|
|
37
|
-
* Suspense-like boundary for streaming components
|
|
38
|
-
*/
|
|
39
|
-
export interface StreamingSuspenseProps {
|
|
40
|
-
/**
|
|
41
|
-
* Fallback to show while loading
|
|
42
|
-
*/
|
|
43
|
-
fallback?: ComponentChildren;
|
|
44
|
-
/**
|
|
45
|
-
* Children components
|
|
46
|
-
*/
|
|
47
|
-
children: ComponentChildren;
|
|
48
|
-
/**
|
|
49
|
-
* Priority for this suspense boundary
|
|
50
|
-
*/
|
|
51
|
-
priority?: 'high' | 'medium' | 'low';
|
|
52
|
-
/**
|
|
53
|
-
* Timeout for all children (ms)
|
|
54
|
-
*/
|
|
55
|
-
timeout?: number;
|
|
56
|
-
/**
|
|
57
|
-
* Error boundary for failed components
|
|
58
|
-
*/
|
|
59
|
-
onError?: (error: Error) => ComponentChildren;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Streaming Suspense Boundary Component
|
|
63
|
-
*/
|
|
64
|
-
export declare function StreamingSuspense(props: StreamingSuspenseProps): ComponentChildren;
|
|
65
|
-
/**
|
|
66
|
-
* Higher-order component to add streaming capabilities
|
|
67
|
-
*/
|
|
68
|
-
export declare function withStreaming<P extends object>(WrappedComponent: ComponentType<P>, streamingOptions?: {
|
|
69
|
-
fallback?: ComponentChildren;
|
|
70
|
-
priority?: 'high' | 'medium' | 'low';
|
|
71
|
-
isReady?: () => Promise<boolean>;
|
|
72
|
-
timeout?: number;
|
|
73
|
-
}): (props: P) => import("preact").JSX.Element;
|
|
74
|
-
/**
|
|
75
|
-
* Hook for streaming component state
|
|
76
|
-
*/
|
|
77
|
-
export declare function useStreamingState(isReady?: () => Promise<boolean>, timeout?: number): {
|
|
78
|
-
retry: () => void;
|
|
79
|
-
isReady: boolean;
|
|
80
|
-
isLoading: boolean;
|
|
81
|
-
error: Error | null;
|
|
82
|
-
hasTimedOut: boolean;
|
|
83
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import{Component as e}from"preact";import{useState as t,useEffect as n,useRef as r}from"preact/hooks";import{jsxs as i,jsx as a}from"preact/jsx-runtime";export function StreamingLayout(e){let{component:o,componentProps:c={},children:l,fallback:u,priority:d=`medium`,isReady:f,timeout:p=5e3,onError:m,onLoadingChange:h}=e;if(typeof window>`u`)return l;let[g,_]=t({isReady:!1,isLoading:!0,error:null,hasTimedOut:!1}),v=r(),y=r(!0);return n(()=>()=>{y.current=!1},[]),n(()=>{if(!f){_(e=>({...e,isReady:!0,isLoading:!1})),h?.(!1);return}let e=!1;return(async()=>{try{p>0&&(v.current=setTimeout(()=>{!e&&y.current&&(_(e=>({...e,hasTimedOut:!0,isLoading:!1})),h?.(!1))},p));let t=await f();if(e||!y.current)return;v.current&&clearTimeout(v.current),_(e=>({...e,isReady:t,isLoading:!1,hasTimedOut:!1})),h?.(!1)}catch(t){if(e||!y.current)return;v.current&&clearTimeout(v.current),_(e=>({...e,error:t,isLoading:!1})),h?.(!1)}})(),()=>{e=!0,v.current&&clearTimeout(v.current)}},[f,p,h]),g.error?m?m(g.error):i(`div`,{class:`streaming-error`,"data-priority":d,children:[i(`p`,{children:[`Failed to load component: `,g.error.message]}),a(`button`,{onClick:()=>{_({isReady:!1,isLoading:!0,error:null,hasTimedOut:!1}),h?.(!0)},children:`Retry`})]}):g.hasTimedOut?a(`div`,{class:`streaming-timeout`,"data-priority":d,children:u||i(`div`,{class:`timeout-message`,children:[a(`p`,{children:`Component loading timed out`}),a(`button`,{onClick:()=>{_({isReady:!1,isLoading:!0,error:null,hasTimedOut:!1}),h?.(!0)},children:`Retry`})]})}):g.isLoading||!g.isReady?a(`div`,{class:`streaming-loading`,"data-priority":d,children:u||a(s,{priority:d})}):a(`div`,{class:`streaming-ready`,"data-priority":d,children:a(o,{...c,children:l})})}function s({priority:e}){return i(`div`,{class:`streaming-skeleton ${`skeleton-${e}`}`,children:[i(`div`,{class:`skeleton-content`,children:[a(`div`,{class:`skeleton-line skeleton-line-1`}),a(`div`,{class:`skeleton-line skeleton-line-2`}),a(`div`,{class:`skeleton-line skeleton-line-3`})]}),a(`style`,{children:`
|
|
2
|
-
.streaming-skeleton {
|
|
3
|
-
padding: 1rem;
|
|
4
|
-
border-radius: 0.5rem;
|
|
5
|
-
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|
6
|
-
background-size: 200% 100%;
|
|
7
|
-
animation: skeleton-loading 1.5s infinite;
|
|
8
|
-
}
|
|
9
|
-
.skeleton-content {
|
|
10
|
-
display: flex;
|
|
11
|
-
flex-direction: column;
|
|
12
|
-
gap: 0.5rem;
|
|
13
|
-
}
|
|
14
|
-
.skeleton-line {
|
|
15
|
-
height: 1rem;
|
|
16
|
-
background: rgba(255, 255, 255, 0.8);
|
|
17
|
-
border-radius: 0.25rem;
|
|
18
|
-
}
|
|
19
|
-
.skeleton-line-1 { width: 100%; }
|
|
20
|
-
.skeleton-line-2 { width: 75%; }
|
|
21
|
-
.skeleton-line-3 { width: 50%; }
|
|
22
|
-
.skeleton-high { border-left: 4px solid #ef4444; }
|
|
23
|
-
.skeleton-medium { border-left: 4px solid #f59e0b; }
|
|
24
|
-
.skeleton-low { border-left: 4px solid #10b981; }
|
|
25
|
-
@keyframes skeleton-loading {
|
|
26
|
-
0% { background-position: 200% 0; }
|
|
27
|
-
100% { background-position: -200% 0; }
|
|
28
|
-
}
|
|
29
|
-
`})]})}export function StreamingSuspense(e){let{fallback:o,children:c,priority:u=`medium`,timeout:d=5e3,onError:f}=e,[p,m]=t(!0),[h,g]=t(null),[_,v]=t(!1),y=r(),b=r(!0);n(()=>()=>{b.current=!1},[]),n(()=>(d>0&&(y.current=setTimeout(()=>{b.current&&p&&(v(!0),m(!1))},d)),()=>{y.current&&clearTimeout(y.current)}),[d,p]);let x=e=>{g(e),m(!1),y.current&&clearTimeout(y.current)};return h?f?f(h):i(`div`,{class:`streaming-suspense-error`,"data-priority":u,children:[i(`p`,{children:[`Suspense boundary error: `,h.message]}),a(`button`,{onClick:()=>{g(null),m(!0),v(!1)},children:`Retry`})]}):_?a(`div`,{class:`streaming-suspense-timeout`,"data-priority":u,children:o||i(`div`,{class:`suspense-timeout-message`,children:[a(`p`,{children:`Suspense boundary timed out`}),a(`button`,{onClick:()=>{v(!1),m(!0)},children:`Retry`})]})}):p?a(`div`,{class:`streaming-suspense-loading`,"data-priority":u,children:o||a(s,{priority:u})}):a(`div`,{class:`streaming-suspense-ready`,"data-priority":u,children:a(l,{onError:x,children:c})})}class l extends e{constructor(e){super(e),this.state={hasError:!1}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,t){console.error(`StreamingErrorBoundary caught an error:`,e,t),this.props.onError?.(e)}render(){return this.state.hasError?i(`div`,{class:`streaming-error-boundary`,children:[a(`p`,{children:`Something went wrong in streaming component.`}),a(`button`,{onClick:()=>this.setState({hasError:!1,error:void 0}),children:`Retry`})]}):this.props.children}}export function withStreaming(e,t={}){return function(n){return a(StreamingLayout,{component:e,componentProps:n,priority:`medium`,...t,children:null})}}export function useStreamingState(e,r=5e3){let[i,a]=t({isReady:!1,isLoading:!0,error:null,hasTimedOut:!1});n(()=>{if(!e){a(e=>({...e,isReady:!0,isLoading:!1}));return}let t=!1,n;return(async()=>{try{r>0&&(n=setTimeout(()=>{t||a(e=>({...e,hasTimedOut:!0,isLoading:!1}))},r));let i=await e();if(t)return;n&&clearTimeout(n),a(e=>({...e,isReady:i,isLoading:!1,hasTimedOut:!1}))}catch(e){if(t)return;n&&clearTimeout(n),a(t=>({...t,error:e,isLoading:!1}))}})(),()=>{t=!0,n&&clearTimeout(n)}},[e,r]);let o=()=>{a({isReady:!1,isLoading:!0,error:null,hasTimedOut:!1})};return{...i,retry:o}}
|