@qwik.dev/router 2.0.0-beta.4 → 2.0.0-beta.40
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/adapters/static/vite.d.ts +1 -1
- package/lib/adapters/azure-swa/vite/index.d.ts +2 -2
- package/lib/adapters/azure-swa/vite/index.mjs +44 -58
- package/lib/adapters/bun-server/vite/index.d.ts +2 -2
- package/lib/adapters/bun-server/vite/index.mjs +16 -23
- package/lib/adapters/cloud-run/vite/index.d.ts +2 -2
- package/lib/adapters/cloud-run/vite/index.mjs +15 -20
- package/lib/adapters/cloudflare-pages/vite/index.d.ts +2 -2
- package/lib/adapters/cloudflare-pages/vite/index.mjs +45 -77
- package/lib/adapters/deno-server/vite/index.d.ts +2 -2
- package/lib/adapters/deno-server/vite/index.mjs +25 -35
- package/lib/adapters/netlify-edge/vite/index.d.ts +2 -2
- package/lib/adapters/netlify-edge/vite/index.mjs +49 -89
- package/lib/adapters/node-server/vite/index.d.ts +2 -2
- package/lib/adapters/node-server/vite/index.mjs +16 -23
- package/lib/adapters/shared/vite/index.d.ts +9 -22
- package/lib/adapters/shared/vite/index.mjs +273 -333
- package/lib/adapters/ssg/vite/index.d.ts +13 -0
- package/lib/adapters/ssg/vite/index.mjs +13 -0
- package/lib/adapters/vercel-edge/vite/index.d.ts +3 -3
- package/lib/adapters/vercel-edge/vite/index.mjs +73 -80
- package/lib/chunks/dev-preloaded-route-loader.mjs +2 -0
- package/lib/chunks/format.mjs +24 -0
- package/lib/chunks/fs.mjs +133 -0
- package/lib/chunks/head.qwik.mjs +1217 -0
- package/lib/chunks/http-error.qwik.mjs +27 -0
- package/lib/chunks/pathname.mjs +57 -0
- package/lib/chunks/request-body-limit.mjs +27 -0
- package/lib/chunks/request-path.mjs +49 -0
- package/lib/chunks/routes.mjs +26 -0
- package/lib/chunks/server-error.mjs +16 -0
- package/lib/chunks/system.mjs +265 -0
- package/lib/chunks/url.mjs +59 -0
- package/lib/chunks/user-response.mjs +1707 -0
- package/lib/chunks/worker-thread.mjs +264 -0
- package/lib/index.d.ts +626 -170
- package/lib/index.qwik.mjs +1649 -1964
- package/lib/middleware/aws-lambda/index.d.ts +15 -16
- package/lib/middleware/aws-lambda/index.mjs +45 -54
- package/lib/middleware/azure-swa/index.mjs +85 -295
- package/lib/middleware/bun/index.d.ts +20 -5
- package/lib/middleware/bun/index.mjs +119 -205
- package/lib/middleware/cloudflare-pages/index.mjs +83 -105
- package/lib/middleware/deno/index.d.ts +22 -5
- package/lib/middleware/deno/index.mjs +105 -192
- package/lib/middleware/firebase/index.mjs +22 -35
- package/lib/middleware/netlify-edge/index.mjs +63 -81
- package/lib/middleware/node/index.d.ts +17 -5
- package/lib/middleware/node/index.mjs +299 -273
- package/lib/middleware/request-handler/index.d.ts +206 -99
- package/lib/middleware/request-handler/index.mjs +117 -1648
- package/lib/middleware/vercel-edge/index.mjs +76 -101
- package/lib/modules.d.ts +11 -16
- package/lib/service-worker/index.mjs +13 -0
- package/lib/{static → ssg}/index.d.ts +46 -22
- package/lib/ssg/index.mjs +257 -0
- package/lib/vite/index.d.ts +58 -10
- package/lib/vite/index.mjs +2459 -27446
- package/modules.d.ts +11 -16
- package/package.json +63 -67
- package/ssg.d.ts +2 -0
- package/static.d.ts +1 -1
- package/lib/adapters/azure-swa/vite/index.cjs +0 -96
- package/lib/adapters/bun-server/vite/index.cjs +0 -50
- package/lib/adapters/cloud-run/vite/index.cjs +0 -47
- package/lib/adapters/cloudflare-pages/vite/index.cjs +0 -115
- package/lib/adapters/deno-server/vite/index.cjs +0 -62
- package/lib/adapters/netlify-edge/vite/index.cjs +0 -129
- package/lib/adapters/node-server/vite/index.cjs +0 -50
- package/lib/adapters/shared/vite/index.cjs +0 -378
- package/lib/adapters/static/vite/index.cjs +0 -368
- package/lib/adapters/static/vite/index.d.ts +0 -10
- package/lib/adapters/static/vite/index.mjs +0 -331
- package/lib/adapters/vercel-edge/vite/index.cjs +0 -118
- package/lib/index.qwik.cjs +0 -2022
- package/lib/middleware/node/index.cjs +0 -314
- package/lib/middleware/request-handler/index.cjs +0 -1691
- package/lib/service-worker.cjs +0 -17
- package/lib/service-worker.mjs +0 -15
- package/lib/static/deno.mjs +0 -8
- package/lib/static/index.cjs +0 -67
- package/lib/static/index.mjs +0 -48
- package/lib/static/node.cjs +0 -1126
- package/lib/static/node.mjs +0 -1088
- package/lib/vite/index.cjs +0 -27525
- package/middleware/request-handler/generated/not-found-paths.ts +0 -7
- package/middleware/request-handler/generated/static-paths.ts +0 -35
|
@@ -0,0 +1,1217 @@
|
|
|
1
|
+
import { t as ensureSlash } from "./pathname.mjs";
|
|
2
|
+
import { o as getLoaderName } from "./request-path.mjs";
|
|
3
|
+
import { i as RedirectMessage, n as throwIfControlFlowSignal, t as ServerError } from "./server-error.mjs";
|
|
4
|
+
import { _captures, createComputedQrl, createContextId, implicit$FirstArg, inlinedQrl, isDev, isServer, withLocale } from "@qwik.dev/core";
|
|
5
|
+
import * as qwikRouterConfig from "@qwik-router-config";
|
|
6
|
+
import { SerializerSymbol, _UNINITIALIZED, _deserialize, _getAsyncLocalStorage, _getContextEvent, _injectAsyncSignalValue, _markSignalAsExternallyOwned, _resolveContextWithoutSequentialScope, _verifySerializable } from "@qwik.dev/core/internal";
|
|
7
|
+
import { isServer as isServer$1 } from "@qwik.dev/core/build";
|
|
8
|
+
/** @internal */ var _asyncRequestStore;
|
|
9
|
+
if (isServer$1) {
|
|
10
|
+
const AsyncLocalStorage = _getAsyncLocalStorage();
|
|
11
|
+
if (AsyncLocalStorage) _asyncRequestStore = new AsyncLocalStorage();
|
|
12
|
+
else console.warn("\n=====================\n Qwik Router Warning:\n AsyncLocalStorage is not available, continuing without it.\n This impacts concurrent async server calls, where they lose access to the ServerRequestEv object.\n=====================\n\n");
|
|
13
|
+
}
|
|
14
|
+
var QACTION_KEY = "qaction";
|
|
15
|
+
var QFN_KEY = "qfunc";
|
|
16
|
+
var QDATA_KEY = "qdata";
|
|
17
|
+
/** @public */ var Q_ROUTE = "q:route";
|
|
18
|
+
var DEFAULT_LOADERS_SERIALIZATION_STRATEGY = globalThis.__DEFAULT_LOADERS_SERIALIZATION_STRATEGY__ || "never";
|
|
19
|
+
var RouteStateContext = /*#__PURE__*/ createContextId("qr-s");
|
|
20
|
+
var RouteLoaderCtxContext = /*#__PURE__*/ createContextId("qr-lc");
|
|
21
|
+
var ContentContext = /*#__PURE__*/ createContextId("qr-c");
|
|
22
|
+
var ContentInternalContext = /*#__PURE__*/ createContextId("qr-ic");
|
|
23
|
+
var DocumentHeadContext = /*#__PURE__*/ createContextId("qr-h");
|
|
24
|
+
var RouteLocationContext = /*#__PURE__*/ createContextId("qr-l");
|
|
25
|
+
var RouteNavigateContext = /*#__PURE__*/ createContextId("qr-n");
|
|
26
|
+
var RouteActionContext = /*#__PURE__*/ createContextId("qr-a");
|
|
27
|
+
var RoutePreventNavigateContext = /*#__PURE__*/ createContextId("qr-p");
|
|
28
|
+
var HttpStatusContext = /*#__PURE__*/ createContextId("qr-hs");
|
|
29
|
+
/**
|
|
30
|
+
* Route loaders read data before the route rendering starts, based on the route being navigated to.
|
|
31
|
+
* They automatically update when the route changes on the client.
|
|
32
|
+
*
|
|
33
|
+
* They are represented by a ComputedSignal.
|
|
34
|
+
*/ var REQUEST_ROUTE_LOADER_STATE = "@routeLoaderState";
|
|
35
|
+
var REQUEST_LOADER_PATHS_STORE = "@loaderPathsStore";
|
|
36
|
+
var REQUEST_ROUTE_LOADERS = "@routeLoaders";
|
|
37
|
+
var REQUEST_ROUTE_LOADER_PROMISES = "@routeLoaderPromises";
|
|
38
|
+
var REQUEST_ROUTE_LOADER_EVENTS = "@routeLoaderEvents";
|
|
39
|
+
var REQUEST_ROUTE_LOADER_ROOT_EVENT = "@routeLoaderRootEvent";
|
|
40
|
+
var ROUTE_LOADER_VALUE_PREFIX = "__qwik_route_loader_value__";
|
|
41
|
+
/** Header name sent by client to tell the server the actual page URL for loader requests. */ var FULLPATH_HEADER = "X-Qwik-fullpath";
|
|
42
|
+
/** Dev route hint that preserves the loader URL. */ var ROUTE_PATH_HEADER = "X-Qwik-route-path";
|
|
43
|
+
/**
|
|
44
|
+
* Per-navigation fetch dedupe: shares in-flight and completed fetches (hover prefetch → click nav)
|
|
45
|
+
* and prevents repeat hover fetches. Cleared after each navigation kicks off its loader fetches;
|
|
46
|
+
* across navigations the browser HTTP cache is the freshness authority.
|
|
47
|
+
*/ var navFetchCache = /* @__PURE__ */ new Map();
|
|
48
|
+
var clearNavFetchCache = () => navFetchCache.clear();
|
|
49
|
+
var isRedirectStatus = (status) => status >= 300 && status < 400;
|
|
50
|
+
/** We don't have aborts when preloading so we just pretend we do */ var wrapWithAbort = (promise, signal) => {
|
|
51
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const abort = () => reject(signal.reason);
|
|
54
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
55
|
+
promise.finally(() => signal.removeEventListener("abort", abort)).then(resolve, reject);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
var getRouteLoaderValueStateKey = (loaderId) => `${ROUTE_LOADER_VALUE_PREFIX}${loaderId}`;
|
|
59
|
+
var isRequestEvent = (value) => !!value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, "sharedMap") && Object.prototype.hasOwnProperty.call(value, "cookie");
|
|
60
|
+
var isLoaderInternal = (value) => typeof value === "function" && value.__brand === "server_loader";
|
|
61
|
+
/**
|
|
62
|
+
* Fetch a single loader's raw JSON response from the server.
|
|
63
|
+
*
|
|
64
|
+
* URL pattern: `{basePath}{routePath}/q-loader-{loaderId}.{manifestHash}.json`
|
|
65
|
+
*/ var fetchRouteLoaderData = async (loaderId, routePath, manifestHash, opts) => {
|
|
66
|
+
if (!routePath) return;
|
|
67
|
+
let resolvedPath = routePath;
|
|
68
|
+
const basePath = opts?.basePath ?? "/";
|
|
69
|
+
if (basePath !== "/" && !resolvedPath.startsWith(basePath)) resolvedPath = basePath + resolvedPath.slice(1);
|
|
70
|
+
const pathBase = ensureSlash(resolvedPath);
|
|
71
|
+
const pageUrl = opts?.pageUrl;
|
|
72
|
+
const search = pageUrl?.search ?? "";
|
|
73
|
+
const url = `${pathBase}${getLoaderName(loaderId, manifestHash)}${search}`;
|
|
74
|
+
const headers = {};
|
|
75
|
+
if (pageUrl && pageUrl.pathname !== pathBase) {
|
|
76
|
+
if (!globalThis.__STRICT_LOADERS__) headers[FULLPATH_HEADER] = pageUrl.pathname;
|
|
77
|
+
else if (isDev) headers[ROUTE_PATH_HEADER] = pageUrl.pathname;
|
|
78
|
+
}
|
|
79
|
+
const cacheKey = `${url}\n${headers["X-Qwik-fullpath"] ?? ""}`;
|
|
80
|
+
if (!opts?.ignoreCache) {
|
|
81
|
+
const entry = navFetchCache.get(cacheKey);
|
|
82
|
+
if (entry) return opts?.signal ? wrapWithAbort(entry, opts.signal) : entry;
|
|
83
|
+
}
|
|
84
|
+
const request = async () => {
|
|
85
|
+
const response = await fetch(url, {
|
|
86
|
+
signal: opts?.signal,
|
|
87
|
+
cache: opts?.ignoreCache ? "reload" : "default",
|
|
88
|
+
headers
|
|
89
|
+
});
|
|
90
|
+
if (response.redirected) return { r: response.url };
|
|
91
|
+
if (isRedirectStatus(response.status)) {
|
|
92
|
+
const location1 = response.headers.get("Location");
|
|
93
|
+
if (location1) return { r: location1 };
|
|
94
|
+
}
|
|
95
|
+
if (!response.ok) return;
|
|
96
|
+
return { raw: await response.text() };
|
|
97
|
+
};
|
|
98
|
+
if (opts?.ignoreCache) return request();
|
|
99
|
+
if (opts?.signal) return request().then((value) => {
|
|
100
|
+
if (value !== void 0 && !navFetchCache.has(cacheKey)) navFetchCache.set(cacheKey, Promise.resolve(value));
|
|
101
|
+
return value;
|
|
102
|
+
});
|
|
103
|
+
const promise = request().then((value) => {
|
|
104
|
+
if (value === void 0) navFetchCache.delete(cacheKey);
|
|
105
|
+
return value;
|
|
106
|
+
}, (err) => {
|
|
107
|
+
navFetchCache.delete(cacheKey);
|
|
108
|
+
throw err;
|
|
109
|
+
});
|
|
110
|
+
navFetchCache.set(cacheKey, promise);
|
|
111
|
+
return promise;
|
|
112
|
+
};
|
|
113
|
+
/** Build a sorted, stable search string from only the allowed param names. */ var filterSearchParams = (params, allowed) => {
|
|
114
|
+
const filtered = new URLSearchParams();
|
|
115
|
+
for (let i = 0; i < allowed.length; i++) {
|
|
116
|
+
const name = allowed[i];
|
|
117
|
+
const values = params.getAll(name);
|
|
118
|
+
for (let j = 0; j < values.length; j++) filtered.append(name, values[j]);
|
|
119
|
+
}
|
|
120
|
+
filtered.sort();
|
|
121
|
+
return filtered.toString() ? `?${filtered.toString()}` : "";
|
|
122
|
+
};
|
|
123
|
+
var getLoaderOptions = (rest) => {
|
|
124
|
+
let id;
|
|
125
|
+
let serializationStrategy = DEFAULT_LOADERS_SERIALIZATION_STRATEGY;
|
|
126
|
+
let cacheControl;
|
|
127
|
+
let eTag;
|
|
128
|
+
let cacheKey;
|
|
129
|
+
let search;
|
|
130
|
+
let blockSSR = true;
|
|
131
|
+
const validators = [];
|
|
132
|
+
if (rest.length === 1) {
|
|
133
|
+
const options = rest[0];
|
|
134
|
+
if (options && typeof options === "object") {
|
|
135
|
+
if ("validate" in options) validators.push(options);
|
|
136
|
+
else {
|
|
137
|
+
if (options.id) id = options.id;
|
|
138
|
+
if (options.serializationStrategy) serializationStrategy = options.serializationStrategy;
|
|
139
|
+
if (options.validation) validators.push(...options.validation);
|
|
140
|
+
if ("cacheControl" in options) cacheControl = options.cacheControl;
|
|
141
|
+
if ("eTag" in options) eTag = options.eTag;
|
|
142
|
+
if ("cacheKey" in options) cacheKey = options.cacheKey;
|
|
143
|
+
if (options.search) search = options.search;
|
|
144
|
+
else if (globalThis.__STRICT_LOADERS__) search = [];
|
|
145
|
+
if (options.blockSSR === false) {
|
|
146
|
+
if (!__EXPERIMENTAL__.blockSSR) throw new Error("`blockSSR: false` is an experimental feature and is not enabled. Please enable the feature flag by adding `experimental: [\"blockSSR\"]` to your qwikVite plugin options.");
|
|
147
|
+
blockSSR = false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} else if (rest.length > 1) validators.push(...rest.filter(Boolean));
|
|
152
|
+
return {
|
|
153
|
+
id,
|
|
154
|
+
validators: validators.reverse(),
|
|
155
|
+
serializationStrategy,
|
|
156
|
+
cacheControl,
|
|
157
|
+
eTag,
|
|
158
|
+
cacheKey,
|
|
159
|
+
search,
|
|
160
|
+
blockSSR
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Returns the current RequestEvent if possible. Only usable on the server, and only during request
|
|
165
|
+
* processing.
|
|
166
|
+
*
|
|
167
|
+
* @public
|
|
168
|
+
*/ var getRequestEvent = (thisArg) => {
|
|
169
|
+
if (!isServer) throw new Error("getRequestEvent() can only be used on the server.");
|
|
170
|
+
return _asyncRequestStore?.getStore() || [thisArg, _getContextEvent()].find(isRequestEvent);
|
|
171
|
+
};
|
|
172
|
+
var REQUEST_ROUTE_LOADER_VALUES = "@routeLoaderValues";
|
|
173
|
+
/** Get/create the record of pre-loaded loader values (used by middleware before component). */ function getRouteLoaderValues(requestEv) {
|
|
174
|
+
let values = requestEv.sharedMap.get(REQUEST_ROUTE_LOADER_VALUES);
|
|
175
|
+
if (!values) {
|
|
176
|
+
values = {};
|
|
177
|
+
requestEv.sharedMap.set(REQUEST_ROUTE_LOADER_VALUES, values);
|
|
178
|
+
}
|
|
179
|
+
return values;
|
|
180
|
+
}
|
|
181
|
+
function getRouteLoaderPromises(requestEv) {
|
|
182
|
+
let promises = requestEv.sharedMap.get(REQUEST_ROUTE_LOADER_PROMISES);
|
|
183
|
+
if (!promises) {
|
|
184
|
+
promises = {};
|
|
185
|
+
requestEv.sharedMap.set(REQUEST_ROUTE_LOADER_PROMISES, promises);
|
|
186
|
+
}
|
|
187
|
+
return promises;
|
|
188
|
+
}
|
|
189
|
+
/** Clear loader state before rendering a route-independent error boundary. */ function clearRouteLoaderData(requestEv) {
|
|
190
|
+
requestEv.sharedMap.delete(REQUEST_ROUTE_LOADER_STATE);
|
|
191
|
+
requestEv.sharedMap.delete(REQUEST_ROUTE_LOADER_VALUES);
|
|
192
|
+
requestEv.sharedMap.delete(REQUEST_ROUTE_LOADER_PROMISES);
|
|
193
|
+
requestEv.sharedMap.delete(REQUEST_ROUTE_LOADER_EVENTS);
|
|
194
|
+
requestEv.sharedMap.delete(REQUEST_ROUTE_LOADER_ROOT_EVENT);
|
|
195
|
+
requestEv.sharedMap.delete(REQUEST_ROUTE_LOADERS);
|
|
196
|
+
}
|
|
197
|
+
/** Store the route loader internals on the request for SSG to read. */ function setRouteLoaders(requestEv, loaders) {
|
|
198
|
+
requestEv.sharedMap.set(REQUEST_ROUTE_LOADERS, loaders);
|
|
199
|
+
}
|
|
200
|
+
/** Get the route loader internals stored on the request. */ function getRouteLoaders(requestEv) {
|
|
201
|
+
return requestEv.sharedMap.get(REQUEST_ROUTE_LOADERS) ?? [];
|
|
202
|
+
}
|
|
203
|
+
function getRouteLoaderCtx(requestEv) {
|
|
204
|
+
let ctx = requestEv.sharedMap.get(REQUEST_LOADER_PATHS_STORE);
|
|
205
|
+
if (!ctx) {
|
|
206
|
+
ctx = { loaderPaths: {} };
|
|
207
|
+
requestEv.sharedMap.set(REQUEST_LOADER_PATHS_STORE, ctx);
|
|
208
|
+
}
|
|
209
|
+
return ctx;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Update the loader paths store on client-side navigation.
|
|
213
|
+
*
|
|
214
|
+
* Only adds/updates entries for loaders present on the new route. Entries for loaders that are NOT
|
|
215
|
+
* on the new route are left untouched — their ComputedSignals keep their prior values, and no
|
|
216
|
+
* track() fires to invalidate them. If the user navigates back to a route where the loader IS
|
|
217
|
+
* present, the path updates and the signal re-fetches. This is the "stale is fine by default"
|
|
218
|
+
* contract: readers see old data until new data arrives.
|
|
219
|
+
*/ var updateRouteLoaderPaths = (ctx, loaderPaths, pageUrl) => {
|
|
220
|
+
if (!isServer) {
|
|
221
|
+
ctx.pagePathname = pageUrl.pathname;
|
|
222
|
+
ctx.pageSearch = pageUrl.search;
|
|
223
|
+
if (isDev) ctx.devRouteLoaderPaths = {};
|
|
224
|
+
}
|
|
225
|
+
if (loaderPaths) for (const key in loaderPaths) {
|
|
226
|
+
ctx.loaderPaths[key] = loaderPaths[key];
|
|
227
|
+
if (!isServer && isDev) ctx.devRouteLoaderPaths[key] = true;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
var getModuleRouteLoaders = (mods) => {
|
|
231
|
+
const routeLoaders = [];
|
|
232
|
+
const seen = /* @__PURE__ */ new Map();
|
|
233
|
+
for (let i = 0; i < mods.length; i++) {
|
|
234
|
+
const mod = mods[i];
|
|
235
|
+
if (!mod) continue;
|
|
236
|
+
for (const key in mod) {
|
|
237
|
+
const value = mod[key];
|
|
238
|
+
if (isLoaderInternal(value)) {
|
|
239
|
+
const existing = seen.get(value.__id);
|
|
240
|
+
if (existing) {
|
|
241
|
+
if (isDev && existing !== value) console.warn(`Two route loaders share the same id "${value.__id}". Only the first will run; the others are ignored. If they are created by a shared wrapper around routeLoader$, give each loader a distinct \`id\` option.`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
seen.set(value.__id, value);
|
|
245
|
+
routeLoaders.push(value);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return routeLoaders;
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Loader ids declared `cacheControl: 'immutable'`. Their data cannot change until a rebuild, so
|
|
253
|
+
* nav-wide invalidation skips them; they still re-fetch when their tracked URL inputs change.
|
|
254
|
+
* Registered on every nav via ensureRouteLoaderSignal, so resumed signals are covered too.
|
|
255
|
+
*/ var immutableLoaderIds = /* @__PURE__ */ new Set();
|
|
256
|
+
var isImmutableLoader = (loaderId) => immutableLoaderIds.has(loaderId);
|
|
257
|
+
/**
|
|
258
|
+
* Invalidate every loader signal on navigation: the browser HTTP cache (driven by each loader's
|
|
259
|
+
* cacheControl) decides freshness. Unsubscribed signals only mark stale and refetch lazily when
|
|
260
|
+
* read again. Immutable loaders are skipped — their data cannot change until a rebuild; tracked URL
|
|
261
|
+
* inputs still invalidate them.
|
|
262
|
+
*/ var invalidateNavRouteLoaders = (state) => {
|
|
263
|
+
for (const id in state) if (!id.startsWith(ROUTE_LOADER_VALUE_PREFIX) && !immutableLoaderIds.has(id)) state[id].invalidate();
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Inject a pre-loaded value into a ComputedSignal while preserving subscriptions. Delegates to the
|
|
267
|
+
* core helper which calls `invalidate({__v})` + `$computeIfNeeded$()` so the compute function runs
|
|
268
|
+
* synchronously and returns the pre-loaded value without fetching. Must go through the core helper
|
|
269
|
+
* because `$computeIfNeeded$` is mangled in core builds and not directly callable from this
|
|
270
|
+
* package.
|
|
271
|
+
*/ var setLoaderSignalValue = (signal, value) => {
|
|
272
|
+
_injectAsyncSignalValue(signal, value);
|
|
273
|
+
};
|
|
274
|
+
var matchesRouteLoaderId = (loader, loaderId) => {
|
|
275
|
+
return typeof loaderId === "string" && (loader.__id === loaderId || isDev && loader.__qrl.getHash() === loaderId);
|
|
276
|
+
};
|
|
277
|
+
var resolveRouteLoaderByHash = (routeLoaders, loaderId) => {
|
|
278
|
+
return routeLoaders.find((loader) => matchesRouteLoaderId(loader, loaderId));
|
|
279
|
+
};
|
|
280
|
+
/** @internal */ var getLoaderRequestEvent = (loader, requestEv) => {
|
|
281
|
+
let rootRequestEv = requestEv.sharedMap.get(REQUEST_ROUTE_LOADER_ROOT_EVENT);
|
|
282
|
+
if (!rootRequestEv) {
|
|
283
|
+
rootRequestEv = requestEv;
|
|
284
|
+
requestEv.sharedMap.set(REQUEST_ROUTE_LOADER_ROOT_EVENT, rootRequestEv);
|
|
285
|
+
}
|
|
286
|
+
const url = new URL(rootRequestEv.url);
|
|
287
|
+
const pathname = globalThis.__STRICT_LOADERS__ ? getRouteLoaderCtx(rootRequestEv).loaderPaths[loader.__id] || rootRequestEv.url.pathname : rootRequestEv.url.pathname;
|
|
288
|
+
const filteredSearch = loader.__search ? filterSearchParams(url.searchParams, loader.__search) : rootRequestEv.url.search;
|
|
289
|
+
if (pathname === rootRequestEv.url.pathname && filteredSearch === rootRequestEv.url.search) return rootRequestEv;
|
|
290
|
+
let events = rootRequestEv.sharedMap.get(REQUEST_ROUTE_LOADER_EVENTS);
|
|
291
|
+
if (!events) {
|
|
292
|
+
events = /* @__PURE__ */ new Map();
|
|
293
|
+
rootRequestEv.sharedMap.set(REQUEST_ROUTE_LOADER_EVENTS, events);
|
|
294
|
+
}
|
|
295
|
+
const eventKey = `${pathname}\n${filteredSearch}`;
|
|
296
|
+
let loaderRequestEv = events.get(eventKey);
|
|
297
|
+
if (!loaderRequestEv) {
|
|
298
|
+
url.pathname = pathname;
|
|
299
|
+
url.search = filteredSearch;
|
|
300
|
+
const request = new Request(url, rootRequestEv.request);
|
|
301
|
+
loaderRequestEv = Object.create(rootRequestEv, {
|
|
302
|
+
originalUrl: {
|
|
303
|
+
value: new URL(url),
|
|
304
|
+
enumerable: true
|
|
305
|
+
},
|
|
306
|
+
params: {
|
|
307
|
+
value: {},
|
|
308
|
+
enumerable: true
|
|
309
|
+
},
|
|
310
|
+
pathname: {
|
|
311
|
+
get: () => url.pathname,
|
|
312
|
+
enumerable: true
|
|
313
|
+
},
|
|
314
|
+
query: {
|
|
315
|
+
get: () => url.searchParams,
|
|
316
|
+
enumerable: true
|
|
317
|
+
},
|
|
318
|
+
request: {
|
|
319
|
+
value: request,
|
|
320
|
+
enumerable: true
|
|
321
|
+
},
|
|
322
|
+
url: {
|
|
323
|
+
value: url,
|
|
324
|
+
enumerable: true
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
events.set(eventKey, loaderRequestEv);
|
|
328
|
+
}
|
|
329
|
+
return loaderRequestEv;
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* View of a request event whose response controls (status/headers/redirect/error/fail/cacheControl)
|
|
333
|
+
* are captured locally, so a background (`blockSSR: false`) loader's status/redirect/error surfaces
|
|
334
|
+
* on its own signal instead of mutating the page response. The originals also call `check()`, which
|
|
335
|
+
* throws once SSR streaming has started — a background loader must fail on its signal, not crash.
|
|
336
|
+
*/ var detachResponseFromEvent = (requestEv) => {
|
|
337
|
+
let status = 200;
|
|
338
|
+
const headers = new Headers();
|
|
339
|
+
return Object.create(requestEv, {
|
|
340
|
+
headers: {
|
|
341
|
+
value: headers,
|
|
342
|
+
enumerable: true
|
|
343
|
+
},
|
|
344
|
+
status: {
|
|
345
|
+
value: (statusCode) => {
|
|
346
|
+
if (typeof statusCode === "number") status = statusCode;
|
|
347
|
+
return status;
|
|
348
|
+
},
|
|
349
|
+
enumerable: true
|
|
350
|
+
},
|
|
351
|
+
error: {
|
|
352
|
+
value: (statusCode, message) => {
|
|
353
|
+
status = statusCode;
|
|
354
|
+
return new ServerError(statusCode, message);
|
|
355
|
+
},
|
|
356
|
+
enumerable: true
|
|
357
|
+
},
|
|
358
|
+
redirect: {
|
|
359
|
+
value: (statusCode, url) => {
|
|
360
|
+
status = statusCode;
|
|
361
|
+
if (url) headers.set("Location", url);
|
|
362
|
+
return new RedirectMessage();
|
|
363
|
+
},
|
|
364
|
+
enumerable: true
|
|
365
|
+
},
|
|
366
|
+
fail: {
|
|
367
|
+
value: (statusCode, data) => {
|
|
368
|
+
status = statusCode;
|
|
369
|
+
return {
|
|
370
|
+
failed: true,
|
|
371
|
+
...data
|
|
372
|
+
};
|
|
373
|
+
},
|
|
374
|
+
enumerable: true
|
|
375
|
+
},
|
|
376
|
+
cacheControl: {
|
|
377
|
+
value: () => {},
|
|
378
|
+
enumerable: true
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
async function runValidators(requestEv, validators, data) {
|
|
383
|
+
let lastResult = {
|
|
384
|
+
success: true,
|
|
385
|
+
data
|
|
386
|
+
};
|
|
387
|
+
if (validators) for (let i = 0; i < validators.length; i++) {
|
|
388
|
+
lastResult = await validators[i].validate(requestEv, data);
|
|
389
|
+
if (!lastResult.success) return lastResult;
|
|
390
|
+
data = lastResult.data;
|
|
391
|
+
}
|
|
392
|
+
return lastResult;
|
|
393
|
+
}
|
|
394
|
+
function verifySerializable(data, qrl) {
|
|
395
|
+
try {
|
|
396
|
+
_verifySerializable(data, void 0);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (error instanceof Error && qrl.dev) error.loc = qrl.dev;
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/** Run a loader and return its raw value. Errors/redirects propagate as exceptions. */ var getRouteLoaderData = async (loaderQrl, validators, requestEv) => {
|
|
403
|
+
const loaderRequestEv = requestEv;
|
|
404
|
+
const result = await runValidators(requestEv, validators, void 0);
|
|
405
|
+
if (!result.success) return loaderRequestEv.fail(result.status ?? 500, result.error);
|
|
406
|
+
const resolved = await loaderQrl.call(loaderRequestEv, loaderRequestEv);
|
|
407
|
+
const value = typeof resolved === "function" ? resolved() : resolved;
|
|
408
|
+
throwIfControlFlowSignal(value);
|
|
409
|
+
if (isDev) verifySerializable(value, loaderQrl);
|
|
410
|
+
return value;
|
|
411
|
+
};
|
|
412
|
+
var loadRouteLoaderByQrl = (loaderId, loaderQrl, validators, requestEv) => {
|
|
413
|
+
const values = getRouteLoaderValues(requestEv);
|
|
414
|
+
if (loaderId in values) return Promise.resolve(values[loaderId]);
|
|
415
|
+
const promises = getRouteLoaderPromises(requestEv);
|
|
416
|
+
let promise = promises[loaderId];
|
|
417
|
+
if (!promise) {
|
|
418
|
+
promise = getRouteLoaderData(loaderQrl, validators, requestEv).then((value) => {
|
|
419
|
+
values[loaderId] = value;
|
|
420
|
+
return value;
|
|
421
|
+
}, (err) => {
|
|
422
|
+
delete promises[loaderId];
|
|
423
|
+
throw err;
|
|
424
|
+
});
|
|
425
|
+
promises[loaderId] = promise;
|
|
426
|
+
}
|
|
427
|
+
return promise;
|
|
428
|
+
};
|
|
429
|
+
var ServerRouteLoaderCapture = class {
|
|
430
|
+
hash;
|
|
431
|
+
qrl;
|
|
432
|
+
validators;
|
|
433
|
+
blockSSR;
|
|
434
|
+
constructor(hash, qrl, validators, blockSSR) {
|
|
435
|
+
this.hash = hash;
|
|
436
|
+
this.qrl = qrl;
|
|
437
|
+
this.validators = validators;
|
|
438
|
+
this.blockSSR = blockSSR;
|
|
439
|
+
}
|
|
440
|
+
load() {
|
|
441
|
+
const requestEv = getRequestEvent();
|
|
442
|
+
if (!requestEv) throw new Error("Unable to determine the current RequestEvent.");
|
|
443
|
+
const values = getRouteLoaderValues(requestEv);
|
|
444
|
+
if (this.hash in values) return values[this.hash];
|
|
445
|
+
const ev = this.blockSSR ? requestEv : detachResponseFromEvent(requestEv);
|
|
446
|
+
return loadRouteLoaderByQrl(this.hash, this.qrl, this.validators, ev);
|
|
447
|
+
}
|
|
448
|
+
[SerializerSymbol]() {
|
|
449
|
+
return this.hash;
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
var createRouteLoaderSignal = (loader, routeLoaderCtx, state) => {
|
|
453
|
+
const id = loader.__id;
|
|
454
|
+
const stateValues = state;
|
|
455
|
+
const resumeValueKey = getRouteLoaderValueStateKey(id);
|
|
456
|
+
const capture = isServer ? new ServerRouteLoaderCapture(id, loader.__qrl, loader.__validators, loader.__blockSSR) : id;
|
|
457
|
+
const searchFilter = loader.__search;
|
|
458
|
+
const signal = createComputedQrl(/*#__PURE__*/ inlinedQrl(async (ctx) => {
|
|
459
|
+
const capture = _captures[0], id = _captures[1], lastFetch = _captures[2], resumeValueKey = _captures[3], routeLoaderCtx = _captures[4], searchFilter = _captures[5], stateValues = _captures[6];
|
|
460
|
+
const hasInjectedValue = !!ctx.info && typeof ctx.info === "object" && "__v" in ctx.info;
|
|
461
|
+
const trackedRoutePath = ctx.track(routeLoaderCtx.loaderPaths, id);
|
|
462
|
+
const trackedPagePathname = ctx.track(routeLoaderCtx, "pagePathname");
|
|
463
|
+
const trackedPageSearch = ctx.track(routeLoaderCtx, "pageSearch");
|
|
464
|
+
if (hasInjectedValue) {
|
|
465
|
+
const value = ctx.info.__v;
|
|
466
|
+
if (!isServer && resumeValueKey in stateValues) stateValues[resumeValueKey] = value;
|
|
467
|
+
lastFetch.raw = void 0;
|
|
468
|
+
return value;
|
|
469
|
+
}
|
|
470
|
+
if (isServer) return capture.load();
|
|
471
|
+
const pagePathname = trackedPagePathname || location.pathname;
|
|
472
|
+
const pageSearch = trackedPageSearch || location.search;
|
|
473
|
+
const pageUrl = new URL(pagePathname + pageSearch, location.href);
|
|
474
|
+
const mHash = routeLoaderCtx.manifestHash || "dev";
|
|
475
|
+
const basePath = qwikRouterConfig.basePathname ?? "/";
|
|
476
|
+
const needsResumeFetch = stateValues[resumeValueKey] === _UNINITIALIZED;
|
|
477
|
+
const fetchRoutePath = trackedRoutePath || (needsResumeFetch ? pageUrl.pathname : void 0);
|
|
478
|
+
if (!fetchRoutePath) return ctx.previous;
|
|
479
|
+
let fetchUrl = pageUrl;
|
|
480
|
+
if (searchFilter) {
|
|
481
|
+
fetchUrl = new URL(pageUrl.href);
|
|
482
|
+
fetchUrl.search = filterSearchParams(pageUrl.searchParams, searchFilter);
|
|
483
|
+
}
|
|
484
|
+
const result = await fetchRouteLoaderData(id, fetchRoutePath, mHash, {
|
|
485
|
+
pageUrl: fetchUrl,
|
|
486
|
+
basePath,
|
|
487
|
+
ignoreCache: ctx.info === true,
|
|
488
|
+
signal: ctx.abortSignal
|
|
489
|
+
});
|
|
490
|
+
if (!result) throw new Error(`Loader ${id} returned empty response`);
|
|
491
|
+
let response;
|
|
492
|
+
if (result.raw === void 0) response = result;
|
|
493
|
+
else {
|
|
494
|
+
if (result.raw === lastFetch.raw && ctx.previous !== void 0) return ctx.previous;
|
|
495
|
+
response = await _deserialize(result.raw);
|
|
496
|
+
if (!response) throw new Error(`Loader ${id} returned empty response`);
|
|
497
|
+
}
|
|
498
|
+
if (response.r) {
|
|
499
|
+
const goto = routeLoaderCtx.goto;
|
|
500
|
+
if (goto) goto(response.r, { replaceState: true });
|
|
501
|
+
else location.href = response.r;
|
|
502
|
+
return ctx.previous;
|
|
503
|
+
}
|
|
504
|
+
if (response.e) throw response.e;
|
|
505
|
+
lastFetch.raw = result.raw;
|
|
506
|
+
if (needsResumeFetch) stateValues[resumeValueKey] = response.d;
|
|
507
|
+
return response.d;
|
|
508
|
+
}, "createRouteLoaderSignal_signal_createComputed_4s4ZDCYhDOA", [
|
|
509
|
+
capture,
|
|
510
|
+
id,
|
|
511
|
+
{},
|
|
512
|
+
resumeValueKey,
|
|
513
|
+
routeLoaderCtx,
|
|
514
|
+
searchFilter,
|
|
515
|
+
stateValues
|
|
516
|
+
]), { serializationStrategy: loader.__serializationStrategy });
|
|
517
|
+
_markSignalAsExternallyOwned(signal);
|
|
518
|
+
return signal;
|
|
519
|
+
};
|
|
520
|
+
var ensureRouteLoaderSignal = (loader, state, routeLoaderCtx) => {
|
|
521
|
+
if (loader.__cacheControl === "immutable") immutableLoaderIds.add(loader.__id);
|
|
522
|
+
if (isServer && loader.__serializationStrategy === "never") state[getRouteLoaderValueStateKey(loader.__id)] = _UNINITIALIZED;
|
|
523
|
+
return state[loader.__id] ||= createRouteLoaderSignal(loader, routeLoaderCtx, state);
|
|
524
|
+
};
|
|
525
|
+
var ensureRouteLoaderSignals = (mods, state, routeLoaderCtx) => {
|
|
526
|
+
const loaders = getModuleRouteLoaders(mods);
|
|
527
|
+
for (let i = 0; i < loaders.length; i++) {
|
|
528
|
+
const loader = loaders[i];
|
|
529
|
+
if (isDev && !isServer) {
|
|
530
|
+
const devCtx = routeLoaderCtx;
|
|
531
|
+
if (devCtx.pagePathname && !devCtx.devRouteLoaderPaths?.[loader.__id]) devCtx.loaderPaths[loader.__id] = devCtx.pagePathname;
|
|
532
|
+
}
|
|
533
|
+
ensureRouteLoaderSignal(loader, state, routeLoaderCtx);
|
|
534
|
+
}
|
|
535
|
+
return loaders;
|
|
536
|
+
};
|
|
537
|
+
var loadRouteLoader = (loader, requestEv) => {
|
|
538
|
+
const loaderEv = getLoaderRequestEvent(loader, requestEv);
|
|
539
|
+
return loadRouteLoaderByQrl(loader.__id, loader.__qrl, loader.__validators, loader.__blockSSR ? loaderEv : detachResponseFromEvent(loaderEv));
|
|
540
|
+
};
|
|
541
|
+
/** Run a loader and wrap the result in a LoaderResponse envelope. Catches redirects/errors. */ var getRouteLoaderResponse = async (loaderQrl, validators, requestEv) => {
|
|
542
|
+
try {
|
|
543
|
+
return { d: await getRouteLoaderData(loaderQrl, validators, requestEv) };
|
|
544
|
+
} catch (err) {
|
|
545
|
+
if (err instanceof RedirectMessage) {
|
|
546
|
+
const location1 = requestEv.headers.get("Location") || "/";
|
|
547
|
+
requestEv.headers.delete("Location");
|
|
548
|
+
return { r: location1 };
|
|
549
|
+
}
|
|
550
|
+
if (err instanceof ServerError) return { e: err };
|
|
551
|
+
throw err;
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
/** @internal */ var routeLoaderQrl = (loaderQrl, ...rest) => {
|
|
555
|
+
const { id, validators, serializationStrategy, cacheControl, eTag, cacheKey, search, blockSSR } = getLoaderOptions(rest);
|
|
556
|
+
function loader() {
|
|
557
|
+
const state = _resolveContextWithoutSequentialScope(RouteStateContext);
|
|
558
|
+
let signal = state[loader.__id];
|
|
559
|
+
if (!signal) signal = ensureRouteLoaderSignal(loader, state, _resolveContextWithoutSequentialScope(RouteLoaderCtxContext));
|
|
560
|
+
signal.promise();
|
|
561
|
+
return signal;
|
|
562
|
+
}
|
|
563
|
+
loader.__brand = "server_loader";
|
|
564
|
+
loader.__qrl = loaderQrl;
|
|
565
|
+
loader.__validators = validators;
|
|
566
|
+
loader.__id = id ?? loaderQrl.getHash();
|
|
567
|
+
loader.__serializationStrategy = serializationStrategy;
|
|
568
|
+
loader.__cacheControl = cacheControl;
|
|
569
|
+
loader.__eTag = eTag;
|
|
570
|
+
loader.__cacheKey = cacheKey;
|
|
571
|
+
loader.__search = search;
|
|
572
|
+
loader.__blockSSR = blockSSR;
|
|
573
|
+
Object.freeze(loader);
|
|
574
|
+
return loader;
|
|
575
|
+
};
|
|
576
|
+
/**
|
|
577
|
+
* Define a route loader that fetches data before the route renders.
|
|
578
|
+
*
|
|
579
|
+
* Route loaders run on the server during SSR and return data as a `ComputedSignal`. On the client,
|
|
580
|
+
* loaders automatically re-fetch when the route changes (SPA navigation). Each loader gets its own
|
|
581
|
+
* JSON endpoint (`q-loader-{id}.{hash}.json`), so only the loaders present on the target route are
|
|
582
|
+
* fetched.
|
|
583
|
+
*
|
|
584
|
+
* **Important:** Route loader data uses Qwik's custom serialization format, not standard JSON. This
|
|
585
|
+
* means the data supports features like circular references, Dates, and other non-JSON types, but
|
|
586
|
+
* it cannot be consumed by external clients expecting plain JSON.
|
|
587
|
+
*
|
|
588
|
+
* ## Options
|
|
589
|
+
*
|
|
590
|
+
* - `search: string[]`: Allowlist of URL search params the loader depends on. Only listed params are
|
|
591
|
+
* sent in the request and changes to other params are ignored. During SSR and loader JSON
|
|
592
|
+
* requests, the loader's request event is filtered to those params too. `search: []` means no
|
|
593
|
+
* search params are sent and only route path changes trigger a re-fetch.
|
|
594
|
+
* - `eTag`: Enable ETag-based caching. Can be `true` (auto-hash), a string, or a function.
|
|
595
|
+
* - `cacheControl`: Cache-Control for loader JSON responses; the browser HTTP cache controls
|
|
596
|
+
* client-side freshness. `'immutable'` additionally lets SSG write the loader file.
|
|
597
|
+
*
|
|
598
|
+
* The `strictLoaders` Vite plugin option applies `search: []` globally for all loaders that don't
|
|
599
|
+
* specify an explicit `search` option.
|
|
600
|
+
*
|
|
601
|
+
* @public
|
|
602
|
+
*/ var routeLoader$ = /*#__PURE__*/ implicit$FirstArg(routeLoaderQrl);
|
|
603
|
+
var deepFreeze = (obj) => {
|
|
604
|
+
if (obj == null) return obj;
|
|
605
|
+
Object.getOwnPropertyNames(obj).forEach((prop) => {
|
|
606
|
+
const value = obj[prop];
|
|
607
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) try {
|
|
608
|
+
deepFreeze(value);
|
|
609
|
+
} catch {
|
|
610
|
+
return obj;
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
return Object.freeze(obj);
|
|
614
|
+
};
|
|
615
|
+
/** LoadRoute() runs in both client and server. */ var MODULE_CACHE = /*#__PURE__*/ new WeakMap();
|
|
616
|
+
/** Built-in fallback error component loader */ var httpErrorLoader = () => import("./http-error.qwik.mjs");
|
|
617
|
+
/**
|
|
618
|
+
* Walk trie keys from root, gathering `_L` layouts along the way. Returns the final node and
|
|
619
|
+
* collected layouts, or undefined if any key is missing.
|
|
620
|
+
*/ function walkTrieKeys(root, keys) {
|
|
621
|
+
let node = root;
|
|
622
|
+
const layouts = [];
|
|
623
|
+
if (node._L) layouts.push(node._L);
|
|
624
|
+
for (let i = 0; i < keys.length; i++) {
|
|
625
|
+
const key = keys[i];
|
|
626
|
+
let next = node[key];
|
|
627
|
+
if (!next && node._M) for (let j = 0; j < node._M.length; j++) {
|
|
628
|
+
const group = node._M[j];
|
|
629
|
+
next = group[key];
|
|
630
|
+
if (next) {
|
|
631
|
+
if (group._L) layouts.push(group._L);
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (!next) return;
|
|
636
|
+
node = next;
|
|
637
|
+
if (node._L) layouts.push(node._L);
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
node,
|
|
641
|
+
layouts
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* The full chain to render for a boundary: a bare loader in its snapshot layouts, an override
|
|
646
|
+
* as-is.
|
|
647
|
+
*/ var boundaryChain = (ref) => ref.v ? Array.isArray(ref.v) ? ref.v : [...ref.layouts, ref.v] : void 0;
|
|
648
|
+
/**
|
|
649
|
+
* Collect layouts, the nearest error (`_E`) and not-found (`_4`) boundaries, and the menu from a
|
|
650
|
+
* node and the group ancestors entered to reach it.
|
|
651
|
+
*/ function collectNodeMeta(node, groups, layouts, errorLoaderRef, notFoundLoaderRef, menuLoaderRef, loaderHashes, loaderPathsByHash, matchedPathname = "/") {
|
|
652
|
+
for (let j = 0; j < groups.length; j++) {
|
|
653
|
+
const g = groups[j];
|
|
654
|
+
if (g._L) layouts.push(g._L);
|
|
655
|
+
if (g._R && loaderHashes) {
|
|
656
|
+
loaderHashes.push(...g._R);
|
|
657
|
+
if (loaderPathsByHash) for (let i = 0; i < g._R.length; i++) {
|
|
658
|
+
const hash = g._R[i];
|
|
659
|
+
loaderPathsByHash[hash] = matchedPathname;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (g._E) {
|
|
663
|
+
errorLoaderRef.v = g._E;
|
|
664
|
+
errorLoaderRef.layouts = [...layouts];
|
|
665
|
+
}
|
|
666
|
+
if (g._4) {
|
|
667
|
+
notFoundLoaderRef.v = g._4;
|
|
668
|
+
notFoundLoaderRef.layouts = [...layouts];
|
|
669
|
+
}
|
|
670
|
+
if (g._N) menuLoaderRef.v = g._N;
|
|
671
|
+
}
|
|
672
|
+
if (node._L) layouts.push(node._L);
|
|
673
|
+
if (node._R && loaderHashes) {
|
|
674
|
+
loaderHashes.push(...node._R);
|
|
675
|
+
if (loaderPathsByHash) for (let i = 0; i < node._R.length; i++) {
|
|
676
|
+
const hash = node._R[i];
|
|
677
|
+
loaderPathsByHash[hash] = matchedPathname;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (node._E) {
|
|
681
|
+
errorLoaderRef.v = node._E;
|
|
682
|
+
errorLoaderRef.layouts = [...layouts];
|
|
683
|
+
}
|
|
684
|
+
if (node._4) {
|
|
685
|
+
notFoundLoaderRef.v = node._4;
|
|
686
|
+
notFoundLoaderRef.layouts = [...layouts];
|
|
687
|
+
}
|
|
688
|
+
if (node._N) menuLoaderRef.v = node._N;
|
|
689
|
+
}
|
|
690
|
+
/** Try to match a segment against a node's wildcard children (_W, _A). */ function tryWildcardMatch(node, part, partLower, parts, partIndex) {
|
|
691
|
+
let next = node._W;
|
|
692
|
+
if (next) {
|
|
693
|
+
const prefix = next._0;
|
|
694
|
+
const suffix = next._9;
|
|
695
|
+
if (prefix || suffix) {
|
|
696
|
+
const pre = prefix || "";
|
|
697
|
+
const suf = suffix || "";
|
|
698
|
+
if (partLower.length > pre.length + suf.length && (!pre || partLower.startsWith(pre.toLowerCase())) && (!suf || partLower.endsWith(suf.toLowerCase()))) {
|
|
699
|
+
const paramName = next._P;
|
|
700
|
+
const value = part.slice(pre.length, suf ? part.length - suf.length : void 0);
|
|
701
|
+
return {
|
|
702
|
+
next,
|
|
703
|
+
routePart: `${pre}[${paramName}]${suf}`,
|
|
704
|
+
done: false,
|
|
705
|
+
kind: 1,
|
|
706
|
+
paramName,
|
|
707
|
+
paramValue: value
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
const paramName = next._P;
|
|
712
|
+
return {
|
|
713
|
+
next,
|
|
714
|
+
routePart: `[${paramName}]`,
|
|
715
|
+
done: false,
|
|
716
|
+
kind: 1,
|
|
717
|
+
paramName,
|
|
718
|
+
paramValue: part
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
next = node._A;
|
|
723
|
+
if (next) {
|
|
724
|
+
const paramName = next._P;
|
|
725
|
+
const restValue = parts.slice(partIndex).join("/");
|
|
726
|
+
return {
|
|
727
|
+
next,
|
|
728
|
+
routePart: `[...${paramName}]`,
|
|
729
|
+
done: true,
|
|
730
|
+
kind: 2,
|
|
731
|
+
paramName,
|
|
732
|
+
paramValue: restValue
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Try to find a child matching `partLower` in `node`, including inside group children (_M). Returns
|
|
738
|
+
* the matched child node, the groups entered to reach it, what to push to routeParts, and which
|
|
739
|
+
* kind of edge matched. Returns undefined if no match.
|
|
740
|
+
*
|
|
741
|
+
* Priority: exact match → _M groups (recursively) → _W wildcard → _A rest wildcard. This ensures
|
|
742
|
+
* exact matches inside nested groups take precedence over wildcards at parent level.
|
|
743
|
+
*/ function findChild(node, part, partLower, parts, partIndex) {
|
|
744
|
+
const exact = node[partLower];
|
|
745
|
+
if (exact) return {
|
|
746
|
+
next: exact,
|
|
747
|
+
groups: [],
|
|
748
|
+
routePart: part,
|
|
749
|
+
done: false,
|
|
750
|
+
kind: 0
|
|
751
|
+
};
|
|
752
|
+
if (node._M) for (let j = 0; j < node._M.length; j++) {
|
|
753
|
+
const group = node._M[j];
|
|
754
|
+
const groupResult = findChild(group, part, partLower, parts, partIndex);
|
|
755
|
+
if (groupResult) {
|
|
756
|
+
groupResult.groups.unshift(group);
|
|
757
|
+
return groupResult;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const wildcard = tryWildcardMatch(node, part, partLower, parts, partIndex);
|
|
761
|
+
if (wildcard) return {
|
|
762
|
+
...wildcard,
|
|
763
|
+
groups: []
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Descend through a node and its pathless `_M` groups (depth-first, in `_M` order), returning the
|
|
768
|
+
* first node for which `hit` yields a value, plus the chain of groups entered to reach it. The
|
|
769
|
+
* shared traversal behind the index / rest / boundary lookups inside groups.
|
|
770
|
+
*/ function descendGroups(node, hit) {
|
|
771
|
+
const value = hit(node);
|
|
772
|
+
if (value !== void 0) return {
|
|
773
|
+
value,
|
|
774
|
+
node,
|
|
775
|
+
groups: []
|
|
776
|
+
};
|
|
777
|
+
if (node._M) for (let j = 0; j < node._M.length; j++) {
|
|
778
|
+
const group = node._M[j];
|
|
779
|
+
const result = descendGroups(group, hit);
|
|
780
|
+
if (result) {
|
|
781
|
+
result.groups.unshift(group);
|
|
782
|
+
return result;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Find a node with `_I`/`_G` at the current level, including inside `_M` groups. Returns it and the
|
|
788
|
+
* groups entered to reach it — excluding the target itself, whose `_L` the caller applies (so it
|
|
789
|
+
* isn't double-counted).
|
|
790
|
+
*/ function findIndexNode(node) {
|
|
791
|
+
const r = descendGroups(node, (n) => n._I || n._G != null ? n : void 0);
|
|
792
|
+
return r ? {
|
|
793
|
+
target: r.node,
|
|
794
|
+
groups: r.groups.filter((g) => g !== r.node)
|
|
795
|
+
} : void 0;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Given a matched node that has `_I` or `_G`, resolve the final loaders array.
|
|
799
|
+
*
|
|
800
|
+
* - `_G`: re-walk from root to gather target's layouts and `_I`
|
|
801
|
+
* - `_I` array: use as-is (override, e.g. layout stop)
|
|
802
|
+
* - `_I` function: prepend gathered layouts
|
|
803
|
+
*/ function resolveLoaders(root, node, gatheredLayouts) {
|
|
804
|
+
if (node._G != null) {
|
|
805
|
+
const target = walkTrieKeys(root, node._G.split("/").filter((p) => p.length > 0));
|
|
806
|
+
if (!target) return;
|
|
807
|
+
let targetNode = target.node;
|
|
808
|
+
const targetLayouts = target.layouts;
|
|
809
|
+
if (!targetNode._I && targetNode._G == null) {
|
|
810
|
+
const indexResult = findIndexNode(targetNode);
|
|
811
|
+
if (indexResult) {
|
|
812
|
+
for (let j = 0; j < indexResult.groups.length; j++) {
|
|
813
|
+
const g = indexResult.groups[j];
|
|
814
|
+
if (g._L) targetLayouts.push(g._L);
|
|
815
|
+
}
|
|
816
|
+
targetNode = indexResult.target;
|
|
817
|
+
if (targetNode._L) targetLayouts.push(targetNode._L);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return resolveLoaders(root, targetNode, targetLayouts);
|
|
821
|
+
}
|
|
822
|
+
const index = node._I;
|
|
823
|
+
if (!index) return;
|
|
824
|
+
if (Array.isArray(index)) return index;
|
|
825
|
+
return [...gatheredLayouts, index];
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Find a rest wildcard (`_A`) at the current node or inside its `_M` groups, with the groups
|
|
829
|
+
* entered to reach it. Used to save fallback checkpoints during matching.
|
|
830
|
+
*/ function findRestNode(node) {
|
|
831
|
+
const r = descendGroups(node, (n) => n._A);
|
|
832
|
+
return r ? {
|
|
833
|
+
next: r.value,
|
|
834
|
+
groups: r.groups
|
|
835
|
+
} : void 0;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Find a `_4`/`_E` boundary inside a node's pathless `_M` groups (first in `_M` order), wrapped in
|
|
839
|
+
* those groups' layouts.
|
|
840
|
+
*/ function findGroupBoundary(node, layouts, kind) {
|
|
841
|
+
const r = descendGroups(node, (n) => n[kind]);
|
|
842
|
+
if (!r) return;
|
|
843
|
+
const groupLayouts = [...layouts];
|
|
844
|
+
for (const g of r.groups) if (g._L) groupLayouts.push(g._L);
|
|
845
|
+
return {
|
|
846
|
+
v: r.value,
|
|
847
|
+
layouts: groupLayouts
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Match a URL pathname against the route trie.
|
|
852
|
+
*
|
|
853
|
+
* Always returns a result. When no route matches, `notFound` is true and `loaders` contains only
|
|
854
|
+
* the error component (from the nearest `_E` or `_4` ancestor, or the built-in fallback).
|
|
855
|
+
*
|
|
856
|
+
* Group nodes (in `_M` arrays) are pathless — they don't consume a URL segment but contribute their
|
|
857
|
+
* `_L` layout to the chain. The matcher transparently enters groups when searching for a matching
|
|
858
|
+
* child.
|
|
859
|
+
*/ function matchRouteTree(root = {}, pathname) {
|
|
860
|
+
let node = root;
|
|
861
|
+
const params = {};
|
|
862
|
+
const routeParts = [];
|
|
863
|
+
const layouts = [];
|
|
864
|
+
const loaderHashes = [];
|
|
865
|
+
const loaderPathsByHash = {};
|
|
866
|
+
const errorLoaderRef = {
|
|
867
|
+
v: void 0,
|
|
868
|
+
layouts: []
|
|
869
|
+
};
|
|
870
|
+
const notFoundLoaderRef = {
|
|
871
|
+
v: void 0,
|
|
872
|
+
layouts: []
|
|
873
|
+
};
|
|
874
|
+
const menuLoaderRef = { v: void 0 };
|
|
875
|
+
const groupNodes = [];
|
|
876
|
+
collectNodeMeta(root, [], layouts, errorLoaderRef, notFoundLoaderRef, menuLoaderRef, loaderHashes, loaderPathsByHash);
|
|
877
|
+
if (root._M) groupNodes.push({
|
|
878
|
+
node: root,
|
|
879
|
+
depth: layouts.length
|
|
880
|
+
});
|
|
881
|
+
let done = false;
|
|
882
|
+
let matched = true;
|
|
883
|
+
const parts = pathname.split("/").filter((p) => p.length > 0).map(decodeURIComponent);
|
|
884
|
+
let restFallback;
|
|
885
|
+
let i = 0;
|
|
886
|
+
const len = parts.length;
|
|
887
|
+
for (; !done && i < len; i++) {
|
|
888
|
+
const part = parts[i];
|
|
889
|
+
const partLower = part.toLowerCase();
|
|
890
|
+
const found = findChild(node, part, partLower, parts, i);
|
|
891
|
+
if (!found) {
|
|
892
|
+
matched = false;
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
const restInfo = found.kind === 1 ? findRestNode(node) : void 0;
|
|
896
|
+
if (restInfo) restFallback = {
|
|
897
|
+
aNode: restInfo.next,
|
|
898
|
+
groups: restInfo.groups,
|
|
899
|
+
paramName: restInfo.next._P,
|
|
900
|
+
restValue: parts.slice(i).join("/"),
|
|
901
|
+
routeParts: [...routeParts],
|
|
902
|
+
params: { ...params },
|
|
903
|
+
layouts: [...layouts],
|
|
904
|
+
loaderPathsByHash: { ...loaderPathsByHash },
|
|
905
|
+
errorLoader: errorLoaderRef.v,
|
|
906
|
+
errorLayouts: errorLoaderRef.layouts,
|
|
907
|
+
notFoundLoader: notFoundLoaderRef.v,
|
|
908
|
+
notFoundLayouts: notFoundLoaderRef.layouts,
|
|
909
|
+
menuLoader: menuLoaderRef.v
|
|
910
|
+
};
|
|
911
|
+
if (found.paramName) params[found.paramName] = found.paramValue;
|
|
912
|
+
routeParts.push(found.routePart);
|
|
913
|
+
done = found.done;
|
|
914
|
+
node = found.next;
|
|
915
|
+
const matchedPathname = `/${parts.slice(0, found.done ? len : i + 1).join("/")}/`;
|
|
916
|
+
collectNodeMeta(node, found.groups, layouts, errorLoaderRef, notFoundLoaderRef, menuLoaderRef, loaderHashes, loaderPathsByHash, matchedPathname);
|
|
917
|
+
if (node._M) groupNodes.push({
|
|
918
|
+
node,
|
|
919
|
+
depth: layouts.length
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
if (matched && !done && i === len) {
|
|
923
|
+
if (!node._I && node._G == null) {
|
|
924
|
+
const indexResult = findIndexNode(node);
|
|
925
|
+
if (indexResult) {
|
|
926
|
+
collectNodeMeta(indexResult.target, indexResult.groups, layouts, errorLoaderRef, notFoundLoaderRef, menuLoaderRef, loaderHashes, loaderPathsByHash, pathname);
|
|
927
|
+
node = indexResult.target;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (!node._I && node._G == null) {
|
|
931
|
+
const restInfo = findRestNode(node);
|
|
932
|
+
if (restInfo) {
|
|
933
|
+
const next = restInfo.next;
|
|
934
|
+
const paramName = next._P;
|
|
935
|
+
params[paramName] = "";
|
|
936
|
+
routeParts.push(`[...${paramName}]`);
|
|
937
|
+
collectNodeMeta(next, restInfo.groups, layouts, errorLoaderRef, notFoundLoaderRef, menuLoaderRef, loaderHashes, loaderPathsByHash, pathname);
|
|
938
|
+
node = next;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
const loaders = matched && (done || i >= len) ? resolveLoaders(root, node, layouts) : void 0;
|
|
943
|
+
if (!loaders && restFallback) {
|
|
944
|
+
const fb = restFallback;
|
|
945
|
+
const fbParams = {
|
|
946
|
+
...fb.params,
|
|
947
|
+
[fb.paramName]: fb.restValue
|
|
948
|
+
};
|
|
949
|
+
const fbRouteParts = [...fb.routeParts, `[...${fb.paramName}]`];
|
|
950
|
+
const fbLayouts = [...fb.layouts];
|
|
951
|
+
const fbLoaderPathsByHash = { ...fb.loaderPathsByHash };
|
|
952
|
+
const fbErrorRef = {
|
|
953
|
+
v: fb.errorLoader,
|
|
954
|
+
layouts: fb.errorLayouts
|
|
955
|
+
};
|
|
956
|
+
const fbNotFoundRef = {
|
|
957
|
+
v: fb.notFoundLoader,
|
|
958
|
+
layouts: fb.notFoundLayouts
|
|
959
|
+
};
|
|
960
|
+
const fbMenuRef = { v: fb.menuLoader };
|
|
961
|
+
const fbLoaderHashes = [];
|
|
962
|
+
collectNodeMeta(fb.aNode, fb.groups, fbLayouts, fbErrorRef, fbNotFoundRef, fbMenuRef, fbLoaderHashes, fbLoaderPathsByHash, pathname);
|
|
963
|
+
const fbLoaders = resolveLoaders(root, fb.aNode, fbLayouts);
|
|
964
|
+
if (fbLoaders) return {
|
|
965
|
+
loaders: fbLoaders,
|
|
966
|
+
params: fbParams,
|
|
967
|
+
routeParts: fbRouteParts,
|
|
968
|
+
notFound: false,
|
|
969
|
+
routeBundleNames: fb.aNode._B,
|
|
970
|
+
loaderHashes: fbLoaderHashes.length > 0 ? fbLoaderHashes : void 0,
|
|
971
|
+
loaderPathsByHash: Object.keys(fbLoaderPathsByHash).length > 0 ? fbLoaderPathsByHash : void 0,
|
|
972
|
+
menuLoader: fbMenuRef.v,
|
|
973
|
+
errorLoader: boundaryChain(fbErrorRef)
|
|
974
|
+
};
|
|
975
|
+
errorLoaderRef.v = fbErrorRef.v;
|
|
976
|
+
errorLoaderRef.layouts = fbErrorRef.layouts;
|
|
977
|
+
notFoundLoaderRef.v = fbNotFoundRef.v;
|
|
978
|
+
notFoundLoaderRef.layouts = fbNotFoundRef.layouts;
|
|
979
|
+
menuLoaderRef.v = fbMenuRef.v;
|
|
980
|
+
}
|
|
981
|
+
if (!loaders) {
|
|
982
|
+
const recover = (kind) => {
|
|
983
|
+
for (let k = groupNodes.length - 1; k >= 0; k--) {
|
|
984
|
+
const found = findGroupBoundary(groupNodes[k].node, layouts.slice(0, groupNodes[k].depth), kind);
|
|
985
|
+
if (found) return found;
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
if (!notFoundLoaderRef.v) {
|
|
989
|
+
const g = recover("_4");
|
|
990
|
+
if (g) {
|
|
991
|
+
notFoundLoaderRef.v = g.v;
|
|
992
|
+
notFoundLoaderRef.layouts = g.layouts;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
if (!errorLoaderRef.v) {
|
|
996
|
+
const g = recover("_E");
|
|
997
|
+
if (g) {
|
|
998
|
+
errorLoaderRef.v = g.v;
|
|
999
|
+
errorLoaderRef.layouts = g.layouts;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
const ref = notFoundLoaderRef.v ? notFoundLoaderRef : errorLoaderRef.v ? errorLoaderRef : null;
|
|
1003
|
+
const boundary = ref?.v ?? httpErrorLoader;
|
|
1004
|
+
const boundaryLayouts = ref ? ref.layouts : layouts;
|
|
1005
|
+
return {
|
|
1006
|
+
loaders: Array.isArray(boundary) ? boundary : [...boundaryLayouts, boundary],
|
|
1007
|
+
params,
|
|
1008
|
+
routeParts,
|
|
1009
|
+
notFound: true,
|
|
1010
|
+
routeBundleNames: void 0,
|
|
1011
|
+
loaderHashes: void 0,
|
|
1012
|
+
loaderPathsByHash: void 0,
|
|
1013
|
+
menuLoader: menuLoaderRef.v,
|
|
1014
|
+
errorLoader: boundaryChain(errorLoaderRef)
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
if (node._R) {
|
|
1018
|
+
loaderHashes.push(...node._R);
|
|
1019
|
+
const matchedPathname = ensureSlash(pathname);
|
|
1020
|
+
for (let i = 0; i < node._R.length; i++) {
|
|
1021
|
+
const hash = node._R[i];
|
|
1022
|
+
loaderPathsByHash[hash] = matchedPathname;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
loaders,
|
|
1027
|
+
params,
|
|
1028
|
+
routeParts,
|
|
1029
|
+
notFound: false,
|
|
1030
|
+
routeBundleNames: node._B,
|
|
1031
|
+
loaderHashes: loaderHashes.length > 0 ? loaderHashes : void 0,
|
|
1032
|
+
loaderPathsByHash: Object.keys(loaderPathsByHash).length > 0 ? loaderPathsByHash : void 0,
|
|
1033
|
+
menuLoader: menuLoaderRef.v,
|
|
1034
|
+
errorLoader: boundaryChain(errorLoaderRef)
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
var loadModule = (moduleLoader, pendingLoads, moduleSetter, cacheModules) => {
|
|
1038
|
+
if (typeof moduleLoader === "function") {
|
|
1039
|
+
const loadedModule = MODULE_CACHE.get(moduleLoader);
|
|
1040
|
+
if (loadedModule) moduleSetter(loadedModule);
|
|
1041
|
+
else {
|
|
1042
|
+
const moduleOrPromise = moduleLoader();
|
|
1043
|
+
if (typeof moduleOrPromise.then === "function") pendingLoads.push(moduleOrPromise.then((loadedModule) => {
|
|
1044
|
+
if (cacheModules !== false) MODULE_CACHE.set(moduleLoader, loadedModule);
|
|
1045
|
+
moduleSetter(loadedModule);
|
|
1046
|
+
}));
|
|
1047
|
+
else if (moduleOrPromise) moduleSetter(moduleOrPromise);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
var loadRoute = async (routes, cacheModules, pathname) => {
|
|
1052
|
+
const { loaders, params, routeParts, notFound, routeBundleNames, loaderHashes, loaderPathsByHash, menuLoader, errorLoader } = matchRouteTree(routes, pathname);
|
|
1053
|
+
const routeName = "/" + routeParts.join("/");
|
|
1054
|
+
const modules = new Array(loaders.length);
|
|
1055
|
+
const pendingLoads = [];
|
|
1056
|
+
for (let i = 0; i < loaders.length; i++) {
|
|
1057
|
+
const moduleLoader = loaders[i];
|
|
1058
|
+
loadModule(moduleLoader, pendingLoads, (routeModule) => modules[i] = routeModule, cacheModules);
|
|
1059
|
+
}
|
|
1060
|
+
let menu = void 0;
|
|
1061
|
+
loadModule(menuLoader, pendingLoads, (menuModule) => menu = menuModule?.default, cacheModules);
|
|
1062
|
+
if (pendingLoads.length > 0) await Promise.all(pendingLoads);
|
|
1063
|
+
return {
|
|
1064
|
+
$routeName$: routeName,
|
|
1065
|
+
$params$: params,
|
|
1066
|
+
$mods$: modules,
|
|
1067
|
+
$menu$: deepFreeze(menu),
|
|
1068
|
+
$routeBundleNames$: routeBundleNames,
|
|
1069
|
+
$notFound$: notFound,
|
|
1070
|
+
$errorLoader$: errorLoader,
|
|
1071
|
+
$loaders$: loaderHashes,
|
|
1072
|
+
$loaderPaths$: loaderPathsByHash
|
|
1073
|
+
};
|
|
1074
|
+
};
|
|
1075
|
+
/** Gets an absolute url path string (url.pathname + url.search + url.hash) */ var toPath = (url) => url.pathname + url.search + url.hash;
|
|
1076
|
+
/** Create a URL from a string and baseUrl */ var toUrl = (url, baseUrl) => new URL(url, baseUrl.href);
|
|
1077
|
+
/** Checks only if the origins are the same. */ var isSameOrigin = (a, b) => a.origin === b.origin;
|
|
1078
|
+
/** Checks only if the pathnames are the same for the URLs (doesn't include search and hash) */ var isSamePathname = ({ pathname: a }, { pathname: b }) => {
|
|
1079
|
+
const lDiff = Math.abs(a.length - b.length);
|
|
1080
|
+
return lDiff === 0 ? a === b : lDiff === 1 && ensureSlash(a) === ensureSlash(b);
|
|
1081
|
+
};
|
|
1082
|
+
/** Checks only if the search query strings are the same for the URLs */ var isSameSearchQuery = (a, b) => a.search === b.search;
|
|
1083
|
+
/** Checks only if the pathname + search are the same for the URLs. */ var isSamePath = (a, b) => isSameSearchQuery(a, b) && isSamePathname(a, b);
|
|
1084
|
+
var getClientNavPath = (props, baseUrl) => {
|
|
1085
|
+
const href = props.href;
|
|
1086
|
+
if (typeof href === "string" && typeof props.target !== "string" && !props.reload) try {
|
|
1087
|
+
const linkUrl = toUrl(href.trim(), baseUrl.url);
|
|
1088
|
+
if (isSameOrigin(linkUrl, toUrl("", baseUrl.url))) return toPath(linkUrl);
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
console.error(e);
|
|
1091
|
+
}
|
|
1092
|
+
else if (props.reload) return toPath(toUrl("", baseUrl.url));
|
|
1093
|
+
return null;
|
|
1094
|
+
};
|
|
1095
|
+
var shouldPreload = (clientNavPath, currentLoc) => {
|
|
1096
|
+
if (clientNavPath) return !isSamePathname(toUrl(clientNavPath, currentLoc.url), toUrl("", currentLoc.url));
|
|
1097
|
+
return false;
|
|
1098
|
+
};
|
|
1099
|
+
var isPromise = (value) => {
|
|
1100
|
+
return value && typeof value.then === "function";
|
|
1101
|
+
};
|
|
1102
|
+
var mergeArray = (existingArr, newArr) => {
|
|
1103
|
+
if (Array.isArray(newArr)) for (const newItem of newArr) {
|
|
1104
|
+
if (typeof newItem.key === "string") {
|
|
1105
|
+
const existingIndex = existingArr.findIndex((i) => i.key === newItem.key);
|
|
1106
|
+
if (existingIndex > -1) {
|
|
1107
|
+
existingArr[existingIndex] = newItem;
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
existingArr.push(newItem);
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
var resolveDocumentHead = (resolvedHead, updatedHead) => {
|
|
1115
|
+
if (typeof updatedHead.title === "string") resolvedHead.title = updatedHead.title;
|
|
1116
|
+
mergeArray(resolvedHead.meta, updatedHead.meta);
|
|
1117
|
+
mergeArray(resolvedHead.links, updatedHead.links);
|
|
1118
|
+
mergeArray(resolvedHead.styles, updatedHead.styles);
|
|
1119
|
+
mergeArray(resolvedHead.scripts, updatedHead.scripts);
|
|
1120
|
+
Object.assign(resolvedHead.frontmatter, updatedHead.frontmatter);
|
|
1121
|
+
};
|
|
1122
|
+
var createDocumentHead = (defaults, manifestHash) => ({
|
|
1123
|
+
title: defaults?.title || "",
|
|
1124
|
+
meta: [...defaults?.meta || []],
|
|
1125
|
+
links: [...defaults?.links || []],
|
|
1126
|
+
styles: [...defaults?.styles || []],
|
|
1127
|
+
scripts: [...defaults?.scripts || []],
|
|
1128
|
+
frontmatter: { ...defaults?.frontmatter },
|
|
1129
|
+
manifestHash: manifestHash || "dev"
|
|
1130
|
+
});
|
|
1131
|
+
/**
|
|
1132
|
+
* Resolve the full route config (head + eTag + cacheKey) from all content modules. Uses the same
|
|
1133
|
+
* resolution rules as DocumentHead: objects are merged immediately, functions are queued and
|
|
1134
|
+
* executed inner-before-outer.
|
|
1135
|
+
*/ var resolveRouteConfig = (resolveValue, routeLocation, contentModules, locale, status, defaults) => withLocale(locale, () => {
|
|
1136
|
+
const head = createDocumentHead(defaults);
|
|
1137
|
+
let eTag;
|
|
1138
|
+
let cacheKey;
|
|
1139
|
+
const fns = [];
|
|
1140
|
+
for (let i = 0; i < contentModules.length; i++) {
|
|
1141
|
+
const contentModule = contentModules[i];
|
|
1142
|
+
if (!contentModule) continue;
|
|
1143
|
+
const isLast = i === contentModules.length - 1;
|
|
1144
|
+
let config;
|
|
1145
|
+
if (contentModule.routeConfig) config = contentModule.routeConfig;
|
|
1146
|
+
else if (contentModule.head) {
|
|
1147
|
+
const synthetic = { head: void 0 };
|
|
1148
|
+
const headExport = contentModule.head;
|
|
1149
|
+
if (typeof headExport === "function") {
|
|
1150
|
+
const mod = contentModule;
|
|
1151
|
+
config = (props) => ({
|
|
1152
|
+
head: headExport(props),
|
|
1153
|
+
eTag: mod.eTag,
|
|
1154
|
+
cacheKey: mod.cacheKey
|
|
1155
|
+
});
|
|
1156
|
+
} else {
|
|
1157
|
+
synthetic.head = headExport;
|
|
1158
|
+
const mod = contentModule;
|
|
1159
|
+
if (mod.eTag !== void 0) synthetic.eTag = mod.eTag;
|
|
1160
|
+
if (mod.cacheKey !== void 0) synthetic.cacheKey = mod.cacheKey;
|
|
1161
|
+
config = synthetic;
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
const mod = contentModule;
|
|
1165
|
+
if (mod.eTag !== void 0 || mod.cacheKey !== void 0) config = {
|
|
1166
|
+
eTag: mod.eTag,
|
|
1167
|
+
cacheKey: mod.cacheKey
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
if (!config) continue;
|
|
1171
|
+
if (typeof config === "function") fns.unshift({
|
|
1172
|
+
fn: config,
|
|
1173
|
+
isLast
|
|
1174
|
+
});
|
|
1175
|
+
else {
|
|
1176
|
+
if (config.head) resolveDocumentHead(head, config.head);
|
|
1177
|
+
if (config.eTag !== void 0) eTag = config.eTag;
|
|
1178
|
+
if (config.cacheKey !== void 0) cacheKey = config.cacheKey;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
if (fns.length) {
|
|
1182
|
+
const headProps = {
|
|
1183
|
+
head,
|
|
1184
|
+
status,
|
|
1185
|
+
withLocale: (fn) => fn(),
|
|
1186
|
+
resolveValue,
|
|
1187
|
+
...routeLocation
|
|
1188
|
+
};
|
|
1189
|
+
for (const { fn } of fns) {
|
|
1190
|
+
const result = fn(headProps);
|
|
1191
|
+
if (result.head) resolveDocumentHead(head, result.head);
|
|
1192
|
+
if (result.eTag !== void 0) eTag = result.eTag;
|
|
1193
|
+
if (result.cacheKey !== void 0) cacheKey = result.cacheKey;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return {
|
|
1197
|
+
head,
|
|
1198
|
+
eTag,
|
|
1199
|
+
cacheKey
|
|
1200
|
+
};
|
|
1201
|
+
});
|
|
1202
|
+
/**
|
|
1203
|
+
* Resolve only the document head from all content modules. This is the browser-side entry point
|
|
1204
|
+
* that ignores eTag/cacheKey (server-only concerns).
|
|
1205
|
+
*
|
|
1206
|
+
* Signal values may throw a promise on the client when still loading — callers should wrap this in
|
|
1207
|
+
* `retryOnPromise` to handle that.
|
|
1208
|
+
*/ var resolveHead = (actionData, loaderState, routeLocation, contentModules, locale, defaults) => {
|
|
1209
|
+
const getData = (loaderOrAction) => {
|
|
1210
|
+
const id = loaderOrAction.__id;
|
|
1211
|
+
const signal = loaderState?.[id];
|
|
1212
|
+
if (signal) return signal.value;
|
|
1213
|
+
if (actionData?.action === id) return actionData.actionResult;
|
|
1214
|
+
};
|
|
1215
|
+
return resolveRouteConfig(getData, routeLocation, contentModules, locale, actionData?.status ?? 200, defaults).head;
|
|
1216
|
+
};
|
|
1217
|
+
export { routeLoader$ as A, RouteLoaderCtxContext as B, getRouteLoaderValues as C, loadRouteLoader as D, isImmutableLoader as E, ContentContext as F, QACTION_KEY as G, RouteNavigateContext as H, ContentInternalContext as I, Q_ROUTE as J, QDATA_KEY as K, DocumentHeadContext as L, setLoaderSignalValue as M, setRouteLoaders as N, matchesRouteLoaderId as O, updateRouteLoaderPaths as P, HttpStatusContext as R, getRouteLoaderResponse as S, invalidateNavRouteLoaders as T, RoutePreventNavigateContext as U, RouteLocationContext as V, RouteStateContext as W, _asyncRequestStore as Y, fetchRouteLoaderData as _, isPromise as a, getRequestEvent as b, shouldPreload as c, loadRoute as d, FULLPATH_HEADER as f, ensureRouteLoaderSignals as g, clearRouteLoaderData as h, getClientNavPath as i, routeLoaderQrl as j, resolveRouteLoaderByHash as k, toPath as l, clearNavFetchCache as m, resolveHead as n, isSameOrigin as o, ROUTE_PATH_HEADER as p, QFN_KEY as q, resolveRouteConfig as r, isSamePath as s, createDocumentHead as t, toUrl as u, getLoaderRequestEvent as v, getRouteLoaders as w, getRouteLoaderCtx as x, getModuleRouteLoaders as y, RouteActionContext as z };
|