@solidjs/router 0.10.0-beta.9 → 0.10.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 +44 -6
- package/dist/data/action.js +11 -5
- package/dist/data/cache.d.ts +5 -1
- package/dist/data/cache.js +13 -11
- package/dist/index.js +18 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
# Solid Router [](https://www.npmjs.org/package/@solidjs/router)
|
|
6
6
|
|
|
7
|
+
**Version 0.10.0 requires Solid v1.8.4 or later.**
|
|
8
|
+
|
|
7
9
|
A router lets you change your view based on the URL in the browser. This allows your "single-page" application to simulate a traditional multipage site. To use Solid Router, you specify components called Routes that depend on the value of the URL (the "path"), and the router handles the mechanism of swapping them in and out.
|
|
8
10
|
|
|
9
11
|
Solid Router is a universal router for SolidJS - it works whether you're rendering on the client or on the server. It was inspired by and combines paradigms of React Router and the Ember Router. Routes can be defined directly in your app's template using JSX, but you can also pass your route configuration directly as an object. It also supports nested routing, so navigation can change a part of a component, rather than completely replacing it.
|
|
@@ -376,7 +378,9 @@ getUser.key // returns "users"
|
|
|
376
378
|
getUser.keyFor(id) // returns "users[5]"
|
|
377
379
|
```
|
|
378
380
|
|
|
379
|
-
|
|
381
|
+
You can revalidate the cache using the `revalidate` method or you can set `revalidate` keys on your response from your actions. If you pass the whole key it will invalidate all the entries for the cache (ie "users" in the example above). You can also invalidate a single entry by using `keyFor`.
|
|
382
|
+
|
|
383
|
+
`cache` can be defined anywhere and then used inside your components with:
|
|
380
384
|
|
|
381
385
|
### `createAsync`
|
|
382
386
|
|
|
@@ -390,7 +394,7 @@ Using `cache` in `createResource` directly won't work properly as the fetcher is
|
|
|
390
394
|
|
|
391
395
|
### `action`
|
|
392
396
|
|
|
393
|
-
Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response
|
|
397
|
+
Actions are data mutations that can trigger invalidations and further routing. A list of prebuilt response helpers can be found below.
|
|
394
398
|
```jsx
|
|
395
399
|
import { action, revalidate, redirect } from "@solidjs/router"
|
|
396
400
|
|
|
@@ -450,7 +454,7 @@ const submit = useAction(myAction)
|
|
|
450
454
|
submit(...args)
|
|
451
455
|
```
|
|
452
456
|
|
|
453
|
-
The outside of a form context you can use custom data instead of formData, and these helpers preserve types.
|
|
457
|
+
The outside of a form context you can use custom data instead of formData, and these helpers preserve types. However, even when used with server functions (in projects like SolidStart) this requires client side javascript and is not Progressive Enhancible like forms are.
|
|
454
458
|
|
|
455
459
|
### `useSubmission`/`useSubmissions`
|
|
456
460
|
|
|
@@ -470,6 +474,36 @@ const submissions = useSubmissions(action, (input) => filter(input));
|
|
|
470
474
|
const submission = useSubmission(action, (input) => filter(input));
|
|
471
475
|
```
|
|
472
476
|
|
|
477
|
+
### Response Helpers
|
|
478
|
+
|
|
479
|
+
These are used to communicate router navigations from cache/actions, and can include invalidation hints. Generally these are thrown to not interfere the with the types and make it clear that function ends execution at that point.
|
|
480
|
+
|
|
481
|
+
#### `redirect(path, options)`
|
|
482
|
+
|
|
483
|
+
Redirects to the next route
|
|
484
|
+
```js
|
|
485
|
+
const getUser = cache(() => {
|
|
486
|
+
const user = await api.getCurrentUser()
|
|
487
|
+
if (!user) throw redirect("/login");
|
|
488
|
+
return user;
|
|
489
|
+
})
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
#### `reload(options)`
|
|
493
|
+
|
|
494
|
+
Reloads the data on the current page
|
|
495
|
+
```js
|
|
496
|
+
const getTodo = cache(async (id: number) => {
|
|
497
|
+
const todo = await fetchTodo(id);
|
|
498
|
+
return todo;
|
|
499
|
+
}, "todo")
|
|
500
|
+
|
|
501
|
+
const updateTodo = action(async (todo: Todo) => {
|
|
502
|
+
await updateTodo(todo.id, todo);
|
|
503
|
+
reload({ revalidate: getTodo.keyFor(id) })
|
|
504
|
+
})
|
|
505
|
+
```
|
|
506
|
+
|
|
473
507
|
### Load Functions
|
|
474
508
|
|
|
475
509
|
Even with the cache API it is possible that we have waterfalls both with view logic and with lazy loaded code. With load functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible.
|
|
@@ -614,7 +648,7 @@ This is the main Router component for the browser.
|
|
|
614
648
|
| prop | type | description |
|
|
615
649
|
|-----|----|----|
|
|
616
650
|
| children | `JSX.Element` or `RouteDefinition[]` | The route definitions |
|
|
617
|
-
| root | Component | Top level layout
|
|
651
|
+
| root | Component | Top level layout component |
|
|
618
652
|
| base | string | Base url to use for matching routes |
|
|
619
653
|
| actionBase | string | Root url for server actions, default: `/_server` |
|
|
620
654
|
| preload | boolean | Enables/disables preloads globally, default: `true` |
|
|
@@ -657,7 +691,11 @@ The Component for defining Routes:
|
|
|
657
691
|
|
|
658
692
|
| prop | type | description |
|
|
659
693
|
|-|-|-|
|
|
660
|
-
|
|
|
694
|
+
| path | string | Path partial for defining the route segment |
|
|
695
|
+
| component | `Component` | Component that will be rendered for the matched segment |
|
|
696
|
+
| matchFilters | `MatchFilters` | Additional constraints for matching against the route |
|
|
697
|
+
| children | `JSX.Element` | Nested `<Route>` definitions |
|
|
698
|
+
| load | `RouteLoadFunc` | Function called during preload or when the route is navigated to. |
|
|
661
699
|
|
|
662
700
|
## Router Primitives
|
|
663
701
|
|
|
@@ -786,7 +824,7 @@ The biggest changes are around removed APIs that need to be replaced.
|
|
|
786
824
|
|
|
787
825
|
### `<Outlet>`, `<Routes>`, `useRoutes`
|
|
788
826
|
|
|
789
|
-
This is no longer used and instead will use `props.children` passed from into the page components for outlets. Nested Routes inherently cause waterfalls and are Outlets
|
|
827
|
+
This is no longer used and instead will use `props.children` passed from into the page components for outlets. This keeps the outlet directly passed from its page and avoids oddness of trying to use context across Islands boundaries. Nested `<Routes>` components inherently cause waterfalls and are `<Outlets>` themselves so they have the same concerns. We do not want to encourage the pattern and if you must do it you can always nest `<Router>`s with appropriate base path.
|
|
790
828
|
|
|
791
829
|
## `element` prop removed from `Route`
|
|
792
830
|
|
package/dist/data/action.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $TRACK, createMemo, createSignal, onCleanup, getOwner } from "solid-js";
|
|
1
|
+
import { $TRACK, createMemo, createSignal, onCleanup, getOwner, } from "solid-js";
|
|
2
2
|
import { isServer } from "solid-js/web";
|
|
3
3
|
import { useRouter } from "../routing";
|
|
4
4
|
import { redirectStatusCodes } from "../utils";
|
|
5
|
-
import { hashKey, revalidate } from "./cache";
|
|
5
|
+
import { cacheKeyOp, hashKey, revalidate } from "./cache";
|
|
6
6
|
export const actions = /* #__PURE__ */ new Map();
|
|
7
7
|
export function useSubmissions(fn, filter) {
|
|
8
8
|
const router = useRouter();
|
|
@@ -65,7 +65,9 @@ export function action(fn, name) {
|
|
|
65
65
|
p.then(handler, handler);
|
|
66
66
|
return p;
|
|
67
67
|
}
|
|
68
|
-
const url = fn.url ||
|
|
68
|
+
const url = fn.url ||
|
|
69
|
+
(name && `action:${name}`) ||
|
|
70
|
+
(!isServer ? `action:${hashString(fn.toString())}` : "");
|
|
69
71
|
return toAction(mutate, url);
|
|
70
72
|
}
|
|
71
73
|
function toAction(fn, url) {
|
|
@@ -80,7 +82,7 @@ function toAction(fn, url) {
|
|
|
80
82
|
};
|
|
81
83
|
const uri = new URL(url, "http://sar");
|
|
82
84
|
uri.searchParams.set("args", hashKey(args));
|
|
83
|
-
return toAction(newFn, uri.pathname + uri.search);
|
|
85
|
+
return toAction(newFn, (uri.protocol === "action:" ? uri.protocol : "") + uri.pathname + uri.search);
|
|
84
86
|
};
|
|
85
87
|
fn.url = url;
|
|
86
88
|
if (!isServer) {
|
|
@@ -89,12 +91,15 @@ function toAction(fn, url) {
|
|
|
89
91
|
}
|
|
90
92
|
return fn;
|
|
91
93
|
}
|
|
94
|
+
const hashString = (s) => s.split("").reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0);
|
|
92
95
|
async function handleResponse(response, navigate) {
|
|
93
96
|
let data;
|
|
94
97
|
let keys;
|
|
95
98
|
if (response instanceof Response) {
|
|
96
99
|
if (response.headers.has("X-Revalidate")) {
|
|
97
100
|
keys = response.headers.get("X-Revalidate").split(",");
|
|
101
|
+
// invalidate
|
|
102
|
+
cacheKeyOp(keys, entry => (entry[0] = 0));
|
|
98
103
|
}
|
|
99
104
|
if (response.customBody)
|
|
100
105
|
data = await response.customBody();
|
|
@@ -110,6 +115,7 @@ async function handleResponse(response, navigate) {
|
|
|
110
115
|
}
|
|
111
116
|
else
|
|
112
117
|
data = response;
|
|
113
|
-
|
|
118
|
+
// trigger revalidation
|
|
119
|
+
await revalidate(keys, false);
|
|
114
120
|
return data;
|
|
115
121
|
}
|
package/dist/data/cache.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { type Signal } from "solid-js";
|
|
1
2
|
import { type ReconcileOptions } from "solid-js/store";
|
|
2
|
-
|
|
3
|
+
type CacheEntry = [number, any, string, Set<Signal<number>>];
|
|
4
|
+
export declare function revalidate(key?: string | string[] | void, force?: boolean): Promise<void>;
|
|
5
|
+
export declare function cacheKeyOp(key: string | string[] | void, fn: (cacheEntry: CacheEntry) => void): void;
|
|
3
6
|
export type CachedFunction<T extends (...args: any) => U | Response, U> = T & {
|
|
4
7
|
keyFor: (...args: Parameters<T>) => string;
|
|
5
8
|
key: string;
|
|
@@ -9,3 +12,4 @@ export declare namespace cache {
|
|
|
9
12
|
var set: (key: string, value: any) => void;
|
|
10
13
|
}
|
|
11
14
|
export declare function hashKey<T extends Array<any>>(args: T): string;
|
|
15
|
+
export {};
|
package/dist/data/cache.js
CHANGED
|
@@ -26,19 +26,22 @@ function getCache() {
|
|
|
26
26
|
throw new Error("Cannot find cache context");
|
|
27
27
|
return req.routerCache || (req.routerCache = new Map());
|
|
28
28
|
}
|
|
29
|
-
export function revalidate(key) {
|
|
30
|
-
key && !Array.isArray(key) && (key = [key]);
|
|
29
|
+
export function revalidate(key, force = true) {
|
|
31
30
|
return startTransition(() => {
|
|
32
31
|
const now = Date.now();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
revalidateSignals(entry[3], now); // retrigger live signals
|
|
38
|
-
}
|
|
39
|
-
}
|
|
32
|
+
cacheKeyOp(key, entry => {
|
|
33
|
+
force && (entry[0] = 0); //force cache miss
|
|
34
|
+
revalidateSignals(entry[3], now); // retrigger live signals
|
|
35
|
+
});
|
|
40
36
|
});
|
|
41
37
|
}
|
|
38
|
+
export function cacheKeyOp(key, fn) {
|
|
39
|
+
key && !Array.isArray(key) && (key = [key]);
|
|
40
|
+
for (let k of cacheMap.keys()) {
|
|
41
|
+
if (key === undefined || matchKey(k, key))
|
|
42
|
+
fn(cacheMap.get(k));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
42
45
|
function revalidateSignals(set, time) {
|
|
43
46
|
for (let s of set)
|
|
44
47
|
s[1](time);
|
|
@@ -80,7 +83,7 @@ export function cache(fn, name, options) {
|
|
|
80
83
|
? sharedConfig.load(key) // hydrating
|
|
81
84
|
: fn(...args);
|
|
82
85
|
// serialize on server
|
|
83
|
-
if (isServer &&
|
|
86
|
+
if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
|
|
84
87
|
const e = getRequestEvent();
|
|
85
88
|
(!e || !e.serverOnly) && sharedConfig.context.serialize(key, res);
|
|
86
89
|
}
|
|
@@ -133,7 +136,6 @@ export function cache(fn, name, options) {
|
|
|
133
136
|
cachedFn.key = name;
|
|
134
137
|
return cachedFn;
|
|
135
138
|
}
|
|
136
|
-
;
|
|
137
139
|
cache.set = (key, value) => {
|
|
138
140
|
const cache = getCache();
|
|
139
141
|
const now = Date.now();
|
package/dist/index.js
CHANGED
|
@@ -768,20 +768,22 @@ function getCache() {
|
|
|
768
768
|
if (!req) throw new Error("Cannot find cache context");
|
|
769
769
|
return req.routerCache || (req.routerCache = new Map());
|
|
770
770
|
}
|
|
771
|
-
function revalidate(key) {
|
|
772
|
-
key && !Array.isArray(key) && (key = [key]);
|
|
771
|
+
function revalidate(key, force = true) {
|
|
773
772
|
return startTransition(() => {
|
|
774
773
|
const now = Date.now();
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
revalidateSignals(entry[3], now); // retrigger live signals
|
|
780
|
-
}
|
|
781
|
-
}
|
|
774
|
+
cacheKeyOp(key, entry => {
|
|
775
|
+
force && (entry[0] = 0); //force cache miss
|
|
776
|
+
revalidateSignals(entry[3], now); // retrigger live signals
|
|
777
|
+
});
|
|
782
778
|
});
|
|
783
779
|
}
|
|
784
780
|
|
|
781
|
+
function cacheKeyOp(key, fn) {
|
|
782
|
+
key && !Array.isArray(key) && (key = [key]);
|
|
783
|
+
for (let k of cacheMap.keys()) {
|
|
784
|
+
if (key === undefined || matchKey(k, key)) fn(cacheMap.get(k));
|
|
785
|
+
}
|
|
786
|
+
}
|
|
785
787
|
function revalidateSignals(set, time) {
|
|
786
788
|
for (let s of set) s[1](time);
|
|
787
789
|
}
|
|
@@ -965,7 +967,7 @@ function action(fn, name) {
|
|
|
965
967
|
p.then(handler, handler);
|
|
966
968
|
return p;
|
|
967
969
|
}
|
|
968
|
-
const url = fn.url || name && `action:${name}` || (!isServer ? `action:${fn.
|
|
970
|
+
const url = fn.url || name && `action:${name}` || (!isServer ? `action:${hashString(fn.toString())}` : "");
|
|
969
971
|
return toAction(mutate, url);
|
|
970
972
|
}
|
|
971
973
|
function toAction(fn, url) {
|
|
@@ -979,7 +981,7 @@ function toAction(fn, url) {
|
|
|
979
981
|
};
|
|
980
982
|
const uri = new URL(url, "http://sar");
|
|
981
983
|
uri.searchParams.set("args", hashKey(args));
|
|
982
|
-
return toAction(newFn, uri.pathname + uri.search);
|
|
984
|
+
return toAction(newFn, (uri.protocol === "action:" ? uri.protocol : "") + uri.pathname + uri.search);
|
|
983
985
|
};
|
|
984
986
|
fn.url = url;
|
|
985
987
|
if (!isServer) {
|
|
@@ -988,12 +990,15 @@ function toAction(fn, url) {
|
|
|
988
990
|
}
|
|
989
991
|
return fn;
|
|
990
992
|
}
|
|
993
|
+
const hashString = s => s.split("").reduce((a, b) => (a << 5) - a + b.charCodeAt(0) | 0, 0);
|
|
991
994
|
async function handleResponse(response, navigate) {
|
|
992
995
|
let data;
|
|
993
996
|
let keys;
|
|
994
997
|
if (response instanceof Response) {
|
|
995
998
|
if (response.headers.has("X-Revalidate")) {
|
|
996
999
|
keys = response.headers.get("X-Revalidate").split(",");
|
|
1000
|
+
// invalidate
|
|
1001
|
+
cacheKeyOp(keys, entry => entry[0] = 0);
|
|
997
1002
|
}
|
|
998
1003
|
if (response.customBody) data = await response.customBody();
|
|
999
1004
|
if (redirectStatusCodes.has(response.status)) {
|
|
@@ -1005,7 +1010,8 @@ async function handleResponse(response, navigate) {
|
|
|
1005
1010
|
}
|
|
1006
1011
|
}
|
|
1007
1012
|
} else data = response;
|
|
1008
|
-
|
|
1013
|
+
// trigger revalidation
|
|
1014
|
+
await revalidate(keys, false);
|
|
1009
1015
|
return data;
|
|
1010
1016
|
}
|
|
1011
1017
|
|