@pracht/core 0.7.0 → 0.8.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/dist/app-BWriseLj.d.mts +298 -0
- package/dist/app-BvC1uQG5.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-B50kX9RL.mjs +118 -0
- package/dist/prefetch-cache-DzP2Bj9H.mjs +129 -0
- package/dist/prerender-B8_CqYwd.d.mts +51 -0
- package/dist/prerender-D3E2H96p.mjs +648 -0
- package/dist/router-B7J4YYwg.mjs +418 -0
- package/dist/router-BcUmg1kB.d.mts +27 -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-idmK5omD.mjs +355 -0
- package/package.json +19 -2
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { o as matchAppRoute, t as buildHref } from "./app-BvC1uQG5.mjs";
|
|
2
|
+
import { i as getCachedRouteState, l as parseSafeNavigationUrl, s as fetchPrachtRouteState, u as routeNeedsServerFetch } from "./prefetch-cache-DzP2Bj9H.mjs";
|
|
3
|
+
import { s as deserializeRouteError, t as PrachtRuntimeProvider } from "./runtime-context-B5pREhcM.mjs";
|
|
4
|
+
import { createContext, h, hydrate, options, render } from "preact";
|
|
5
|
+
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
|
6
|
+
import { Suspense } from "preact-suspense";
|
|
7
|
+
//#region src/hydration.ts
|
|
8
|
+
const MODE_HYDRATE = 32;
|
|
9
|
+
let _hydrating = false;
|
|
10
|
+
let _suspensionCount = 0;
|
|
11
|
+
let _hydrated = false;
|
|
12
|
+
new Suspense({});
|
|
13
|
+
const oldCatchError = options.__e;
|
|
14
|
+
options.__e = (err, newVNode, oldVNode, errorInfo) => {
|
|
15
|
+
if (_hydrating && !_hydrated && err && err.then) {
|
|
16
|
+
if (!!(newVNode && newVNode.__u && newVNode.__u & MODE_HYDRATE) || !!(newVNode && newVNode.__h)) {
|
|
17
|
+
_suspensionCount++;
|
|
18
|
+
let settled = false;
|
|
19
|
+
const onSettled = () => {
|
|
20
|
+
if (settled) return;
|
|
21
|
+
settled = true;
|
|
22
|
+
_suspensionCount--;
|
|
23
|
+
};
|
|
24
|
+
err.then(onSettled, onSettled);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (oldCatchError) oldCatchError(err, newVNode, oldVNode, errorInfo);
|
|
28
|
+
};
|
|
29
|
+
const oldCommit = options.__c;
|
|
30
|
+
options.__c = (vnode, commitQueue) => {
|
|
31
|
+
if (_hydrating && !_hydrated && _suspensionCount <= 0) {
|
|
32
|
+
_hydrated = true;
|
|
33
|
+
_hydrating = false;
|
|
34
|
+
}
|
|
35
|
+
if (oldCommit) oldCommit(vnode, commitQueue);
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Mark the start of a hydration pass. Call this right before `hydrate()`.
|
|
39
|
+
*/
|
|
40
|
+
function markHydrating() {
|
|
41
|
+
if (!_hydrated) _hydrating = true;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Returns `true` once the initial hydration (including all Suspense
|
|
45
|
+
* boundaries) has fully resolved. During SSR and hydration this returns
|
|
46
|
+
* `false`.
|
|
47
|
+
*/
|
|
48
|
+
function useIsHydrated() {
|
|
49
|
+
const [hydrated, setHydrated] = useState(_hydrated);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
setHydrated(true);
|
|
52
|
+
}, []);
|
|
53
|
+
return hydrated;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/hydration-mismatch.ts
|
|
57
|
+
const HYDRATION_BANNER_ID = "__pracht_hydration_mismatch__";
|
|
58
|
+
let installed = false;
|
|
59
|
+
function installHydrationMismatchWarning() {
|
|
60
|
+
if (installed) return;
|
|
61
|
+
installed = true;
|
|
62
|
+
const prev = options.__m;
|
|
63
|
+
options.__m = function(vnode) {
|
|
64
|
+
appendHydrationWarning(vnode);
|
|
65
|
+
if (prev) prev(vnode);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function appendHydrationWarning(vnode) {
|
|
69
|
+
if (typeof document === "undefined") return;
|
|
70
|
+
const componentName = getVNodeName(vnode);
|
|
71
|
+
let banner = document.getElementById(HYDRATION_BANNER_ID);
|
|
72
|
+
const message = `Hydration mismatch detected on <${componentName}>. The server-rendered HTML did not match the client.`;
|
|
73
|
+
if (banner) {
|
|
74
|
+
const list = banner.querySelector(`[data-pracht-mismatch-list]`);
|
|
75
|
+
if (list) {
|
|
76
|
+
const item = document.createElement("li");
|
|
77
|
+
item.textContent = message;
|
|
78
|
+
list.appendChild(item);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
banner = document.createElement("div");
|
|
83
|
+
banner.id = HYDRATION_BANNER_ID;
|
|
84
|
+
banner.setAttribute("role", "alert");
|
|
85
|
+
banner.style.cssText = [
|
|
86
|
+
"position:fixed",
|
|
87
|
+
"top:0",
|
|
88
|
+
"left:0",
|
|
89
|
+
"right:0",
|
|
90
|
+
"z-index:2147483647",
|
|
91
|
+
"background:#1a1a2e",
|
|
92
|
+
"color:#ff6b6b",
|
|
93
|
+
"padding:12px 16px",
|
|
94
|
+
"font:12px/1.5 ui-monospace,Menlo,Consolas,monospace",
|
|
95
|
+
"border-bottom:2px solid #e74c3c",
|
|
96
|
+
"box-shadow:0 2px 8px rgba(0,0,0,0.3)"
|
|
97
|
+
].join(";");
|
|
98
|
+
const title = document.createElement("strong");
|
|
99
|
+
title.textContent = "pracht: hydration mismatch";
|
|
100
|
+
title.style.cssText = "display:block;margin-bottom:4px;color:#fff";
|
|
101
|
+
banner.appendChild(title);
|
|
102
|
+
const list = document.createElement("ul");
|
|
103
|
+
list.setAttribute("data-pracht-mismatch-list", "");
|
|
104
|
+
list.style.cssText = "margin:0;padding-left:18px";
|
|
105
|
+
const item = document.createElement("li");
|
|
106
|
+
item.textContent = message;
|
|
107
|
+
list.appendChild(item);
|
|
108
|
+
banner.appendChild(list);
|
|
109
|
+
document.body.appendChild(banner);
|
|
110
|
+
}
|
|
111
|
+
function getVNodeName(vnode) {
|
|
112
|
+
if (!vnode) return "Unknown";
|
|
113
|
+
const type = vnode.type;
|
|
114
|
+
if (typeof type === "string") return type;
|
|
115
|
+
if (typeof type === "function") {
|
|
116
|
+
const fn = type;
|
|
117
|
+
return fn.displayName || fn.name || "Component";
|
|
118
|
+
}
|
|
119
|
+
return "Unknown";
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/router.ts
|
|
123
|
+
const NavigateContext = createContext(async () => {});
|
|
124
|
+
function useNavigate() {
|
|
125
|
+
return useContext(NavigateContext);
|
|
126
|
+
}
|
|
127
|
+
async function initClientRouter(options) {
|
|
128
|
+
const { app, routeModules, shellModules, root, findModuleKey } = options;
|
|
129
|
+
if (import.meta.env?.DEV) installHydrationMismatchWarning();
|
|
130
|
+
const moduleCache = /* @__PURE__ */ new Map();
|
|
131
|
+
function loadModule(modules, key) {
|
|
132
|
+
let cached = moduleCache.get(key);
|
|
133
|
+
if (!cached) {
|
|
134
|
+
cached = modules[key]();
|
|
135
|
+
moduleCache.set(key, cached);
|
|
136
|
+
}
|
|
137
|
+
return cached;
|
|
138
|
+
}
|
|
139
|
+
function startRouteImport(match) {
|
|
140
|
+
const routeKey = findModuleKey(routeModules, match.route.file);
|
|
141
|
+
if (!routeKey) return null;
|
|
142
|
+
return loadModule(routeModules, routeKey);
|
|
143
|
+
}
|
|
144
|
+
function startShellImport(match) {
|
|
145
|
+
if (!match.route.shellFile) return null;
|
|
146
|
+
const shellKey = findModuleKey(shellModules, match.route.shellFile);
|
|
147
|
+
if (!shellKey) return null;
|
|
148
|
+
return loadModule(shellModules, shellKey);
|
|
149
|
+
}
|
|
150
|
+
let updateRouteState = null;
|
|
151
|
+
let routeStateVersion = 0;
|
|
152
|
+
let latestNavigationId = 0;
|
|
153
|
+
let activeNavigationAbort = null;
|
|
154
|
+
function RouterRoot({ initialState }) {
|
|
155
|
+
const [routeState, setRouteState] = useState(initialState);
|
|
156
|
+
updateRouteState = setRouteState;
|
|
157
|
+
const navigateValue = useMemo(() => navigate, []);
|
|
158
|
+
const { Shell, Component, componentProps, data, params, routeId, url, version } = routeState;
|
|
159
|
+
const componentTree = Shell ? h(Shell, null, h(Component, componentProps)) : h(Component, componentProps);
|
|
160
|
+
return h(NavigateContext.Provider, { value: navigateValue }, h(PrachtRuntimeProvider, {
|
|
161
|
+
data,
|
|
162
|
+
params,
|
|
163
|
+
routeId,
|
|
164
|
+
routes: app.routes,
|
|
165
|
+
stateVersion: version,
|
|
166
|
+
url
|
|
167
|
+
}, componentTree));
|
|
168
|
+
}
|
|
169
|
+
function applyRouteState(routeState) {
|
|
170
|
+
if (updateRouteState) {
|
|
171
|
+
updateRouteState(routeState);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
render(h(RouterRoot, { initialState: routeState }), root);
|
|
175
|
+
}
|
|
176
|
+
async function resolveRouteState(match, state, currentUrl, routeModPromise, shellModPromise) {
|
|
177
|
+
const routeMod = await (routeModPromise ?? startRouteImport(match));
|
|
178
|
+
if (!routeMod) return null;
|
|
179
|
+
let Shell = null;
|
|
180
|
+
const resolvedShell = await (shellModPromise ?? startShellImport(match));
|
|
181
|
+
if (resolvedShell) Shell = resolvedShell.Shell;
|
|
182
|
+
const DefaultComponent = typeof routeMod.default === "function" ? routeMod.default : void 0;
|
|
183
|
+
const ErrorBoundary = routeMod.ErrorBoundary ?? resolvedShell?.ErrorBoundary;
|
|
184
|
+
const Component = state.error ? ErrorBoundary : routeMod.Component ?? DefaultComponent;
|
|
185
|
+
if (!Component) return null;
|
|
186
|
+
const componentProps = state.error ? { error: deserializeRouteError(state.error) } : {
|
|
187
|
+
data: state.data,
|
|
188
|
+
params: match.params
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
Shell,
|
|
192
|
+
Component,
|
|
193
|
+
componentProps,
|
|
194
|
+
data: state.data,
|
|
195
|
+
params: match.params,
|
|
196
|
+
routeId: match.route.id ?? "",
|
|
197
|
+
url: currentUrl,
|
|
198
|
+
version: ++routeStateVersion
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function resolveSpaPendingState(match, currentUrl, shellModPromise) {
|
|
202
|
+
const resolvedShell = await (shellModPromise ?? startShellImport(match));
|
|
203
|
+
if (!resolvedShell) return null;
|
|
204
|
+
const Shell = resolvedShell.Shell ?? null;
|
|
205
|
+
const Loading = resolvedShell.Loading;
|
|
206
|
+
if (!Shell && !Loading) return null;
|
|
207
|
+
return {
|
|
208
|
+
Shell,
|
|
209
|
+
Component: Loading ?? (() => null),
|
|
210
|
+
componentProps: {},
|
|
211
|
+
data: void 0,
|
|
212
|
+
params: match.params,
|
|
213
|
+
routeId: match.route.id ?? "",
|
|
214
|
+
url: currentUrl,
|
|
215
|
+
version: ++routeStateVersion
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function resolveRedirectTarget(location) {
|
|
219
|
+
const targetUrl = parseSafeNavigationUrl(location, window.location.href);
|
|
220
|
+
if (!targetUrl) return {
|
|
221
|
+
isCurrentLocation: false,
|
|
222
|
+
unsafe: true
|
|
223
|
+
};
|
|
224
|
+
const fullInternalTarget = targetUrl.pathname + targetUrl.search + targetUrl.hash;
|
|
225
|
+
const internalPath = targetUrl.pathname + targetUrl.search;
|
|
226
|
+
const currentPath = window.location.pathname + window.location.search + window.location.hash;
|
|
227
|
+
const isCurrentLocation = targetUrl.origin === window.location.origin && fullInternalTarget === currentPath;
|
|
228
|
+
if (targetUrl.origin !== window.location.origin) return {
|
|
229
|
+
externalUrl: targetUrl.toString(),
|
|
230
|
+
isCurrentLocation: false
|
|
231
|
+
};
|
|
232
|
+
if (targetUrl.hash) return {
|
|
233
|
+
documentUrl: targetUrl.toString(),
|
|
234
|
+
isCurrentLocation
|
|
235
|
+
};
|
|
236
|
+
return {
|
|
237
|
+
internalPath,
|
|
238
|
+
isCurrentLocation
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async function navigate(to, opts) {
|
|
242
|
+
const navigationId = ++latestNavigationId;
|
|
243
|
+
activeNavigationAbort?.abort();
|
|
244
|
+
const abortController = new AbortController();
|
|
245
|
+
activeNavigationAbort = abortController;
|
|
246
|
+
const navigationTarget = typeof to === "string" ? to : buildHref(app.routes, to.route, to);
|
|
247
|
+
const target = resolveBrowserRouteTarget(navigationTarget);
|
|
248
|
+
if (!target) {
|
|
249
|
+
window.location.href = navigationTarget;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const match = matchAppRoute(app, target.pathname);
|
|
253
|
+
if (!match) {
|
|
254
|
+
window.location.href = target.browserUrl;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
let statePromise;
|
|
258
|
+
if (routeNeedsServerFetch(match.route)) statePromise = getCachedRouteState(target.requestUrl) ?? fetchPrachtRouteState(target.requestUrl, { signal: abortController.signal });
|
|
259
|
+
else statePromise = Promise.resolve({
|
|
260
|
+
type: "data",
|
|
261
|
+
data: void 0
|
|
262
|
+
});
|
|
263
|
+
const routeModPromise = startRouteImport(match);
|
|
264
|
+
const shellModPromise = startShellImport(match);
|
|
265
|
+
let state = {
|
|
266
|
+
data: void 0,
|
|
267
|
+
error: null
|
|
268
|
+
};
|
|
269
|
+
try {
|
|
270
|
+
const result = await statePromise;
|
|
271
|
+
if (navigationId !== latestNavigationId) return;
|
|
272
|
+
if (result.type === "redirect") {
|
|
273
|
+
if (result.location) {
|
|
274
|
+
const redirect = resolveRedirectTarget(result.location);
|
|
275
|
+
if (redirect.unsafe) {
|
|
276
|
+
console.error(`[pracht] refused to navigate to unsafe URL: ${result.location}`);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (redirect.externalUrl) {
|
|
280
|
+
window.location.href = redirect.externalUrl;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (redirect.isCurrentLocation) return;
|
|
284
|
+
if (redirect.documentUrl) {
|
|
285
|
+
window.location.href = redirect.documentUrl;
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (redirect.internalPath) {
|
|
289
|
+
await navigate(redirect.internalPath, opts);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
window.location.href = target.browserUrl;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
window.location.href = target.browserUrl;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (result.type === "error") state = {
|
|
299
|
+
data: void 0,
|
|
300
|
+
error: result.error
|
|
301
|
+
};
|
|
302
|
+
else state = {
|
|
303
|
+
data: result.data,
|
|
304
|
+
error: null
|
|
305
|
+
};
|
|
306
|
+
} catch {
|
|
307
|
+
if (abortController.signal.aborted || navigationId !== latestNavigationId) return;
|
|
308
|
+
window.location.href = target.browserUrl;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (navigationId !== latestNavigationId) return;
|
|
312
|
+
if (!opts?._popstate) if (opts?.replace) history.replaceState(null, "", target.browserUrl);
|
|
313
|
+
else history.pushState(null, "", target.browserUrl);
|
|
314
|
+
const routeState = await resolveRouteState(match, state, target.requestUrl, routeModPromise, shellModPromise);
|
|
315
|
+
if (navigationId !== latestNavigationId) return;
|
|
316
|
+
if (routeState) {
|
|
317
|
+
applyRouteState(routeState);
|
|
318
|
+
window.scrollTo(0, 0);
|
|
319
|
+
} else window.location.href = target.browserUrl;
|
|
320
|
+
}
|
|
321
|
+
const initialTarget = resolveBrowserRouteTarget(options.initialState.url);
|
|
322
|
+
const initialRequestUrl = initialTarget?.requestUrl ?? options.initialState.url;
|
|
323
|
+
const initialBrowserUrl = initialTarget?.browserUrl ?? options.initialState.url;
|
|
324
|
+
const initialMatch = matchAppRoute(app, initialTarget?.pathname ?? options.initialState.url);
|
|
325
|
+
if (initialMatch) {
|
|
326
|
+
const initialShellPromise = initialMatch.route.render === "spa" && options.initialState.pending ? startShellImport(initialMatch) : null;
|
|
327
|
+
let state = {
|
|
328
|
+
data: options.initialState.data,
|
|
329
|
+
error: options.initialState.error ?? null
|
|
330
|
+
};
|
|
331
|
+
if (initialMatch.route.render === "spa" && options.initialState.pending) {
|
|
332
|
+
const dataPromise = fetchPrachtRouteState(initialRequestUrl, { useDataParam: true });
|
|
333
|
+
const pendingState = await resolveSpaPendingState(initialMatch, initialRequestUrl, initialShellPromise);
|
|
334
|
+
if (pendingState) hydrate(h(RouterRoot, { initialState: pendingState }), root);
|
|
335
|
+
try {
|
|
336
|
+
const result = await dataPromise;
|
|
337
|
+
if (result.type === "redirect") {
|
|
338
|
+
const safeRedirect = parseSafeNavigationUrl(result.location, window.location.href);
|
|
339
|
+
if (!safeRedirect) {
|
|
340
|
+
console.error(`[pracht] refused to navigate to unsafe URL: ${result.location}`);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
window.location.href = safeRedirect.toString();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (result.type === "error") state = {
|
|
347
|
+
data: void 0,
|
|
348
|
+
error: result.error
|
|
349
|
+
};
|
|
350
|
+
else state = {
|
|
351
|
+
data: result.data,
|
|
352
|
+
error: null
|
|
353
|
+
};
|
|
354
|
+
} catch {
|
|
355
|
+
window.location.href = initialBrowserUrl;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const resolvedState = await resolveRouteState(initialMatch, state, initialRequestUrl, void 0, initialShellPromise);
|
|
359
|
+
if (resolvedState) applyRouteState(resolvedState);
|
|
360
|
+
} else {
|
|
361
|
+
const initialRouteState = await resolveRouteState(initialMatch, state, initialRequestUrl, void 0, initialShellPromise);
|
|
362
|
+
if (initialRouteState) if (initialMatch.route.render === "spa") render(h(RouterRoot, { initialState: initialRouteState }), root);
|
|
363
|
+
else {
|
|
364
|
+
markHydrating();
|
|
365
|
+
hydrate(h(RouterRoot, { initialState: initialRouteState }), root);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
document.addEventListener("click", (e) => {
|
|
370
|
+
const anchor = e.target.closest?.("a");
|
|
371
|
+
if (!anchor) return;
|
|
372
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
373
|
+
if (e.defaultPrevented) return;
|
|
374
|
+
if (e.button !== 0) return;
|
|
375
|
+
const target = anchor.getAttribute("target");
|
|
376
|
+
if (target && target !== "_self") return;
|
|
377
|
+
if (anchor.hasAttribute("download")) return;
|
|
378
|
+
const href = anchor.getAttribute("href");
|
|
379
|
+
if (!href || href.startsWith("#")) return;
|
|
380
|
+
let url;
|
|
381
|
+
try {
|
|
382
|
+
url = new URL(href, window.location.origin);
|
|
383
|
+
} catch {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (url.origin !== window.location.origin) return;
|
|
387
|
+
e.preventDefault();
|
|
388
|
+
navigate(url.pathname + url.search + url.hash);
|
|
389
|
+
});
|
|
390
|
+
window.addEventListener("popstate", () => {
|
|
391
|
+
navigate(window.location.pathname + window.location.search + window.location.hash, { _popstate: true });
|
|
392
|
+
});
|
|
393
|
+
window.__PRACHT_NAVIGATE__ = navigate;
|
|
394
|
+
window.__PRACHT_ROUTER_READY__ = true;
|
|
395
|
+
const warmModules = (match) => {
|
|
396
|
+
startRouteImport(match);
|
|
397
|
+
startShellImport(match);
|
|
398
|
+
};
|
|
399
|
+
import("./prefetch-B50kX9RL.mjs").then(({ setupPrefetching }) => {
|
|
400
|
+
setupPrefetching(app, warmModules);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
function resolveBrowserRouteTarget(to) {
|
|
404
|
+
if (typeof window === "undefined") return null;
|
|
405
|
+
try {
|
|
406
|
+
const url = new URL(to, window.location.href);
|
|
407
|
+
if (url.origin !== window.location.origin) return null;
|
|
408
|
+
return {
|
|
409
|
+
browserUrl: url.pathname + url.search + url.hash,
|
|
410
|
+
pathname: url.pathname,
|
|
411
|
+
requestUrl: url.pathname + url.search
|
|
412
|
+
};
|
|
413
|
+
} catch {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
//#endregion
|
|
418
|
+
export { useNavigate as n, useIsHydrated as r, initClientRouter as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { U as NavigateOptions, Z as ResolvedPrachtApp, dt as RouteTarget, nt as RouteId } from "./app-BWriseLj.mjs";
|
|
2
|
+
import { t as PrachtHydrationState } from "./runtime-context-ytVd7GmZ.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/router.d.ts
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
__PRACHT_NAVIGATE__?: NavigateFn;
|
|
8
|
+
__PRACHT_ROUTER_READY__?: boolean;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
type ModuleMap = Record<string, () => Promise<unknown>>;
|
|
12
|
+
interface NavigateFn {
|
|
13
|
+
(to: string, options?: NavigateOptions): Promise<void>;
|
|
14
|
+
<TRoute extends RouteId>(to: RouteTarget<TRoute>, options?: NavigateOptions): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
declare function useNavigate(): NavigateFn;
|
|
17
|
+
interface InitClientRouterOptions {
|
|
18
|
+
app: ResolvedPrachtApp;
|
|
19
|
+
routeModules: ModuleMap;
|
|
20
|
+
shellModules: ModuleMap;
|
|
21
|
+
initialState: PrachtHydrationState;
|
|
22
|
+
root: HTMLElement;
|
|
23
|
+
findModuleKey: (modules: ModuleMap, file: string) => string | null;
|
|
24
|
+
}
|
|
25
|
+
declare function initClientRouter(options: InitClientRouterOptions): Promise<void>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { useNavigate as i, NavigateFn as n, initClientRouter as r, InitClientRouterOptions as t };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { d as EMPTY_ROUTE_PARAMS, f as HYDRATION_STATE_ELEMENT_ID } from "./prefetch-cache-DzP2Bj9H.mjs";
|
|
2
|
+
import { createContext, h } from "preact";
|
|
3
|
+
import { useEffect, useMemo, useState } from "preact/hooks";
|
|
4
|
+
//#region src/runtime-errors.ts
|
|
5
|
+
function isPrachtHttpError(error) {
|
|
6
|
+
return error instanceof Error && error.name === "PrachtHttpError" && "status" in error;
|
|
7
|
+
}
|
|
8
|
+
let warnedAboutProductionDebugErrors = false;
|
|
9
|
+
/**
|
|
10
|
+
* `debugErrors: true` opts into surfacing stack traces, module paths,
|
|
11
|
+
* and middleware names in error responses. That is great in dev and
|
|
12
|
+
* dangerous in production — a misconfigured deploy would leak internals
|
|
13
|
+
* to the public. When `NODE_ENV === "production"` we refuse to honor
|
|
14
|
+
* the flag and emit a single console warning so the misconfiguration
|
|
15
|
+
* is visible in logs.
|
|
16
|
+
*/
|
|
17
|
+
function shouldExposeServerErrors(options) {
|
|
18
|
+
if (options.debugErrors !== true) return false;
|
|
19
|
+
if ((typeof process !== "undefined" && process.env ? process.env.NODE_ENV : typeof globalThis !== "undefined" && globalThis.process ? globalThis.process?.env?.NODE_ENV : void 0) === "production") {
|
|
20
|
+
if (!warnedAboutProductionDebugErrors) {
|
|
21
|
+
warnedAboutProductionDebugErrors = true;
|
|
22
|
+
console.warn("[pracht] debugErrors is ignored in production builds. Remove it to silence this warning.");
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
function createSerializedRouteError(message, status, options = {}) {
|
|
29
|
+
return {
|
|
30
|
+
message,
|
|
31
|
+
name: options.name ?? "Error",
|
|
32
|
+
status,
|
|
33
|
+
...options.diagnostics ? { diagnostics: options.diagnostics } : {}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function buildRuntimeDiagnostics(options) {
|
|
37
|
+
const route = options.route;
|
|
38
|
+
const routeId = route && "id" in route ? route.id : void 0;
|
|
39
|
+
return {
|
|
40
|
+
phase: options.phase,
|
|
41
|
+
routeId,
|
|
42
|
+
routePath: route?.path,
|
|
43
|
+
routeFile: route?.file,
|
|
44
|
+
loaderFile: options.loaderFile,
|
|
45
|
+
shellFile: options.shellFile,
|
|
46
|
+
middlewareFiles: options.middlewareFiles ? [...options.middlewareFiles] : [],
|
|
47
|
+
status: options.status
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function normalizeRouteError(error, options) {
|
|
51
|
+
if (isPrachtHttpError(error)) {
|
|
52
|
+
const status = typeof error.status === "number" ? error.status : 500;
|
|
53
|
+
if (status >= 400 && status < 500) return {
|
|
54
|
+
message: error.message,
|
|
55
|
+
name: error.name,
|
|
56
|
+
status
|
|
57
|
+
};
|
|
58
|
+
if (options.exposeDetails) return {
|
|
59
|
+
message: error.message || "Internal Server Error",
|
|
60
|
+
name: error.name || "Error",
|
|
61
|
+
status
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
message: "Internal Server Error",
|
|
65
|
+
name: "Error",
|
|
66
|
+
status
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (error instanceof Error) {
|
|
70
|
+
if (options.exposeDetails) return {
|
|
71
|
+
message: error.message || "Internal Server Error",
|
|
72
|
+
name: error.name || "Error",
|
|
73
|
+
status: 500
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
message: "Internal Server Error",
|
|
77
|
+
name: "Error",
|
|
78
|
+
status: 500
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (options.exposeDetails) return {
|
|
82
|
+
message: typeof error === "string" && error ? error : "Internal Server Error",
|
|
83
|
+
name: "Error",
|
|
84
|
+
status: 500
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
message: "Internal Server Error",
|
|
88
|
+
name: "Error",
|
|
89
|
+
status: 500
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function deserializeRouteError(error) {
|
|
93
|
+
const result = new Error(error.message);
|
|
94
|
+
result.name = error.name;
|
|
95
|
+
result.status = error.status;
|
|
96
|
+
result.diagnostics = error.diagnostics;
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/runtime-context.ts
|
|
101
|
+
const RouteDataContext = createContext(void 0);
|
|
102
|
+
function PrachtRuntimeProvider({ children, data, params = EMPTY_ROUTE_PARAMS, routeId, routes, stateVersion = 0, url }) {
|
|
103
|
+
registerRuntimeRoutes(routes);
|
|
104
|
+
const [routeDataState, setRouteDataState] = useState({
|
|
105
|
+
data,
|
|
106
|
+
stateVersion
|
|
107
|
+
});
|
|
108
|
+
const routeData = routeDataState.stateVersion === stateVersion ? routeDataState.data : data;
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
setRouteDataState({
|
|
111
|
+
data,
|
|
112
|
+
stateVersion
|
|
113
|
+
});
|
|
114
|
+
}, [
|
|
115
|
+
data,
|
|
116
|
+
routeId,
|
|
117
|
+
stateVersion,
|
|
118
|
+
url
|
|
119
|
+
]);
|
|
120
|
+
const context = useMemo(() => ({
|
|
121
|
+
data: routeData,
|
|
122
|
+
params,
|
|
123
|
+
routeId,
|
|
124
|
+
routes,
|
|
125
|
+
setData: (nextData) => setRouteDataState({
|
|
126
|
+
data: nextData,
|
|
127
|
+
stateVersion
|
|
128
|
+
}),
|
|
129
|
+
url
|
|
130
|
+
}), [
|
|
131
|
+
routeData,
|
|
132
|
+
params,
|
|
133
|
+
routeId,
|
|
134
|
+
routes,
|
|
135
|
+
stateVersion,
|
|
136
|
+
url
|
|
137
|
+
]);
|
|
138
|
+
return h(RouteDataContext.Provider, {
|
|
139
|
+
value: context,
|
|
140
|
+
children
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function startApp(options = {}) {
|
|
144
|
+
if (typeof window === "undefined") return options.initialData;
|
|
145
|
+
if (typeof options.initialData !== "undefined") return options.initialData;
|
|
146
|
+
return readHydrationState()?.data;
|
|
147
|
+
}
|
|
148
|
+
function readHydrationState() {
|
|
149
|
+
if (typeof window === "undefined") return;
|
|
150
|
+
if (window.__PRACHT_STATE__) return window.__PRACHT_STATE__;
|
|
151
|
+
const element = document.getElementById(HYDRATION_STATE_ELEMENT_ID);
|
|
152
|
+
if (!(element instanceof HTMLScriptElement)) return;
|
|
153
|
+
const raw = element.textContent;
|
|
154
|
+
if (!raw) return;
|
|
155
|
+
const state = JSON.parse(raw);
|
|
156
|
+
window.__PRACHT_STATE__ = state;
|
|
157
|
+
return state;
|
|
158
|
+
}
|
|
159
|
+
function registerRuntimeRoutes(routes) {
|
|
160
|
+
if (!routes) return;
|
|
161
|
+
globalThis.__PRACHT_ROUTE_DEFINITIONS__ = routes;
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { buildRuntimeDiagnostics as a, normalizeRouteError as c, startApp as i, shouldExposeServerErrors as l, RouteDataContext as n, createSerializedRouteError as o, readHydrationState as r, deserializeRouteError as s, PrachtRuntimeProvider as t };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { A as HrefRouteDefinition, st as RouteParams } from "./app-BWriseLj.mjs";
|
|
2
|
+
import * as _$preact from "preact";
|
|
3
|
+
import { ComponentChildren } from "preact";
|
|
4
|
+
|
|
5
|
+
//#region src/runtime-errors.d.ts
|
|
6
|
+
type PrachtRuntimeDiagnosticPhase = "match" | "middleware" | "loader" | "action" | "render" | "api";
|
|
7
|
+
interface PrachtRuntimeDiagnostics {
|
|
8
|
+
phase: PrachtRuntimeDiagnosticPhase;
|
|
9
|
+
routeId?: string;
|
|
10
|
+
routePath?: string;
|
|
11
|
+
routeFile?: string;
|
|
12
|
+
loaderFile?: string;
|
|
13
|
+
shellFile?: string;
|
|
14
|
+
middlewareFiles?: string[];
|
|
15
|
+
status: number;
|
|
16
|
+
}
|
|
17
|
+
interface SerializedRouteError {
|
|
18
|
+
message: string;
|
|
19
|
+
name: string;
|
|
20
|
+
status: number;
|
|
21
|
+
diagnostics?: PrachtRuntimeDiagnostics;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/runtime-context.d.ts
|
|
25
|
+
interface PrachtHydrationState<TData = unknown> {
|
|
26
|
+
url: string;
|
|
27
|
+
routeId: string;
|
|
28
|
+
data: TData;
|
|
29
|
+
error?: SerializedRouteError | null;
|
|
30
|
+
pending?: boolean;
|
|
31
|
+
}
|
|
32
|
+
interface StartAppOptions<TData = unknown> {
|
|
33
|
+
initialData?: TData;
|
|
34
|
+
}
|
|
35
|
+
declare global {
|
|
36
|
+
var __PRACHT_ROUTE_DEFINITIONS__: readonly HrefRouteDefinition[] | undefined;
|
|
37
|
+
interface Window {
|
|
38
|
+
__PRACHT_STATE__?: PrachtHydrationState;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
interface PrachtRuntimeValue {
|
|
42
|
+
data: unknown;
|
|
43
|
+
params: RouteParams;
|
|
44
|
+
routeId: string;
|
|
45
|
+
routes?: readonly HrefRouteDefinition[];
|
|
46
|
+
url: string;
|
|
47
|
+
setData: (data: unknown) => void;
|
|
48
|
+
}
|
|
49
|
+
declare function PrachtRuntimeProvider<TData>({
|
|
50
|
+
children,
|
|
51
|
+
data,
|
|
52
|
+
params,
|
|
53
|
+
routeId,
|
|
54
|
+
routes,
|
|
55
|
+
stateVersion,
|
|
56
|
+
url
|
|
57
|
+
}: {
|
|
58
|
+
children: ComponentChildren;
|
|
59
|
+
data: TData;
|
|
60
|
+
params?: RouteParams;
|
|
61
|
+
routeId: string;
|
|
62
|
+
routes?: readonly HrefRouteDefinition[];
|
|
63
|
+
stateVersion?: number;
|
|
64
|
+
url: string;
|
|
65
|
+
}): _$preact.VNode<_$preact.Attributes & {
|
|
66
|
+
value: PrachtRuntimeValue | undefined;
|
|
67
|
+
children?: ComponentChildren;
|
|
68
|
+
}>;
|
|
69
|
+
declare function startApp<TData = unknown>(options?: StartAppOptions<TData>): TData | undefined;
|
|
70
|
+
declare function readHydrationState<TData = unknown>(): PrachtHydrationState<TData> | undefined;
|
|
71
|
+
//#endregion
|
|
72
|
+
export { startApp as a, SerializedRouteError as c, readHydrationState as i, PrachtRuntimeProvider as n, PrachtRuntimeDiagnosticPhase as o, StartAppOptions as r, PrachtRuntimeDiagnostics as s, PrachtHydrationState as t };
|