@pracht/core 0.7.0 → 0.8.1
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/app-BWriseLj.d.mts +298 -0
- package/dist/app-w-P1wf5T.mjs +308 -0
- package/dist/browser.d.mts +7 -0
- package/dist/browser.mjs +8 -0
- package/dist/client.d.mts +4 -0
- package/dist/client.mjs +4 -0
- package/dist/forwardRef-grZ6t4GS.mjs +27 -0
- package/dist/hydration-M1QzbQ1O.d.mts +23 -0
- package/dist/index.d.mts +7 -492
- package/dist/index.mjs +7 -2088
- package/dist/manifest.d.mts +2 -0
- package/dist/manifest.mjs +2 -0
- package/dist/prefetch-D9amIQtw.mjs +118 -0
- package/dist/prefetch-cache-DzP2Bj9H.mjs +129 -0
- package/dist/prerender-B8_CqYwd.d.mts +51 -0
- package/dist/prerender-CoMyuJKm.mjs +646 -0
- package/dist/router-BcUmg1kB.d.mts +27 -0
- package/dist/router-BkkyNjqB.mjs +501 -0
- package/dist/runtime-context-B5pREhcM.mjs +164 -0
- package/dist/runtime-context-ytVd7GmZ.d.mts +72 -0
- package/dist/runtime-middleware-aeBdgjYr.d.mts +77 -0
- package/dist/server.d.mts +5 -0
- package/dist/server.mjs +5 -0
- package/dist/types-DQv2poC5.mjs +348 -0
- package/package.json +19 -2
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { G as PrachtAppConfig, V as ModuleRef, W as PrachtApp, Y as RenderMode, b as GroupDefinition, et as RouteConfig, ft as RouteTreeNode, i as group, it as RouteMeta, l as route, lt as RouteRevalidate, r as defineApp, tt as RouteDefinition, u as timeRevalidate, vt as TimeRevalidatePolicy, x as GroupMeta } from "./app-BWriseLj.mjs";
|
|
2
|
+
export { type GroupDefinition, type GroupMeta, type ModuleRef, type PrachtApp, type PrachtAppConfig, type RenderMode, type RouteConfig, type RouteDefinition, type RouteMeta, type RouteRevalidate, type RouteTreeNode, type TimeRevalidatePolicy, defineApp, group, route, timeRevalidate };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { o as matchAppRoute } from "./app-w-P1wf5T.mjs";
|
|
2
|
+
import { a as trimMapToSize, i as getCachedRouteState, n as cacheRouteState, s as fetchPrachtRouteState, t as EMPTY_ROUTE_STATE_PROMISE, u as routeNeedsServerFetch } from "./prefetch-cache-DzP2Bj9H.mjs";
|
|
3
|
+
//#region src/prefetch.ts
|
|
4
|
+
const MAX_MATCH_CACHE_ENTRIES = 250;
|
|
5
|
+
function prefetchRouteState(url, route) {
|
|
6
|
+
if (route && !routeNeedsServerFetch(route)) return EMPTY_ROUTE_STATE_PROMISE;
|
|
7
|
+
const cached = getCachedRouteState(url);
|
|
8
|
+
if (cached) return cached;
|
|
9
|
+
const promise = fetchPrachtRouteState(url);
|
|
10
|
+
cacheRouteState(url, promise);
|
|
11
|
+
return promise;
|
|
12
|
+
}
|
|
13
|
+
function setupPrefetching(app, warmModules) {
|
|
14
|
+
let hoverTimer = null;
|
|
15
|
+
const observedViewportAnchors = /* @__PURE__ */ new WeakSet();
|
|
16
|
+
const matchCache = /* @__PURE__ */ new Map();
|
|
17
|
+
function getRoutePathname(url) {
|
|
18
|
+
try {
|
|
19
|
+
return new URL(url, window.location.origin).pathname;
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function getInternalHref(anchor) {
|
|
25
|
+
const href = anchor.getAttribute("href");
|
|
26
|
+
if (!href || href.startsWith("#")) return null;
|
|
27
|
+
let url;
|
|
28
|
+
try {
|
|
29
|
+
url = new URL(href, window.location.origin);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (url.origin !== window.location.origin) return null;
|
|
34
|
+
return url.pathname + url.search;
|
|
35
|
+
}
|
|
36
|
+
function getMatchEntry(href) {
|
|
37
|
+
const cached = matchCache.get(href);
|
|
38
|
+
if (cached) {
|
|
39
|
+
matchCache.delete(href);
|
|
40
|
+
matchCache.set(href, cached);
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
43
|
+
const routePathname = getRoutePathname(href);
|
|
44
|
+
const match = routePathname ? matchAppRoute(app, routePathname) ?? null : null;
|
|
45
|
+
const entry = {
|
|
46
|
+
match,
|
|
47
|
+
strategy: match ? match.route.prefetch ?? "intent" : "none"
|
|
48
|
+
};
|
|
49
|
+
matchCache.set(href, entry);
|
|
50
|
+
trimMapToSize(matchCache, MAX_MATCH_CACHE_ENTRIES);
|
|
51
|
+
return entry;
|
|
52
|
+
}
|
|
53
|
+
function prefetchHref(href) {
|
|
54
|
+
const match = getMatchEntry(href).match;
|
|
55
|
+
prefetchRouteState(href, match?.route);
|
|
56
|
+
if (warmModules && match) warmModules(match);
|
|
57
|
+
}
|
|
58
|
+
document.addEventListener("mouseenter", (e) => {
|
|
59
|
+
const anchor = e.target.closest?.("a");
|
|
60
|
+
if (!anchor) return;
|
|
61
|
+
const href = getInternalHref(anchor);
|
|
62
|
+
if (!href) return;
|
|
63
|
+
const strategy = getMatchEntry(href).strategy;
|
|
64
|
+
if (strategy !== "hover" && strategy !== "intent") return;
|
|
65
|
+
if (hoverTimer) clearTimeout(hoverTimer);
|
|
66
|
+
hoverTimer = setTimeout(() => {
|
|
67
|
+
prefetchHref(href);
|
|
68
|
+
}, 50);
|
|
69
|
+
}, true);
|
|
70
|
+
document.addEventListener("mouseleave", (e) => {
|
|
71
|
+
if (!e.target.closest?.("a")) return;
|
|
72
|
+
if (hoverTimer) {
|
|
73
|
+
clearTimeout(hoverTimer);
|
|
74
|
+
hoverTimer = null;
|
|
75
|
+
}
|
|
76
|
+
}, true);
|
|
77
|
+
document.addEventListener("focusin", (e) => {
|
|
78
|
+
const anchor = e.target.closest?.("a");
|
|
79
|
+
if (!anchor) return;
|
|
80
|
+
const href = getInternalHref(anchor);
|
|
81
|
+
if (!href) return;
|
|
82
|
+
const strategy = getMatchEntry(href).strategy;
|
|
83
|
+
if (strategy !== "hover" && strategy !== "intent") return;
|
|
84
|
+
prefetchHref(href);
|
|
85
|
+
}, true);
|
|
86
|
+
if (typeof IntersectionObserver === "undefined") return;
|
|
87
|
+
const observer = new IntersectionObserver((entries) => {
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
if (!entry.isIntersecting) continue;
|
|
90
|
+
const anchor = entry.target;
|
|
91
|
+
const href = getInternalHref(anchor);
|
|
92
|
+
if (!href) continue;
|
|
93
|
+
prefetchHref(href);
|
|
94
|
+
observer.unobserve(anchor);
|
|
95
|
+
}
|
|
96
|
+
}, { rootMargin: "200px" });
|
|
97
|
+
function observeAnchor(anchor) {
|
|
98
|
+
if (observedViewportAnchors.has(anchor)) return;
|
|
99
|
+
const href = getInternalHref(anchor);
|
|
100
|
+
if (!href) return;
|
|
101
|
+
if (getMatchEntry(href).strategy !== "viewport") return;
|
|
102
|
+
observedViewportAnchors.add(anchor);
|
|
103
|
+
observer.observe(anchor);
|
|
104
|
+
}
|
|
105
|
+
function observeViewportLinks(root) {
|
|
106
|
+
if (root instanceof HTMLAnchorElement) observeAnchor(root);
|
|
107
|
+
for (const anchor of root.querySelectorAll("a[href]")) observeAnchor(anchor);
|
|
108
|
+
}
|
|
109
|
+
observeViewportLinks(document.body);
|
|
110
|
+
new MutationObserver((records) => {
|
|
111
|
+
for (const record of records) for (const node of record.addedNodes) if (node instanceof HTMLElement || node instanceof DocumentFragment) observeViewportLinks(node);
|
|
112
|
+
}).observe(document.body, {
|
|
113
|
+
childList: true,
|
|
114
|
+
subtree: true
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { setupPrefetching };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
//#region src/runtime-constants.ts
|
|
2
|
+
const SAFE_METHODS = new Set(["GET", "HEAD"]);
|
|
3
|
+
const HYDRATION_STATE_ELEMENT_ID = "pracht-state";
|
|
4
|
+
const ROUTE_STATE_REQUEST_HEADER = "x-pracht-route-state-request";
|
|
5
|
+
const ROUTE_STATE_CACHE_CONTROL = "no-store";
|
|
6
|
+
const EMPTY_ROUTE_PARAMS = {};
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/runtime-client-fetch.ts
|
|
9
|
+
const SAFE_NAVIGATION_PROTOCOLS = new Set(["http:", "https:"]);
|
|
10
|
+
/**
|
|
11
|
+
* Parse a possibly-server-supplied redirect target against a base URL and
|
|
12
|
+
* return it only if it uses a safe navigation scheme (`http:` or `https:`).
|
|
13
|
+
*
|
|
14
|
+
* `javascript:`, `data:`, `vbscript:`, `blob:`, `file:` and similar schemes
|
|
15
|
+
* can execute script or bypass same-origin assumptions when assigned to
|
|
16
|
+
* `window.location.href` — a server-controlled redirect (from a loader,
|
|
17
|
+
* middleware, form action response, or API route) must never be able to
|
|
18
|
+
* trigger them. Returns `null` for unsafe or unparseable inputs.
|
|
19
|
+
*/
|
|
20
|
+
function parseSafeNavigationUrl(location, base) {
|
|
21
|
+
let targetUrl;
|
|
22
|
+
try {
|
|
23
|
+
targetUrl = new URL(location, base);
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (!SAFE_NAVIGATION_PROTOCOLS.has(targetUrl.protocol)) return null;
|
|
28
|
+
return targetUrl;
|
|
29
|
+
}
|
|
30
|
+
function routeNeedsServerFetch(route) {
|
|
31
|
+
if (route.hasLoader === false && route.middlewareFiles.length === 0) return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function buildRouteStateUrl(url) {
|
|
35
|
+
return `${url}${url.includes("?") ? "&" : "?"}_data=1`;
|
|
36
|
+
}
|
|
37
|
+
async function fetchPrachtRouteState(url, options) {
|
|
38
|
+
const fetchUrl = options?.useDataParam ? buildRouteStateUrl(url) : url;
|
|
39
|
+
const response = await fetch(fetchUrl, {
|
|
40
|
+
headers: options?.useDataParam ? {} : {
|
|
41
|
+
[ROUTE_STATE_REQUEST_HEADER]: "1",
|
|
42
|
+
"Cache-Control": "no-cache"
|
|
43
|
+
},
|
|
44
|
+
redirect: "manual",
|
|
45
|
+
signal: options?.signal
|
|
46
|
+
});
|
|
47
|
+
if (response.type === "opaqueredirect" || response.status >= 300 && response.status < 400) return {
|
|
48
|
+
location: response.headers.get("location") ?? url,
|
|
49
|
+
type: "redirect"
|
|
50
|
+
};
|
|
51
|
+
const json = await response.json();
|
|
52
|
+
if (json.redirect) return {
|
|
53
|
+
location: json.redirect,
|
|
54
|
+
type: "redirect"
|
|
55
|
+
};
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
if (json.error) return {
|
|
58
|
+
error: json.error,
|
|
59
|
+
type: "error"
|
|
60
|
+
};
|
|
61
|
+
throw new Error(`Failed to fetch route state (${response.status})`);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
data: json.data,
|
|
65
|
+
type: "data"
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function navigateToClientLocation(location, options) {
|
|
69
|
+
if (typeof window === "undefined") return;
|
|
70
|
+
const targetUrl = parseSafeNavigationUrl(location, window.location.href);
|
|
71
|
+
if (!targetUrl) {
|
|
72
|
+
console.error(`[pracht] refused to navigate to unsafe URL: ${location}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const target = targetUrl.pathname + targetUrl.search + targetUrl.hash;
|
|
76
|
+
if (targetUrl.origin === window.location.origin && window.__PRACHT_NAVIGATE__) {
|
|
77
|
+
await window.__PRACHT_NAVIGATE__(target, options);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (options?.replace) {
|
|
81
|
+
window.location.replace(targetUrl.toString());
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
window.location.href = targetUrl.toString();
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/prefetch-cache.ts
|
|
88
|
+
const CACHE_TTL_MS = 3e4;
|
|
89
|
+
const MAX_PREFETCH_CACHE_ENTRIES = 100;
|
|
90
|
+
const EMPTY_ROUTE_STATE = {
|
|
91
|
+
type: "data",
|
|
92
|
+
data: void 0
|
|
93
|
+
};
|
|
94
|
+
const prefetchCache = /* @__PURE__ */ new Map();
|
|
95
|
+
const EMPTY_ROUTE_STATE_PROMISE = Promise.resolve(EMPTY_ROUTE_STATE);
|
|
96
|
+
function clearPrefetchCache() {
|
|
97
|
+
prefetchCache.clear();
|
|
98
|
+
}
|
|
99
|
+
function getCachedRouteState(url) {
|
|
100
|
+
const entry = prefetchCache.get(url);
|
|
101
|
+
if (!entry) return null;
|
|
102
|
+
if (Date.now() - entry.timestamp > CACHE_TTL_MS) {
|
|
103
|
+
prefetchCache.delete(url);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
prefetchCache.delete(url);
|
|
107
|
+
prefetchCache.set(url, entry);
|
|
108
|
+
return entry.promise;
|
|
109
|
+
}
|
|
110
|
+
function cacheRouteState(url, promise) {
|
|
111
|
+
sweepPrefetchCache();
|
|
112
|
+
prefetchCache.set(url, {
|
|
113
|
+
promise,
|
|
114
|
+
timestamp: Date.now()
|
|
115
|
+
});
|
|
116
|
+
trimMapToSize(prefetchCache, MAX_PREFETCH_CACHE_ENTRIES);
|
|
117
|
+
}
|
|
118
|
+
function sweepPrefetchCache(now = Date.now()) {
|
|
119
|
+
for (const [url, entry] of prefetchCache) if (now - entry.timestamp > CACHE_TTL_MS) prefetchCache.delete(url);
|
|
120
|
+
}
|
|
121
|
+
function trimMapToSize(map, maxEntries) {
|
|
122
|
+
while (map.size > maxEntries) {
|
|
123
|
+
const first = map.keys().next();
|
|
124
|
+
if (first.done) return;
|
|
125
|
+
map.delete(first.value);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { trimMapToSize as a, navigateToClientLocation as c, EMPTY_ROUTE_PARAMS as d, HYDRATION_STATE_ELEMENT_ID as f, SAFE_METHODS as h, getCachedRouteState as i, parseSafeNavigationUrl as l, ROUTE_STATE_REQUEST_HEADER as m, cacheRouteState as n, buildRouteStateUrl as o, ROUTE_STATE_CACHE_CONTROL as p, clearPrefetchCache as r, fetchPrachtRouteState as s, EMPTY_ROUTE_STATE_PROMISE as t, routeNeedsServerFetch as u };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { H as ModuleRegistry, W as PrachtApp, X as ResolvedApiRoute, lt as RouteRevalidate } from "./app-BWriseLj.mjs";
|
|
2
|
+
//#region src/runtime-headers.d.ts
|
|
3
|
+
declare function applyDefaultSecurityHeaders(headers: Headers): Headers;
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/runtime.d.ts
|
|
6
|
+
interface HandlePrachtRequestOptions<TContext = unknown> {
|
|
7
|
+
app: PrachtApp;
|
|
8
|
+
request: Request;
|
|
9
|
+
context?: TContext;
|
|
10
|
+
registry?: ModuleRegistry;
|
|
11
|
+
/** Expose raw server error details in rendered HTML and route-state JSON. */
|
|
12
|
+
debugErrors?: boolean;
|
|
13
|
+
clientEntryUrl?: string;
|
|
14
|
+
/** Per-source-file CSS map produced by the vite plugin. */
|
|
15
|
+
cssManifest?: Record<string, string[]>;
|
|
16
|
+
/** Per-source-file JS chunk map produced by the vite plugin for modulepreload hints. */
|
|
17
|
+
jsManifest?: Record<string, string[]>;
|
|
18
|
+
apiRoutes?: ResolvedApiRoute[];
|
|
19
|
+
}
|
|
20
|
+
declare function handlePrachtRequest<TContext>(options: HandlePrachtRequestOptions<TContext>): Promise<Response>;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/prerender.d.ts
|
|
23
|
+
interface PrerenderResult {
|
|
24
|
+
path: string;
|
|
25
|
+
html: string;
|
|
26
|
+
headers?: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
interface ISGManifestEntry {
|
|
29
|
+
revalidate: RouteRevalidate;
|
|
30
|
+
}
|
|
31
|
+
interface PrerenderAppResult {
|
|
32
|
+
pages: PrerenderResult[];
|
|
33
|
+
isgManifest: Record<string, ISGManifestEntry>;
|
|
34
|
+
}
|
|
35
|
+
interface PrerenderAppOptions {
|
|
36
|
+
app: PrachtApp;
|
|
37
|
+
registry?: ModuleRegistry;
|
|
38
|
+
clientEntryUrl?: string;
|
|
39
|
+
/** Per-source-file CSS map produced by the vite plugin. */
|
|
40
|
+
cssManifest?: Record<string, string[]>;
|
|
41
|
+
/** Per-source-file JS map produced by the vite plugin for modulepreload hints. */
|
|
42
|
+
jsManifest?: Record<string, string[]>;
|
|
43
|
+
/** Maximum number of pages rendered concurrently. Defaults to 10. */
|
|
44
|
+
concurrency?: number;
|
|
45
|
+
}
|
|
46
|
+
declare function prerenderApp(options: PrerenderAppOptions): Promise<PrerenderResult[]>;
|
|
47
|
+
declare function prerenderApp(options: PrerenderAppOptions & {
|
|
48
|
+
withISGManifest: true;
|
|
49
|
+
}): Promise<PrerenderAppResult>;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { prerenderApp as a, applyDefaultSecurityHeaders as c, PrerenderResult as i, PrerenderAppOptions as n, HandlePrachtRequestOptions as o, PrerenderAppResult as r, handlePrachtRequest as s, ISGManifestEntry as t };
|