@solidjs/router 1.0.0-next.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +715 -531
- package/dist/components.d.ts +31 -0
- package/dist/components.jsx +40 -0
- package/dist/data/action.d.ts +11 -40
- package/dist/data/action.js +100 -274
- package/dist/data/createAsync.d.ts +32 -0
- package/dist/data/createAsync.js +96 -0
- package/dist/data/events.d.ts +0 -8
- package/dist/data/events.js +23 -24
- package/dist/data/index.d.ts +4 -2
- package/dist/data/index.js +4 -2
- package/dist/data/query.d.ts +3 -1
- package/dist/data/query.js +22 -39
- package/dist/data/response.d.ts +4 -0
- package/dist/data/response.js +42 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +1024 -1578
- package/dist/index.jsx +2 -2
- package/dist/lifecycle.d.ts +4 -29
- package/dist/lifecycle.js +37 -40
- package/dist/routers/HashRouter.d.ts +9 -0
- package/dist/routers/HashRouter.js +41 -0
- package/dist/routers/MemoryRouter.d.ts +24 -0
- package/dist/routers/MemoryRouter.js +57 -0
- package/dist/routers/Router.d.ts +17 -0
- package/dist/routers/Router.js +59 -0
- package/dist/routers/StaticRouter.d.ts +6 -0
- package/dist/routers/StaticRouter.js +15 -0
- package/dist/routers/components.d.ts +26 -12
- package/dist/routers/components.jsx +54 -68
- package/dist/routers/createRouter.d.ts +10 -0
- package/dist/routers/createRouter.js +41 -0
- package/dist/routers/index.d.ts +11 -4
- package/dist/routers/index.js +6 -2
- package/dist/routers/scrollRestoration.d.ts +32 -0
- package/dist/routers/scrollRestoration.js +96 -0
- package/dist/routing.d.ts +52 -88
- package/dist/routing.js +166 -381
- package/dist/types.d.ts +33 -83
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +0 -2
- package/package.json +7 -11
- package/dist/claims.d.ts +0 -21
- package/dist/claims.js +0 -115
- package/dist/data/flash.d.ts +0 -21
- package/dist/data/flash.js +0 -78
- package/dist/data/flashCookie.d.ts +0 -7
- package/dist/data/flashCookie.js +0 -20
- package/dist/data/serverForms.d.ts +0 -1
- package/dist/data/serverForms.js +0 -5
- package/dist/paths.d.ts +0 -117
- package/dist/paths.js +0 -41
- package/dist/routers/factory.d.ts +0 -45
- package/dist/routers/factory.jsx +0 -143
- package/dist/routers/history.d.ts +0 -24
- package/dist/routers/history.js +0 -180
- package/dist/server.d.ts +0 -75
- package/dist/server.js +0 -264
package/dist/server.js
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
// The router's server-side integration with the server function runtime
|
|
2
|
-
// (@solidjs/web/server-functions). Server-only entry (`@solidjs/router/server`):
|
|
3
|
-
// it reaches for node async context via @solidjs/web/storage — keep it out of
|
|
4
|
-
// client bundles.
|
|
5
|
-
//
|
|
6
|
-
// Two policies live here, both previously implemented inside SolidStart and
|
|
7
|
-
// now owned by the router (the vocabulary — query keys, submissions — is the
|
|
8
|
-
// router's):
|
|
9
|
-
//
|
|
10
|
-
// - single-flight mutations: `createFlightDataCollector` produces the
|
|
11
|
-
// `collectFlightData` hook the app hands to
|
|
12
|
-
// `configureServerFunctionsServer` (or the handler options). After a
|
|
13
|
-
// mutation it produces the route data the mutation invalidated for the
|
|
14
|
-
// page the client is on (or is being redirected to), which the handler
|
|
15
|
-
// folds into the response — mutation and fresh data in one round trip.
|
|
16
|
-
// - the no-JS form convention: `createNoJSHandler` produces the
|
|
17
|
-
// `handleNoJS` hook — form posts made without the client runtime redirect
|
|
18
|
-
// back (303) carrying the outcome in a one-shot flash cookie that the
|
|
19
|
-
// router's SSR initialization reads into submission state.
|
|
20
|
-
import { provideRequestEvent } from "@solidjs/web/storage";
|
|
21
|
-
import { createBranches, getRouteMatches, mergeParams, peekLazySubtrees, resolveLazySubtree } from "./routing.js";
|
|
22
|
-
import { extractSearchParams } from "./utils.js";
|
|
23
|
-
import { encodeFlashCookie } from "./data/flash.js";
|
|
24
|
-
// the instance is the provider component, so it (unlike an options object) is a function
|
|
25
|
-
function isRouterInstance(options) {
|
|
26
|
-
return typeof options === "function";
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Produces the `collectFlightData` implementation for
|
|
30
|
-
* `configureServerFunctionsServer` (or `handleServerFunctionRequest`
|
|
31
|
-
* options). Accepts a `createRouter` instance directly — its routes, base,
|
|
32
|
-
* and `preload` are the single source of truth — or an options object for
|
|
33
|
-
* trees not created through the factory.
|
|
34
|
-
*
|
|
35
|
-
* Strategy: rerun the route data for the URL the client will show
|
|
36
|
-
* after the mutation — the redirect `Location` when the outcome carries
|
|
37
|
-
* one, the referring page otherwise — collecting each `query` result under
|
|
38
|
-
* its cache key, scoped to the outcome's `X-Revalidate` keys when present
|
|
39
|
-
* (routes newly entered via redirect always collect fully). The returned
|
|
40
|
-
* payload seeds the client router's cache through its registered
|
|
41
|
-
* flight-data consumer.
|
|
42
|
-
*/
|
|
43
|
-
export function createFlightDataCollector(options) {
|
|
44
|
-
const { routes, rootPreload, base = "" } = isRouterInstance(options)
|
|
45
|
-
? { routes: options.routes, rootPreload: options.config.preload, base: options.config.base }
|
|
46
|
-
: options;
|
|
47
|
-
if (!routes)
|
|
48
|
-
throw new Error("createFlightDataCollector requires `routes`");
|
|
49
|
-
let branches;
|
|
50
|
-
let compiledVersion = -1;
|
|
51
|
-
// thunks are called at first collection so per-request trees build lazily;
|
|
52
|
-
// recompiled when a lazy subtree resolves (shared, append-only state)
|
|
53
|
-
const resolveBranches = () => {
|
|
54
|
-
const version = peekLazySubtrees();
|
|
55
|
-
if (!branches || compiledVersion !== version) {
|
|
56
|
-
branches = createBranches(typeof routes === "function" ? routes() : routes, base);
|
|
57
|
-
compiledVersion = version;
|
|
58
|
-
}
|
|
59
|
-
return branches;
|
|
60
|
-
};
|
|
61
|
-
return async (sourceEvent, outcome) => {
|
|
62
|
-
// no referrer, nothing to produce data for (e.g. non-browser callers)
|
|
63
|
-
const referrer = outcome.request.headers.get("referer");
|
|
64
|
-
if (!referrer)
|
|
65
|
-
return undefined;
|
|
66
|
-
// a raw body-carrying Response is the caller's verbatim payload — there
|
|
67
|
-
// is no envelope to fold data into
|
|
68
|
-
if (outcome.value instanceof Response && outcome.value.body)
|
|
69
|
-
return undefined;
|
|
70
|
-
const origin = new URL(outcome.request.url).origin;
|
|
71
|
-
let revalidate;
|
|
72
|
-
let url = new URL(referrer).toString();
|
|
73
|
-
if (outcome.response) {
|
|
74
|
-
if (outcome.response.headers.has("X-Revalidate"))
|
|
75
|
-
revalidate = outcome.response.headers.get("X-Revalidate").split(",");
|
|
76
|
-
if (outcome.response.headers.has("Location"))
|
|
77
|
-
url = new URL(outcome.response.headers.get("Location"), origin + base).toString();
|
|
78
|
-
}
|
|
79
|
-
// redirects leaving the app can't be collected for
|
|
80
|
-
if (new URL(url).origin !== origin)
|
|
81
|
-
return undefined;
|
|
82
|
-
// The flight event: the source event (its `response` rides along, so
|
|
83
|
-
// cookies/headers set during preloads still reach the real response)
|
|
84
|
-
// pointed at the target URL, with the mutation's own cookie mutations
|
|
85
|
-
// folded into the request, in data-only router mode.
|
|
86
|
-
const event = { ...sourceEvent };
|
|
87
|
-
event.request = new Request(url, { headers: createSingleFlightHeaders(sourceEvent) });
|
|
88
|
-
event.router = {
|
|
89
|
-
dataOnly: revalidate || true,
|
|
90
|
-
previousUrl: referrer,
|
|
91
|
-
data: {}
|
|
92
|
-
};
|
|
93
|
-
return provideRequestEvent(event, async () => {
|
|
94
|
-
try {
|
|
95
|
-
await resolveLazyMatches(resolveBranches, url, referrer);
|
|
96
|
-
runPreloads(event, resolveBranches(), url, referrer, rootPreload);
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
console.error(error);
|
|
100
|
-
}
|
|
101
|
-
const data = event.router.data;
|
|
102
|
-
if (!data)
|
|
103
|
-
return undefined;
|
|
104
|
-
let containsKey = false;
|
|
105
|
-
for (const key in data) {
|
|
106
|
-
if (data[key] === undefined)
|
|
107
|
-
delete data[key];
|
|
108
|
-
else
|
|
109
|
-
containsKey = true;
|
|
110
|
-
}
|
|
111
|
-
return containsKey ? data : undefined;
|
|
112
|
-
});
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
// Lazy subtrees matched by the target (or previous) URL must resolve before
|
|
116
|
-
// the preload pass — the collector would otherwise be blind to inner routes'
|
|
117
|
-
// data. Resolution is shared with the client-side machinery (append-only,
|
|
118
|
-
// cached per thunk); the loop handles boundaries nested inside boundaries.
|
|
119
|
-
async function resolveLazyMatches(resolveBranches, url, previousUrl) {
|
|
120
|
-
for (;;) {
|
|
121
|
-
const branches = resolveBranches();
|
|
122
|
-
const target = new URL(url);
|
|
123
|
-
const matches = [
|
|
124
|
-
...getRouteMatches(branches, target.pathname),
|
|
125
|
-
...getRouteMatches(branches, new URL(previousUrl, target).pathname)
|
|
126
|
-
];
|
|
127
|
-
const pending = matches.filter(m => m.route.lazy && !m.route.lazy.resolved);
|
|
128
|
-
if (!pending.length)
|
|
129
|
-
return;
|
|
130
|
-
await Promise.all(pending.map(m => resolveLazySubtree(m.route.lazy)));
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// The pure preload runner: the same collection loop the router's <Routes>
|
|
134
|
-
// component runs when it detects a data-only render (routers/components.tsx),
|
|
135
|
-
// driven directly off the route tree — matched routes' preloads run and
|
|
136
|
-
// their `query` calls store results on `event.router.data`. Routes not
|
|
137
|
-
// matched by the previous URL flip collection from the revalidation-key
|
|
138
|
-
// filter to everything (their queries have no client cache yet).
|
|
139
|
-
function runPreloads(event, branches, url, previousUrl, rootPreload) {
|
|
140
|
-
const target = new URL(url);
|
|
141
|
-
const prevMatches = getRouteMatches(branches, new URL(previousUrl, target).pathname);
|
|
142
|
-
const matches = getRouteMatches(branches, target.pathname);
|
|
143
|
-
const location = {
|
|
144
|
-
pathname: target.pathname,
|
|
145
|
-
search: target.search,
|
|
146
|
-
hash: target.hash,
|
|
147
|
-
query: extractSearchParams(target),
|
|
148
|
-
state: null,
|
|
149
|
-
key: ""
|
|
150
|
-
};
|
|
151
|
-
// the root layout preloads first, exactly as <Root> would during a real
|
|
152
|
-
// server render: merged params of every match, intent "initial" (there is
|
|
153
|
-
// no navigation intent on the server), before any dataOnly-filter flip
|
|
154
|
-
rootPreload && rootPreload({ params: mergeParams(matches), location, intent: "initial" });
|
|
155
|
-
for (let match = 0; match < matches.length; match++) {
|
|
156
|
-
if (!prevMatches[match] || matches[match].route !== prevMatches[match].route)
|
|
157
|
-
event.router.dataOnly = true;
|
|
158
|
-
const { route, params } = matches[match];
|
|
159
|
-
route.preload &&
|
|
160
|
-
route.preload({
|
|
161
|
-
params,
|
|
162
|
-
location,
|
|
163
|
-
intent: "preload"
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* The request headers for the flight-data collection pass: the source
|
|
169
|
-
* request's headers with the mutation's `Set-Cookie` mutations folded into
|
|
170
|
-
* the `Cookie` header, so preloads observe the post-mutation cookie state
|
|
171
|
-
* (deletions honored via Max-Age/Expires).
|
|
172
|
-
*/
|
|
173
|
-
export function createSingleFlightHeaders(sourceEvent) {
|
|
174
|
-
const headers = new Headers(sourceEvent.request.headers);
|
|
175
|
-
const setCookies = sourceEvent.response?.headers?.getSetCookie() ?? [];
|
|
176
|
-
if (!setCookies.length)
|
|
177
|
-
return headers;
|
|
178
|
-
const cookies = {};
|
|
179
|
-
for (const pair of headers.get("cookie")?.split(";") ?? []) {
|
|
180
|
-
const eq = pair.indexOf("=");
|
|
181
|
-
if (eq > -1)
|
|
182
|
-
cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
183
|
-
}
|
|
184
|
-
for (const setCookie of setCookies) {
|
|
185
|
-
const parsed = parseSetCookie(setCookie);
|
|
186
|
-
if (!parsed)
|
|
187
|
-
continue;
|
|
188
|
-
if ((parsed.maxAge != null && parsed.maxAge <= 0) ||
|
|
189
|
-
(parsed.expires != null && parsed.expires.getTime() <= Date.now())) {
|
|
190
|
-
delete cookies[parsed.name];
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
cookies[parsed.name] = parsed.value;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
headers.delete("cookie");
|
|
197
|
-
const serialized = Object.entries(cookies)
|
|
198
|
-
.map(([name, value]) => `${name}=${value}`)
|
|
199
|
-
.join("; ");
|
|
200
|
-
if (serialized)
|
|
201
|
-
headers.set("cookie", serialized);
|
|
202
|
-
return headers;
|
|
203
|
-
}
|
|
204
|
-
function parseSetCookie(setCookie) {
|
|
205
|
-
const [pair, ...attributes] = setCookie.split(";");
|
|
206
|
-
const eq = pair.indexOf("=");
|
|
207
|
-
if (eq < 0)
|
|
208
|
-
return undefined;
|
|
209
|
-
const parsed = {
|
|
210
|
-
name: pair.slice(0, eq).trim(),
|
|
211
|
-
value: pair.slice(eq + 1).trim()
|
|
212
|
-
};
|
|
213
|
-
for (const attribute of attributes) {
|
|
214
|
-
const attrEq = attribute.indexOf("=");
|
|
215
|
-
const key = (attrEq < 0 ? attribute : attribute.slice(0, attrEq)).trim().toLowerCase();
|
|
216
|
-
const value = attrEq < 0 ? "" : attribute.slice(attrEq + 1).trim();
|
|
217
|
-
if (key === "max-age")
|
|
218
|
-
parsed.maxAge = Number(value);
|
|
219
|
-
else if (key === "expires")
|
|
220
|
-
parsed.expires = new Date(value);
|
|
221
|
-
}
|
|
222
|
-
return parsed;
|
|
223
|
-
}
|
|
224
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages
|
|
225
|
-
const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
226
|
-
/**
|
|
227
|
-
* Produces the `handleNoJS` implementation for
|
|
228
|
-
* `handleServerFunctionRequest` options: form posts made without the client
|
|
229
|
-
* runtime redirect back to the referring page (or to the result's
|
|
230
|
-
* `Location`) with the outcome riding a one-shot `flash` cookie. The
|
|
231
|
-
* router's SSR initialization reads the cookie into submission state, so
|
|
232
|
-
* `useSubmission()` renders the result exactly as a scripted submission
|
|
233
|
-
* would — progressive enhancement with no app code.
|
|
234
|
-
*/
|
|
235
|
-
export function createNoJSHandler(options = {}) {
|
|
236
|
-
const { base = "" } = options;
|
|
237
|
-
return function handleNoJS(result, request, args, thrown) {
|
|
238
|
-
const url = new URL(request.url);
|
|
239
|
-
const back = request.headers.get("referer") || url.origin + base;
|
|
240
|
-
// form post -> GET: 303 See Other unless the result names a redirect status
|
|
241
|
-
let status = 303;
|
|
242
|
-
let headers;
|
|
243
|
-
if (result instanceof Response) {
|
|
244
|
-
headers = new Headers(result.headers);
|
|
245
|
-
if (result.headers.has("Location")) {
|
|
246
|
-
headers.set("Location", new URL(result.headers.get("Location"), url.origin + base).toString());
|
|
247
|
-
if (validRedirectStatuses.has(result.status))
|
|
248
|
-
status = result.status;
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
headers.set("Location", back);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
headers = new Headers({ Location: back });
|
|
256
|
-
}
|
|
257
|
-
// Responses carry their meaning in their metadata; anything else flashes
|
|
258
|
-
// the outcome for the next render's submission state.
|
|
259
|
-
if (result && !(result instanceof Response)) {
|
|
260
|
-
headers.append("Set-Cookie", encodeFlashCookie(url.pathname + url.search, result, args, thrown));
|
|
261
|
-
}
|
|
262
|
-
return new Response(null, { status, headers });
|
|
263
|
-
};
|
|
264
|
-
}
|