@pracht/core 0.1.0 → 0.2.0

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/README.md CHANGED
@@ -16,6 +16,10 @@ npm install @pracht/core preact preact-render-to-string
16
16
  - `route()` — declare a route with path, component, loader, and rendering mode
17
17
  - `group()` — group routes under a shared shell or middleware
18
18
 
19
+ Route modules may export the page as a function default export or as a named
20
+ `Component` export. Named exports such as `loader`, `head`, `ErrorBoundary`, and
21
+ `getStaticPaths` keep their special route-module behavior.
22
+
19
23
  ### Server
20
24
 
21
25
  - `handlePrachtRequest()` — server renderer that produces full HTML with hydration markers
package/dist/index.d.mts CHANGED
@@ -169,7 +169,8 @@ type LoaderFn<TContext = any, TData = unknown> = (args: LoaderArgs<TContext>) =>
169
169
  interface RouteModule<TContext = any, TLoader extends LoaderLike = undefined> {
170
170
  loader?: LoaderFn<TContext>;
171
171
  head?: (args: HeadArgs<TLoader, TContext>) => MaybePromise<HeadMetadata>;
172
- Component: FunctionComponent<RouteComponentProps<TLoader>>;
172
+ Component?: FunctionComponent<RouteComponentProps<TLoader>>;
173
+ default?: FunctionComponent<RouteComponentProps<TLoader>>;
173
174
  ErrorBoundary?: FunctionComponent<ErrorBoundaryProps>;
174
175
  getStaticPaths?: () => MaybePromise<RouteParams[]>;
175
176
  }
