@solidjs/router 0.13.6 → 0.14.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 +23 -23
- package/dist/data/action.d.ts +4 -4
- package/dist/data/cache.d.ts +2 -2
- package/dist/data/cache.js +4 -4
- package/dist/data/events.js +4 -4
- package/dist/data/response.d.ts +4 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +30 -29
- package/dist/index.jsx +1 -1
- package/dist/routers/Router.js +1 -1
- package/dist/routers/components.d.ts +7 -3
- package/dist/routers/components.jsx +8 -8
- package/dist/routing.d.ts +2 -2
- package/dist/routing.js +16 -16
- package/dist/types.d.ts +18 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ A router lets you change your view based on the URL in the browser. This allows
|
|
|
10
10
|
|
|
11
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.
|
|
12
12
|
|
|
13
|
-
It supports all of Solid's SSR methods and has Solid's transitions baked in, so use it freely with suspense, resources, and lazy components. Solid Router also allows you to define a
|
|
13
|
+
It supports all of Solid's SSR methods and has Solid's transitions baked in, so use it freely with suspense, resources, and lazy components. Solid Router also allows you to define a preload function that loads parallel to the routes ([render-as-you-fetch](https://epicreact.dev/render-as-you-fetch/)).
|
|
14
14
|
|
|
15
15
|
- [Getting Started](#getting-started)
|
|
16
16
|
- [Set Up the Router](#set-up-the-router)
|
|
@@ -380,11 +380,11 @@ You can nest indefinitely - just remember that only leaf nodes will become their
|
|
|
380
380
|
</Route>
|
|
381
381
|
```
|
|
382
382
|
|
|
383
|
-
##
|
|
383
|
+
## Preload Functions
|
|
384
384
|
|
|
385
|
-
Even with smart caches it is possible that we have waterfalls both with view logic and with lazy loaded code. With
|
|
385
|
+
Even with smart caches it is possible that we have waterfalls both with view logic and with lazy loaded code. With preload functions, we can instead start fetching the data parallel to loading the route, so we can use the data as soon as possible. The preload function is called when the Route is loaded or eagerly when links are hovered.
|
|
386
386
|
|
|
387
|
-
As its only argument, the
|
|
387
|
+
As its only argument, the preload function is passed an object that you can use to access route information:
|
|
388
388
|
|
|
389
389
|
```js
|
|
390
390
|
import { lazy } from "solid-js";
|
|
@@ -392,13 +392,13 @@ import { Route } from "@solidjs/router";
|
|
|
392
392
|
|
|
393
393
|
const User = lazy(() => import("./pages/users/[id].js"));
|
|
394
394
|
|
|
395
|
-
//
|
|
396
|
-
function
|
|
397
|
-
// do
|
|
395
|
+
// preload function
|
|
396
|
+
function preloadUser({params, location}) {
|
|
397
|
+
// do preloading
|
|
398
398
|
}
|
|
399
399
|
|
|
400
400
|
// Pass it in the route definition
|
|
401
|
-
<Route path="/users/:id" component={User}
|
|
401
|
+
<Route path="/users/:id" component={User} preload={preloadUser} />;
|
|
402
402
|
```
|
|
403
403
|
|
|
404
404
|
| key | type | description |
|
|
@@ -408,24 +408,24 @@ function loadUser({params, location}) {
|
|
|
408
408
|
| intent | `"initial", "navigate", "native", "preload"` | Indicates why this function is being called. <ul><li>"initial" - the route is being initially shown (ie page load)</li><li>"native" - navigate originated from the browser (eg back/forward)</li><li>"navigate" - navigate originated from the router (eg call to navigate or anchor clicked)</li><li>"preload" - not navigating, just preloading (eg link hover)</li></ul> |
|
|
409
409
|
|
|
410
410
|
|
|
411
|
-
A common pattern is to export the
|
|
411
|
+
A common pattern is to export the preload function and data wrappers that corresponds to a route in a dedicated `route.data.js` file. This way, the data function can be imported without loading anything else.
|
|
412
412
|
|
|
413
413
|
```js
|
|
414
414
|
import { lazy } from "solid-js";
|
|
415
415
|
import { Route } from "@solidjs/router";
|
|
416
|
-
import
|
|
416
|
+
import preloadUser from "./pages/users/[id].data.js";
|
|
417
417
|
const User = lazy(() => import("/pages/users/[id].js"));
|
|
418
418
|
|
|
419
419
|
// In the Route definition
|
|
420
|
-
<Route path="/users/:id" component={User}
|
|
420
|
+
<Route path="/users/:id" component={User} preload={preloadUser} />;
|
|
421
421
|
```
|
|
422
422
|
|
|
423
|
-
The return value of the `
|
|
423
|
+
The return value of the `preload` function is passed to the page component when called at anytime other than `"preload"` intent, so you can initialize things in there, or alternatively use our new Data APIs:
|
|
424
424
|
|
|
425
425
|
|
|
426
426
|
## Data APIs
|
|
427
427
|
|
|
428
|
-
Keep in mind these are completely optional. To use but showcase the power of our
|
|
428
|
+
Keep in mind these are completely optional. To use but showcase the power of our preload mechanism.
|
|
429
429
|
|
|
430
430
|
### `cache`
|
|
431
431
|
|
|
@@ -441,11 +441,11 @@ It is expected that the arguments to the cache function are serializable.
|
|
|
441
441
|
This cache accomplishes the following:
|
|
442
442
|
|
|
443
443
|
1. It does just deduping on the server for the lifetime of the request.
|
|
444
|
-
2. It does preload cache in the browser which lasts
|
|
444
|
+
2. It does preload cache in the browser which lasts 5 seconds. When a route is preloaded on hover or when preload is called when entering a route it will make sure to dedupe calls.
|
|
445
445
|
3. We have a reactive refetch mechanism based on key. So we can tell routes that aren't new to retrigger on action revalidation.
|
|
446
446
|
4. It will serve as a back/forward cache for browser navigation up to 5 mins. Any user based navigation or link click bypasses it. Revalidation or new fetch updates the cache.
|
|
447
447
|
|
|
448
|
-
Using it with
|
|
448
|
+
Using it with preload function might look like:
|
|
449
449
|
|
|
450
450
|
```js
|
|
451
451
|
import { lazy } from "solid-js";
|
|
@@ -454,13 +454,13 @@ import { getUser } from ... // the cache function
|
|
|
454
454
|
|
|
455
455
|
const User = lazy(() => import("./pages/users/[id].js"));
|
|
456
456
|
|
|
457
|
-
//
|
|
458
|
-
function
|
|
457
|
+
// preload function
|
|
458
|
+
function preloadUser({params, location}) {
|
|
459
459
|
void getUser(params.id)
|
|
460
460
|
}
|
|
461
461
|
|
|
462
462
|
// Pass it in the route definition
|
|
463
|
-
<Route path="/users/:id" component={User}
|
|
463
|
+
<Route path="/users/:id" component={User} preload={preloadUser} />;
|
|
464
464
|
```
|
|
465
465
|
|
|
466
466
|
Inside your page component you:
|
|
@@ -770,7 +770,7 @@ The Component for defining Routes:
|
|
|
770
770
|
| component | `Component` | Component that will be rendered for the matched segment |
|
|
771
771
|
| matchFilters | `MatchFilters` | Additional constraints for matching against the route |
|
|
772
772
|
| children | `JSX.Element` | Nested `<Route>` definitions |
|
|
773
|
-
|
|
|
773
|
+
| preload | `RoutePreloadFunc` | Function called during preload or when the route is navigated to. |
|
|
774
774
|
|
|
775
775
|
## Router Primitives
|
|
776
776
|
|
|
@@ -929,7 +929,7 @@ Related without Outlet component it has to be passed in manually. At which point
|
|
|
929
929
|
|
|
930
930
|
### `data` functions & `useRouteData`
|
|
931
931
|
|
|
932
|
-
These have been replaced by a
|
|
932
|
+
These have been replaced by a preload mechanism. This allows link hover preloads (as the preload function can be run as much as wanted without worry about reactivity). It support deduping/cache APIs which give more control over how things are cached. It also addresses TS issues with getting the right types in the Component without `typeof` checks.
|
|
933
933
|
|
|
934
934
|
That being said you can reproduce the old pattern largely by turning off preloads at the router level and then injecting your own Context:
|
|
935
935
|
|
|
@@ -939,15 +939,15 @@ import { Route } from "@solidjs/router";
|
|
|
939
939
|
|
|
940
940
|
const User = lazy(() => import("./pages/users/[id].js"));
|
|
941
941
|
|
|
942
|
-
//
|
|
943
|
-
function
|
|
942
|
+
// preload function
|
|
943
|
+
function preloadUser({params, location}) {
|
|
944
944
|
const [user] = createResource(() => params.id, fetchUser);
|
|
945
945
|
return user;
|
|
946
946
|
}
|
|
947
947
|
|
|
948
948
|
// Pass it in the route definition
|
|
949
949
|
<Router preload={false}>
|
|
950
|
-
<Route path="/users/:id" component={User}
|
|
950
|
+
<Route path="/users/:id" component={User} preload={preloadUser} />
|
|
951
951
|
</Router>
|
|
952
952
|
```
|
|
953
953
|
|
package/dist/data/action.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { JSX } from "solid-js";
|
|
2
|
-
import type { Submission, SubmissionStub } from "../types.js";
|
|
3
|
-
export type Action<T extends Array<any>, U> = (T extends [FormData] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<U
|
|
2
|
+
import type { Submission, SubmissionStub, NarrowResponse } from "../types.js";
|
|
3
|
+
export type Action<T extends Array<any>, U> = (T extends [FormData] | [] ? JSX.SerializableAttributeValue : unknown) & ((...vars: T) => Promise<NarrowResponse<U>>) & {
|
|
4
4
|
url: string;
|
|
5
|
-
with<A extends any[], B extends any[]>(this: (this: any, ...args: [...A, ...B]) => Promise<U
|
|
5
|
+
with<A extends any[], B extends any[]>(this: (this: any, ...args: [...A, ...B]) => Promise<NarrowResponse<U>>, ...args: A): Action<B, U>;
|
|
6
6
|
};
|
|
7
7
|
export declare const actions: Map<string, Action<any, any>>;
|
|
8
8
|
export declare function useSubmissions<T extends Array<any>, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U>[] & {
|
|
9
9
|
pending: boolean;
|
|
10
10
|
};
|
|
11
11
|
export declare function useSubmission<T extends Array<any>, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U> | SubmissionStub;
|
|
12
|
-
export declare function useAction<T extends Array<any>, U>(action: Action<T, U>): (...args: Parameters<Action<T, U>>) => Promise<U
|
|
12
|
+
export declare function useAction<T extends Array<any>, U>(action: Action<T, U>): (...args: Parameters<Action<T, U>>) => Promise<NarrowResponse<U>>;
|
|
13
13
|
export declare function action<T extends Array<any>, U = void>(fn: (...args: T) => Promise<U>, name?: string): Action<T, U>;
|
package/dist/data/cache.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { CacheEntry } from "../types.js";
|
|
1
|
+
import type { CacheEntry, NarrowResponse } from "../types.js";
|
|
2
2
|
export declare function revalidate(key?: string | string[] | void, force?: boolean): Promise<void>;
|
|
3
3
|
export declare function cacheKeyOp(key: string | string[] | void, fn: (cacheEntry: CacheEntry) => void): void;
|
|
4
4
|
export type CachedFunction<T extends (...args: any) => any> = T extends (...args: infer A) => infer R ? ([] extends {
|
|
5
5
|
[K in keyof A]-?: A[K];
|
|
6
|
-
} ? (...args: never[]) => R :
|
|
6
|
+
} ? (...args: never[]) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R> : (...args: A) => R extends Promise<infer P> ? Promise<NarrowResponse<P>> : NarrowResponse<R>) & {
|
|
7
7
|
keyFor: (...args: A) => string;
|
|
8
8
|
key: string;
|
|
9
9
|
} : never;
|
package/dist/data/cache.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createSignal, getListener, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js";
|
|
2
2
|
import { getRequestEvent, isServer } from "solid-js/web";
|
|
3
|
-
import { useNavigate, getIntent,
|
|
3
|
+
import { useNavigate, getIntent, getInPreloadFn } from "../routing.js";
|
|
4
4
|
const LocationHeader = "Location";
|
|
5
5
|
const PRELOAD_TIMEOUT = 5000;
|
|
6
6
|
const CACHE_TIMEOUT = 180000;
|
|
@@ -47,7 +47,7 @@ export function cache(fn, name) {
|
|
|
47
47
|
const cachedFn = ((...args) => {
|
|
48
48
|
const cache = getCache();
|
|
49
49
|
const intent = getIntent();
|
|
50
|
-
const
|
|
50
|
+
const inPreloadFn = getInPreloadFn();
|
|
51
51
|
const owner = getOwner();
|
|
52
52
|
const navigate = owner ? useNavigate() : undefined;
|
|
53
53
|
const now = Date.now();
|
|
@@ -94,7 +94,7 @@ export function cache(fn, name) {
|
|
|
94
94
|
: handleResponse(false)(cached[1]);
|
|
95
95
|
!isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version
|
|
96
96
|
}
|
|
97
|
-
|
|
97
|
+
inPreloadFn && "then" in res && res.catch(() => { });
|
|
98
98
|
return res;
|
|
99
99
|
}
|
|
100
100
|
let res = !isServer && sharedConfig.context && sharedConfig.has(key)
|
|
@@ -125,7 +125,7 @@ export function cache(fn, name) {
|
|
|
125
125
|
? res.then(handleResponse(false), handleResponse(true))
|
|
126
126
|
: handleResponse(false)(res);
|
|
127
127
|
}
|
|
128
|
-
|
|
128
|
+
inPreloadFn && "then" in res && res.catch(() => { });
|
|
129
129
|
// serialize on server
|
|
130
130
|
if (isServer &&
|
|
131
131
|
sharedConfig.context &&
|
package/dist/data/events.js
CHANGED
|
@@ -111,10 +111,10 @@ export function setupNativeEvents(preload = true, explicitLinks = false, actionB
|
|
|
111
111
|
const handler = actions.get(actionRef);
|
|
112
112
|
if (handler) {
|
|
113
113
|
evt.preventDefault();
|
|
114
|
-
const data = new FormData(evt.target);
|
|
115
|
-
|
|
116
|
-
data
|
|
117
|
-
|
|
114
|
+
const data = new FormData(evt.target, evt.submitter);
|
|
115
|
+
handler.call({ r: router, f: evt.target }, evt.target.enctype === "multipart/form-data"
|
|
116
|
+
? data
|
|
117
|
+
: new URLSearchParams(data));
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
// ensure delegated event run first
|
package/dist/data/response.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function reload(init?: RouterResponseInit): never;
|
|
6
|
-
export declare function json<T>(data: T, init?: RouterResponseInit): T;
|
|
1
|
+
import type { RouterResponseInit, CustomResponse } from "../types";
|
|
2
|
+
export declare function redirect(url: string, init?: number | RouterResponseInit): CustomResponse<never>;
|
|
3
|
+
export declare function reload(init?: RouterResponseInit): CustomResponse<never>;
|
|
4
|
+
export declare function json<T>(data: T, init?: RouterResponseInit): CustomResponse<T>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from "./routers/index.js";
|
|
2
2
|
export * from "./components.jsx";
|
|
3
3
|
export * from "./lifecycle.js";
|
|
4
|
-
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing.js";
|
|
4
|
+
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute } from "./routing.js";
|
|
5
5
|
export { mergeSearchString as _mergeSearchString } from "./utils.js";
|
|
6
6
|
export * from "./data/index.js";
|
|
7
|
-
export type { Location, LocationChange, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps,
|
|
7
|
+
export type { Location, LocationChange, MatchFilter, MatchFilters, NavigateOptions, Navigator, OutputMatch, Params, PathMatch, RouteSectionProps, RoutePreloadFunc, RoutePreloadFuncArgs, RouteDefinition, RouteDescription, RouteMatch, RouterIntegration, RouterUtils, SetParams, BeforeLeaveEventArgs, RouteLoadFunc, RouteLoadFuncArgs, RouterResponseInit, CustomResponse } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -246,6 +246,7 @@ const useHref = to => {
|
|
|
246
246
|
const useNavigate = () => useRouter().navigatorFactory();
|
|
247
247
|
const useLocation = () => useRouter().location;
|
|
248
248
|
const useIsRouting = () => useRouter().isRouting;
|
|
249
|
+
const usePreloadRoute = () => useRouter().preloadRoute;
|
|
249
250
|
const useMatch = (path, matchFilters) => {
|
|
250
251
|
const location = useLocation();
|
|
251
252
|
const matchers = createMemo(() => expandOptionals(path()).map(path => createMatcher(path, undefined, matchFilters)));
|
|
@@ -282,6 +283,7 @@ const useBeforeLeave = listener => {
|
|
|
282
283
|
function createRoutes(routeDef, base = "") {
|
|
283
284
|
const {
|
|
284
285
|
component,
|
|
286
|
+
preload,
|
|
285
287
|
load,
|
|
286
288
|
children,
|
|
287
289
|
info
|
|
@@ -290,7 +292,7 @@ function createRoutes(routeDef, base = "") {
|
|
|
290
292
|
const shared = {
|
|
291
293
|
key: routeDef,
|
|
292
294
|
component,
|
|
293
|
-
load,
|
|
295
|
+
preload: preload || load,
|
|
294
296
|
info
|
|
295
297
|
};
|
|
296
298
|
return asArray(routeDef.path).reduce((acc, originalPath) => {
|
|
@@ -407,12 +409,12 @@ let intent;
|
|
|
407
409
|
function getIntent() {
|
|
408
410
|
return intent;
|
|
409
411
|
}
|
|
410
|
-
let
|
|
411
|
-
function
|
|
412
|
-
return
|
|
412
|
+
let inPreloadFn = false;
|
|
413
|
+
function getInPreloadFn() {
|
|
414
|
+
return inPreloadFn;
|
|
413
415
|
}
|
|
414
|
-
function
|
|
415
|
-
|
|
416
|
+
function setInPreloadFn(value) {
|
|
417
|
+
inPreloadFn = value;
|
|
416
418
|
}
|
|
417
419
|
function createRouterContext(integration, branches, getContext, options = {}) {
|
|
418
420
|
const {
|
|
@@ -593,10 +595,10 @@ function createRouterContext(integration, branches, getContext, options = {}) {
|
|
|
593
595
|
} = matches[match];
|
|
594
596
|
route.component && route.component.preload && route.component.preload();
|
|
595
597
|
const {
|
|
596
|
-
|
|
598
|
+
preload
|
|
597
599
|
} = route;
|
|
598
|
-
|
|
599
|
-
options.preloadData &&
|
|
600
|
+
inPreloadFn = true;
|
|
601
|
+
options.preloadData && preload && runWithOwner(getContext(), () => preload({
|
|
600
602
|
params,
|
|
601
603
|
location: {
|
|
602
604
|
pathname: url.pathname,
|
|
@@ -608,7 +610,7 @@ function createRouterContext(integration, branches, getContext, options = {}) {
|
|
|
608
610
|
},
|
|
609
611
|
intent: "preload"
|
|
610
612
|
}));
|
|
611
|
-
|
|
613
|
+
inPreloadFn = false;
|
|
612
614
|
}
|
|
613
615
|
intent = prevIntent;
|
|
614
616
|
}
|
|
@@ -626,17 +628,17 @@ function createRouteContext(router, parent, outlet, match) {
|
|
|
626
628
|
const {
|
|
627
629
|
pattern,
|
|
628
630
|
component,
|
|
629
|
-
|
|
631
|
+
preload
|
|
630
632
|
} = match().route;
|
|
631
633
|
const path = createMemo(() => match().path);
|
|
632
634
|
component && component.preload && component.preload();
|
|
633
|
-
|
|
634
|
-
const data =
|
|
635
|
+
inPreloadFn = true;
|
|
636
|
+
const data = preload ? preload({
|
|
635
637
|
params,
|
|
636
638
|
location,
|
|
637
639
|
intent: intent || "initial"
|
|
638
640
|
}) : undefined;
|
|
639
|
-
|
|
641
|
+
inPreloadFn = false;
|
|
640
642
|
const route = {
|
|
641
643
|
parent,
|
|
642
644
|
pattern,
|
|
@@ -677,8 +679,8 @@ const createRouterComponent = router => props => {
|
|
|
677
679
|
get root() {
|
|
678
680
|
return props.root;
|
|
679
681
|
},
|
|
680
|
-
get
|
|
681
|
-
return props.rootLoad;
|
|
682
|
+
get preload() {
|
|
683
|
+
return props.rootPreload || props.rootLoad;
|
|
682
684
|
},
|
|
683
685
|
get children() {
|
|
684
686
|
return [memo(() => (context = getOwner()) && null), createComponent$1(Routes, {
|
|
@@ -695,14 +697,14 @@ const createRouterComponent = router => props => {
|
|
|
695
697
|
function Root(props) {
|
|
696
698
|
const location = props.routerState.location;
|
|
697
699
|
const params = props.routerState.params;
|
|
698
|
-
const data = createMemo(() => props.
|
|
699
|
-
|
|
700
|
-
props.
|
|
700
|
+
const data = createMemo(() => props.preload && untrack(() => {
|
|
701
|
+
setInPreloadFn(true);
|
|
702
|
+
props.preload({
|
|
701
703
|
params,
|
|
702
704
|
location,
|
|
703
705
|
intent: getIntent() || "initial"
|
|
704
706
|
});
|
|
705
|
-
|
|
707
|
+
setInPreloadFn(false);
|
|
706
708
|
}));
|
|
707
709
|
return createComponent$1(Show, {
|
|
708
710
|
get when() {
|
|
@@ -807,7 +809,7 @@ function dataOnly(event, routerState, branches) {
|
|
|
807
809
|
route,
|
|
808
810
|
params
|
|
809
811
|
} = matches[match];
|
|
810
|
-
route.
|
|
812
|
+
route.preload && route.preload({
|
|
811
813
|
params,
|
|
812
814
|
location: routerState.location,
|
|
813
815
|
intent: "preload"
|
|
@@ -922,7 +924,7 @@ function cache(fn, name) {
|
|
|
922
924
|
const cachedFn = (...args) => {
|
|
923
925
|
const cache = getCache();
|
|
924
926
|
const intent = getIntent();
|
|
925
|
-
const
|
|
927
|
+
const inPreloadFn = getInPreloadFn();
|
|
926
928
|
const owner = getOwner();
|
|
927
929
|
const navigate = owner ? useNavigate() : undefined;
|
|
928
930
|
const now = Date.now();
|
|
@@ -960,7 +962,7 @@ function cache(fn, name) {
|
|
|
960
962
|
res = "then" in cached[1] ? cached[1].then(handleResponse(false), handleResponse(true)) : handleResponse(false)(cached[1]);
|
|
961
963
|
!isServer && intent === "navigate" && startTransition(() => cached[3][1](cached[0])); // update version
|
|
962
964
|
}
|
|
963
|
-
|
|
965
|
+
inPreloadFn && "then" in res && res.catch(() => {});
|
|
964
966
|
return res;
|
|
965
967
|
}
|
|
966
968
|
let res = !isServer && sharedConfig.context && sharedConfig.has(key) ? sharedConfig.load(key) // hydrating
|
|
@@ -985,7 +987,7 @@ function cache(fn, name) {
|
|
|
985
987
|
if (intent !== "preload") {
|
|
986
988
|
res = "then" in res ? res.then(handleResponse(false), handleResponse(true)) : handleResponse(false)(res);
|
|
987
989
|
}
|
|
988
|
-
|
|
990
|
+
inPreloadFn && "then" in res && res.catch(() => {});
|
|
989
991
|
// serialize on server
|
|
990
992
|
if (isServer && sharedConfig.context && sharedConfig.context.async && !sharedConfig.context.noHydrate) {
|
|
991
993
|
const e = getRequestEvent();
|
|
@@ -1277,12 +1279,11 @@ function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "
|
|
|
1277
1279
|
const handler = actions.get(actionRef);
|
|
1278
1280
|
if (handler) {
|
|
1279
1281
|
evt.preventDefault();
|
|
1280
|
-
const data = new FormData(evt.target);
|
|
1281
|
-
if (evt.submitter && evt.submitter.name) data.append(evt.submitter.name, evt.submitter.value);
|
|
1282
|
+
const data = new FormData(evt.target, evt.submitter);
|
|
1282
1283
|
handler.call({
|
|
1283
1284
|
r: router,
|
|
1284
1285
|
f: evt.target
|
|
1285
|
-
}, data);
|
|
1286
|
+
}, evt.target.enctype === "multipart/form-data" ? data : new URLSearchParams(data));
|
|
1286
1287
|
}
|
|
1287
1288
|
}
|
|
1288
1289
|
|
|
@@ -1312,7 +1313,7 @@ function setupNativeEvents(preload = true, explicitLinks = false, actionBase = "
|
|
|
1312
1313
|
function Router(props) {
|
|
1313
1314
|
if (isServer) return StaticRouter(props);
|
|
1314
1315
|
const getSource = () => {
|
|
1315
|
-
const url = window.location.pathname + window.location.search;
|
|
1316
|
+
const url = window.location.pathname.replace(/^\/+/, "/") + window.location.search;
|
|
1316
1317
|
return {
|
|
1317
1318
|
value: props.transformUrl ? props.transformUrl(url) + window.location.hash : url + window.location.hash,
|
|
1318
1319
|
state: window.history.state
|
|
@@ -1643,4 +1644,4 @@ function json(data, init = {}) {
|
|
|
1643
1644
|
return response;
|
|
1644
1645
|
}
|
|
1645
1646
|
|
|
1646
|
-
export { A, HashRouter, MemoryRouter, Navigate, Route, Router, StaticRouter, mergeSearchString as _mergeSearchString, action, cache, createAsync, createAsyncStore, createBeforeLeave, createMemoryHistory, createRouter, json, keepDepth, notifyIfNotBlocked, redirect, reload, revalidate, saveCurrentDepth, useAction, useBeforeLeave, useCurrentMatches, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
|
|
1647
|
+
export { A, HashRouter, MemoryRouter, Navigate, Route, Router, StaticRouter, mergeSearchString as _mergeSearchString, action, cache, createAsync, createAsyncStore, createBeforeLeave, createMemoryHistory, createRouter, json, keepDepth, notifyIfNotBlocked, redirect, reload, revalidate, saveCurrentDepth, useAction, useBeforeLeave, useCurrentMatches, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, usePreloadRoute, useResolvedPath, useSearchParams, useSubmission, useSubmissions };
|
package/dist/index.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "./routers/index.js";
|
|
2
2
|
export * from "./components.jsx";
|
|
3
3
|
export * from "./lifecycle.js";
|
|
4
|
-
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing.js";
|
|
4
|
+
export { useHref, useIsRouting, useLocation, useMatch, useCurrentMatches, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, usePreloadRoute } from "./routing.js";
|
|
5
5
|
export { mergeSearchString as _mergeSearchString } from "./utils.js";
|
|
6
6
|
export * from "./data/index.js";
|
package/dist/routers/Router.js
CHANGED
|
@@ -7,7 +7,7 @@ export function Router(props) {
|
|
|
7
7
|
if (isServer)
|
|
8
8
|
return StaticRouter(props);
|
|
9
9
|
const getSource = () => {
|
|
10
|
-
const url = window.location.pathname + window.location.search;
|
|
10
|
+
const url = window.location.pathname.replace(/^\/+/, "/") + window.location.search;
|
|
11
11
|
return {
|
|
12
12
|
value: props.transformUrl ? props.transformUrl(url) + window.location.hash : url + window.location.hash,
|
|
13
13
|
state: window.history.state
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import type { Component, JSX } from "solid-js";
|
|
2
|
-
import type { MatchFilters,
|
|
2
|
+
import type { MatchFilters, RouteDefinition, RouterIntegration, RouteSectionProps, RoutePreloadFunc } from "../types.js";
|
|
3
3
|
export type BaseRouterProps = {
|
|
4
4
|
base?: string;
|
|
5
5
|
/**
|
|
6
6
|
* A component that wraps the content of every route.
|
|
7
7
|
*/
|
|
8
8
|
root?: Component<RouteSectionProps>;
|
|
9
|
-
|
|
9
|
+
rootPreload?: RoutePreloadFunc;
|
|
10
10
|
singleFlight?: boolean;
|
|
11
11
|
children?: JSX.Element | RouteDefinition | RouteDefinition[];
|
|
12
12
|
transformUrl?: (url: string) => string;
|
|
13
|
+
/** @deprecated use rootPreload */
|
|
14
|
+
rootLoad?: RoutePreloadFunc;
|
|
13
15
|
};
|
|
14
16
|
export declare const createRouterComponent: (router: RouterIntegration) => (props: BaseRouterProps) => JSX.Element;
|
|
15
17
|
export type RouteProps<S extends string, T = unknown> = {
|
|
16
18
|
path?: S | S[];
|
|
17
19
|
children?: JSX.Element;
|
|
18
|
-
|
|
20
|
+
preload?: RoutePreloadFunc<T>;
|
|
19
21
|
matchFilters?: MatchFilters<S>;
|
|
20
22
|
component?: Component<RouteSectionProps<T>>;
|
|
21
23
|
info?: Record<string, any>;
|
|
24
|
+
/** @deprecated use preload */
|
|
25
|
+
load?: RoutePreloadFunc<T>;
|
|
22
26
|
};
|
|
23
27
|
export declare const Route: <S extends string, T = unknown>(props: RouteProps<S, T>) => JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*@refresh skip*/
|
|
2
2
|
import { getRequestEvent, isServer } from "solid-js/web";
|
|
3
3
|
import { children, createMemo, createRoot, getOwner, mergeProps, on, Show, untrack } from "solid-js";
|
|
4
|
-
import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj,
|
|
4
|
+
import { createBranches, createRouteContext, createRouterContext, getIntent, getRouteMatches, RouteContextObj, RouterContextObj, setInPreloadFn } from "../routing.js";
|
|
5
5
|
export const createRouterComponent = (router) => (props) => {
|
|
6
6
|
const { base } = props;
|
|
7
7
|
const routeDefs = children(() => props.children);
|
|
@@ -14,7 +14,7 @@ export const createRouterComponent = (router) => (props) => {
|
|
|
14
14
|
});
|
|
15
15
|
router.create && router.create(routerState);
|
|
16
16
|
return (<RouterContextObj.Provider value={routerState}>
|
|
17
|
-
<Root routerState={routerState} root={props.root}
|
|
17
|
+
<Root routerState={routerState} root={props.root} preload={props.rootPreload || props.rootLoad}>
|
|
18
18
|
{(context = getOwner()) && null}
|
|
19
19
|
<Routes routerState={routerState} branches={branches()}/>
|
|
20
20
|
</Root>
|
|
@@ -23,11 +23,11 @@ export const createRouterComponent = (router) => (props) => {
|
|
|
23
23
|
function Root(props) {
|
|
24
24
|
const location = props.routerState.location;
|
|
25
25
|
const params = props.routerState.params;
|
|
26
|
-
const data = createMemo(() => props.
|
|
26
|
+
const data = createMemo(() => props.preload &&
|
|
27
27
|
untrack(() => {
|
|
28
|
-
|
|
29
|
-
props.
|
|
30
|
-
|
|
28
|
+
setInPreloadFn(true);
|
|
29
|
+
props.preload({ params, location, intent: getIntent() || "initial" });
|
|
30
|
+
setInPreloadFn(false);
|
|
31
31
|
}));
|
|
32
32
|
return (<Show when={props.root} keyed fallback={props.children}>
|
|
33
33
|
{Root => (<Root params={params} location={location} data={data()}>
|
|
@@ -105,8 +105,8 @@ function dataOnly(event, routerState, branches) {
|
|
|
105
105
|
if (!prevMatches[match] || matches[match].route !== prevMatches[match].route)
|
|
106
106
|
event.router.dataOnly = true;
|
|
107
107
|
const { route, params } = matches[match];
|
|
108
|
-
route.
|
|
109
|
-
route.
|
|
108
|
+
route.preload &&
|
|
109
|
+
route.preload({
|
|
110
110
|
params,
|
|
111
111
|
location: routerState.location,
|
|
112
112
|
intent: "preload"
|
package/dist/routing.d.ts
CHANGED
|
@@ -23,8 +23,8 @@ export declare function createBranches(routeDef: RouteDefinition | RouteDefiniti
|
|
|
23
23
|
export declare function getRouteMatches(branches: Branch[], location: string): RouteMatch[];
|
|
24
24
|
export declare function createLocation(path: Accessor<string>, state: Accessor<any>): Location;
|
|
25
25
|
export declare function getIntent(): Intent | undefined;
|
|
26
|
-
export declare function
|
|
27
|
-
export declare function
|
|
26
|
+
export declare function getInPreloadFn(): boolean;
|
|
27
|
+
export declare function setInPreloadFn(value: boolean): void;
|
|
28
28
|
export declare function createRouterContext(integration: RouterIntegration, branches: () => Branch[], getContext?: () => any, options?: {
|
|
29
29
|
base?: string;
|
|
30
30
|
singleFlight?: boolean;
|
package/dist/routing.js
CHANGED
|
@@ -59,12 +59,12 @@ export const useBeforeLeave = (listener) => {
|
|
|
59
59
|
onCleanup(s);
|
|
60
60
|
};
|
|
61
61
|
export function createRoutes(routeDef, base = "") {
|
|
62
|
-
const { component, load, children, info } = routeDef;
|
|
62
|
+
const { component, preload, load, children, info } = routeDef;
|
|
63
63
|
const isLeaf = !children || (Array.isArray(children) && !children.length);
|
|
64
64
|
const shared = {
|
|
65
65
|
key: routeDef,
|
|
66
66
|
component,
|
|
67
|
-
load,
|
|
67
|
+
preload: preload || load,
|
|
68
68
|
info
|
|
69
69
|
};
|
|
70
70
|
return asArray(routeDef.path).reduce((acc, originalPath) => {
|
|
@@ -186,12 +186,12 @@ let intent;
|
|
|
186
186
|
export function getIntent() {
|
|
187
187
|
return intent;
|
|
188
188
|
}
|
|
189
|
-
let
|
|
190
|
-
export function
|
|
191
|
-
return
|
|
189
|
+
let inPreloadFn = false;
|
|
190
|
+
export function getInPreloadFn() {
|
|
191
|
+
return inPreloadFn;
|
|
192
192
|
}
|
|
193
|
-
export function
|
|
194
|
-
|
|
193
|
+
export function setInPreloadFn(value) {
|
|
194
|
+
inPreloadFn = value;
|
|
195
195
|
}
|
|
196
196
|
export function createRouterContext(integration, branches, getContext, options = {}) {
|
|
197
197
|
const { signal: [source, setSource], utils = {} } = integration;
|
|
@@ -350,11 +350,11 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
350
350
|
route.component &&
|
|
351
351
|
route.component.preload &&
|
|
352
352
|
route.component.preload();
|
|
353
|
-
const {
|
|
354
|
-
|
|
353
|
+
const { preload } = route;
|
|
354
|
+
inPreloadFn = true;
|
|
355
355
|
options.preloadData &&
|
|
356
|
-
|
|
357
|
-
runWithOwner(getContext(), () =>
|
|
356
|
+
preload &&
|
|
357
|
+
runWithOwner(getContext(), () => preload({
|
|
358
358
|
params,
|
|
359
359
|
location: {
|
|
360
360
|
pathname: url.pathname,
|
|
@@ -366,7 +366,7 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
366
366
|
},
|
|
367
367
|
intent: "preload"
|
|
368
368
|
}));
|
|
369
|
-
|
|
369
|
+
inPreloadFn = false;
|
|
370
370
|
}
|
|
371
371
|
intent = prevIntent;
|
|
372
372
|
}
|
|
@@ -377,14 +377,14 @@ export function createRouterContext(integration, branches, getContext, options =
|
|
|
377
377
|
}
|
|
378
378
|
export function createRouteContext(router, parent, outlet, match) {
|
|
379
379
|
const { base, location, params } = router;
|
|
380
|
-
const { pattern, component,
|
|
380
|
+
const { pattern, component, preload } = match().route;
|
|
381
381
|
const path = createMemo(() => match().path);
|
|
382
382
|
component &&
|
|
383
383
|
component.preload &&
|
|
384
384
|
component.preload();
|
|
385
|
-
|
|
386
|
-
const data =
|
|
387
|
-
|
|
385
|
+
inPreloadFn = true;
|
|
386
|
+
const data = preload ? preload({ params, location, intent: intent || "initial" }) : undefined;
|
|
387
|
+
inPreloadFn = false;
|
|
388
388
|
const route = {
|
|
389
389
|
parent,
|
|
390
390
|
pattern,
|
package/dist/types.d.ts
CHANGED
|
@@ -56,12 +56,12 @@ export interface RouterIntegration {
|
|
|
56
56
|
utils?: Partial<RouterUtils>;
|
|
57
57
|
}
|
|
58
58
|
export type Intent = "initial" | "native" | "navigate" | "preload";
|
|
59
|
-
export interface
|
|
59
|
+
export interface RoutePreloadFuncArgs {
|
|
60
60
|
params: Params;
|
|
61
61
|
location: Location;
|
|
62
62
|
intent: Intent;
|
|
63
63
|
}
|
|
64
|
-
export type
|
|
64
|
+
export type RoutePreloadFunc<T = unknown> = (args: RoutePreloadFuncArgs) => T;
|
|
65
65
|
export interface RouteSectionProps<T = unknown> {
|
|
66
66
|
params: Params;
|
|
67
67
|
location: Location;
|
|
@@ -71,10 +71,12 @@ export interface RouteSectionProps<T = unknown> {
|
|
|
71
71
|
export type RouteDefinition<S extends string | string[] = any, T = unknown> = {
|
|
72
72
|
path?: S;
|
|
73
73
|
matchFilters?: MatchFilters<S>;
|
|
74
|
-
|
|
74
|
+
preload?: RoutePreloadFunc<T>;
|
|
75
75
|
children?: RouteDefinition | RouteDefinition[];
|
|
76
76
|
component?: Component<RouteSectionProps<T>>;
|
|
77
77
|
info?: Record<string, any>;
|
|
78
|
+
/** @deprecated use preload */
|
|
79
|
+
load?: RoutePreloadFunc;
|
|
78
80
|
};
|
|
79
81
|
export type MatchFilter = readonly string[] | RegExp | ((s: string) => boolean);
|
|
80
82
|
export type PathParams<P extends string | readonly string[]> = P extends `${infer Head}/${infer Tail}` ? [...PathParams<Head>, ...PathParams<Tail>] : P extends `:${infer S}?` ? [S] : P extends `:${infer S}` ? [S] : P extends `*${infer S}` ? [S] : [];
|
|
@@ -100,7 +102,7 @@ export interface RouteDescription {
|
|
|
100
102
|
originalPath: string;
|
|
101
103
|
pattern: string;
|
|
102
104
|
component?: Component<RouteSectionProps>;
|
|
103
|
-
|
|
105
|
+
preload?: RoutePreloadFunc;
|
|
104
106
|
matcher: (location: string) => PathMatch | null;
|
|
105
107
|
matchFilters?: MatchFilters;
|
|
106
108
|
info?: Record<string, any>;
|
|
@@ -181,3 +183,15 @@ export interface MaybePreloadableComponent extends Component {
|
|
|
181
183
|
export type CacheEntry = [number, any, Intent | undefined, Signal<number> & {
|
|
182
184
|
count: number;
|
|
183
185
|
}];
|
|
186
|
+
export type NarrowResponse<T> = T extends CustomResponse<infer U> ? U : Exclude<T, Response>;
|
|
187
|
+
export type RouterResponseInit = Omit<ResponseInit, "body"> & {
|
|
188
|
+
revalidate?: string | string[];
|
|
189
|
+
};
|
|
190
|
+
export type CustomResponse<T> = Omit<Response, "clone"> & {
|
|
191
|
+
customBody: () => T;
|
|
192
|
+
clone(...args: readonly unknown[]): CustomResponse<T>;
|
|
193
|
+
};
|
|
194
|
+
/** @deprecated */
|
|
195
|
+
export type RouteLoadFunc = RoutePreloadFunc;
|
|
196
|
+
/** @deprecated */
|
|
197
|
+
export type RouteLoadFuncArgs = RoutePreloadFuncArgs;
|