@@ -234,6 +235,14 @@ declare function forwardRef<P = {}>(fn: ((props: P, ref: any) => any) & {
234
235
  ref?: any;
235
236
  }>;
236
237
  //#endregion
238
+ //#region src/hydration.d.ts
239
+ /**
240
+ * Returns `true` once the initial hydration (including all Suspense
241
+ * boundaries) has fully resolved. During SSR and hydration this returns
242
+ * `false`.
243
+ */
244
+ declare function useIsHydrated(): boolean;
245
+ //#endregion
237
246
  //#region src/runtime.d.ts
238
247
  type PrachtRuntimeDiagnosticPhase = "match" | "middleware" | "loader" | "action" | "render" | "api";
239
248
  interface PrachtRuntimeDiagnostics {
@@ -384,4 +393,4 @@ interface InitClientRouterOptions {
384
393
  }
385
394
  declare function initClientRouter(options: InitClientRouterOptions): Promise<void>;
386
395
  //#endregion
387
- export { type ApiConfig, type ApiRouteHandler, type ApiRouteMatch, type ApiRouteModule, type BaseRouteArgs, type DataModule, type ErrorBoundaryProps, Form, type FormProps, type GroupDefinition, type GroupMeta, type HandlePrachtRequestOptions, type HeadArgs, type HeadMetadata, type HttpMethod, type ISGManifestEntry, type InitClientRouterOptions, type LoaderArgs, type LoaderData, type LoaderFn, type Location, type MiddlewareArgs, type MiddlewareFn, type MiddlewareModule, type MiddlewareResult, type ModuleImporter, type ModuleRef, type ModuleRegistry, type NavigateFn, type PrachtApp, type PrachtAppConfig, PrachtHttpError, type PrachtHydrationState, type PrachtRuntimeDiagnosticPhase, type PrachtRuntimeDiagnostics, PrachtRuntimeProvider, type PrefetchStrategy, type PrerenderAppOptions, type PrerenderAppResult, type PrerenderResult, type Register, type RenderMode, type ResolvedApiRoute, type ResolvedPrachtApp, type ResolvedRoute, type RouteComponentProps, type RouteConfig, type RouteDefinition, type RouteMatch, type RouteMeta, type RouteModule, type RouteParams, type RouteRevalidate, type RouteStateResult, type RouteTreeNode, type SerializedRouteError, type ShellModule, type ShellProps, type StartAppOptions, Suspense, type TimeRevalidatePolicy, applyDefaultSecurityHeaders, buildPathFromSegments, defineApp, forwardRef, group, handlePrachtRequest, initClientRouter, lazy, matchApiRoute, matchAppRoute, prerenderApp, readHydrationState, resolveApiRoutes, resolveApp, route, startApp, timeRevalidate, useLocation, useNavigate, useParams, useRevalidate, useRevalidateRoute, useRouteData };
396
+ export { type ApiConfig, type ApiRouteHandler, type ApiRouteMatch, type ApiRouteModule, type BaseRouteArgs, type DataModule, type ErrorBoundaryProps, Form, type FormProps, type GroupDefinition, type GroupMeta, type HandlePrachtRequestOptions, type HeadArgs, type HeadMetadata, type HttpMethod, type ISGManifestEntry, type InitClientRouterOptions, type LoaderArgs, type LoaderData, type LoaderFn, type Location, type MiddlewareArgs, type MiddlewareFn, type MiddlewareModule, type MiddlewareResult, type ModuleImporter, type ModuleRef, type ModuleRegistry, type NavigateFn, type PrachtApp, type PrachtAppConfig, PrachtHttpError, type PrachtHydrationState, type PrachtRuntimeDiagnosticPhase, type PrachtRuntimeDiagnostics, PrachtRuntimeProvider, type PrefetchStrategy, type PrerenderAppOptions, type PrerenderAppResult, type PrerenderResult, type Register, type RenderMode, type ResolvedApiRoute, type ResolvedPrachtApp, type ResolvedRoute, type RouteComponentProps, type RouteConfig, type RouteDefinition, type RouteMatch, type RouteMeta, type RouteModule, type RouteParams, type RouteRevalidate, type RouteStateResult, type RouteTreeNode, type SerializedRouteError, type ShellModule, type ShellProps, type StartAppOptions, Suspense, type TimeRevalidatePolicy, applyDefaultSecurityHeaders, buildPathFromSegments, defineApp, forwardRef, group, handlePrachtRequest, initClientRouter, lazy, matchApiRoute, matchAppRoute, prerenderApp, readHydrationState, resolveApiRoutes, resolveApp, route, startApp, timeRevalidate, useIsHydrated, useLocation, useNavigate, useParams, useRevalidate, useRevalidateRoute, useRouteData };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { a as matchApiRoute, c as resolveApp, i as group, l as route, n as buildPathFromSegments, o as matchAppRoute, r as defineApp, s as resolveApiRoutes, u as timeRevalidate } from "./app-Cep0el7c.mjs";
2
2
  import { createContext, h, hydrate, options, render } from "preact";
3
- import { Suspense, lazy } from "preact-suspense";
4
3
  import { useContext, useEffect, useMemo, useState } from "preact/hooks";
4
+ import { Suspense, lazy } from "preact-suspense";
5
5
  //#region src/forwardRef.ts
6
6
  let oldDiffHook = options.__b;
7
7
  options.__b = (vnode) => {
@@ -27,6 +27,51 @@ function forwardRef(fn) {
27
27
  return Forwarded;
28
28
  }
29
29
  //#endregion
30
+ //#region src/hydration.ts
31
+ let _hydrating = false;
32
+ let _suspensionCount = 0;
33
+ let _hydrated = false;
34
+ const oldCatchError = options.__e;
35
+ options.__e = (err, newVNode, oldVNode, errorInfo) => {
36
+ if (_hydrating && !_hydrated && err && err.then) {
37
+ _suspensionCount++;
38
+ let settled = false;
39
+ const onSettled = () => {
40
+ if (settled) return;
41
+ settled = true;
42
+ _suspensionCount--;
43
+ };
44
+ err.then(onSettled, onSettled);
45
+ }
46
+ if (oldCatchError) oldCatchError(err, newVNode, oldVNode, errorInfo);
47
+ };
48
+ const oldDiffed = options.diffed;
49
+ options.diffed = (vnode) => {
50
+ if (_hydrating && !_hydrated && _suspensionCount <= 0) {
51
+ _hydrated = true;
52
+ _hydrating = false;
53
+ }
54
+ if (oldDiffed) oldDiffed(vnode);
55
+ };
56
+ /**
57
+ * Mark the start of a hydration pass. Call this right before `hydrate()`.
58
+ */
59
+ function markHydrating() {
60
+ if (!_hydrated) _hydrating = true;
61
+ }
62
+ /**
63
+ * Returns `true` once the initial hydration (including all Suspense
64
+ * boundaries) has fully resolved. During SSR and hydration this returns
65
+ * `false`.
66
+ */
67
+ function useIsHydrated() {
68
+ const [hydrated, setHydrated] = useState(_hydrated);
69
+ useEffect(() => {
70
+ setHydrated(true);
71
+ }, []);
72
+ return hydrated;
73
+ }
74
+ //#endregion
30
75
  //#region src/runtime.ts
31
76
  const SAFE_METHODS = new Set(["GET", "HEAD"]);
32
77
  const HYDRATION_STATE_ELEMENT_ID = "pracht-state";
@@ -305,9 +350,10 @@ async function handlePrachtRequest(options) {
305
350
  modulePreloadUrls
306
351
  }));
307
352
  }
308
- if (!routeModule.Component) throw new Error("Route has no Component export");
353
+ const DefaultComponent = typeof routeModule.default === "function" ? routeModule.default : void 0;
354
+ const Component = routeModule.Component ?? DefaultComponent;
355
+ if (!Component) throw new Error("Route has no Component or default export");
309
356
  const { renderToStringAsync } = await import("preact-render-to-string");
310
- const Component = routeModule.Component;
311
357
  const Shell = shellModule?.Shell;
312
358
  const componentProps = {
313
359
  data,
@@ -868,7 +914,8 @@ async function initClientRouter(options) {
868
914
  let Shell = null;
869
915
  const resolvedShell = await (shellModPromise ?? startShellImport(match));
870
916
  if (resolvedShell) Shell = resolvedShell.Shell;
871
- const Component = state.error ? routeMod.ErrorBoundary : routeMod.Component;
917
+ const DefaultComponent = typeof routeMod.default === "function" ? routeMod.default : void 0;
918
+ const Component = state.error ? routeMod.ErrorBoundary : routeMod.Component ?? DefaultComponent;
872
919
  if (!Component) return null;
873
920
  const props = state.error ? { error: deserializeRouteError(state.error) } : {
874
921
  data: state.data,
@@ -980,7 +1027,10 @@ async function initClientRouter(options) {
980
1027
  }
981
1028
  const tree = await buildRouteTree(initialMatch, state, void 0, initialShellPromise);
982
1029
  if (tree) if (initialMatch.route.render === "spa") render(tree, root);
983
- else hydrate(tree, root);
1030
+ else {
1031
+ markHydrating();
1032
+ hydrate(tree, root);
1033
+ }
984
1034
  }
985
1035
  document.addEventListener("click", (e) => {
986
1036
  const anchor = e.target.closest?.("a");
@@ -1080,4 +1130,4 @@ var PrachtHttpError = class extends Error {
1080
1130
  }
1081
1131
  };
1082
1132
  //#endregion
1083
- export { Form, PrachtHttpError, PrachtRuntimeProvider, Suspense, applyDefaultSecurityHeaders, buildPathFromSegments, defineApp, forwardRef, group, handlePrachtRequest, initClientRouter, lazy, matchApiRoute, matchAppRoute, prerenderApp, readHydrationState, resolveApiRoutes, resolveApp, route, startApp, timeRevalidate, useLocation, useNavigate, useParams, useRevalidate, useRevalidateRoute, useRouteData };
1133
+ export { Form, PrachtHttpError, PrachtRuntimeProvider, Suspense, applyDefaultSecurityHeaders, buildPathFromSegments, defineApp, forwardRef, group, handlePrachtRequest, initClientRouter, lazy, matchApiRoute, matchAppRoute, prerenderApp, readHydrationState, resolveApiRoutes, resolveApp, route, startApp, timeRevalidate, useIsHydrated, useLocation, useNavigate, useParams, useRevalidate, useRevalidateRoute, useRouteData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pracht/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "homepage": "https://github.com/JoviDeCroock/pracht/tree/main/packages/framework",
5
5
  "bugs": {
6
6
  "url": "https://github.com/JoviDeCroock/pracht/issues"