@solidjs/router 0.10.1 → 0.10.3

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 CHANGED
@@ -350,8 +350,53 @@ You can nest indefinitely - just remember that only leaf nodes will become their
350
350
  </Route>
351
351
  ```
352
352
 
353
+ ## Load Functions
354
+
355
+ Even with smart caches 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. The load function is called when the Route is loaded or eagerly when links are hovered.
356
+
357
+ As its only argument, the load function is passed an object that you can use to access route information:
358
+
359
+ ```js
360
+ import { lazy } from "solid-js";
361
+ import { Route } from "@solidjs/router";
362
+
363
+ const User = lazy(() => import("./pages/users/[id].js"));
364
+
365
+ // load function
366
+ function loadUser({params, location}) {
367
+ // do loading
368
+ }
369
+
370
+ // Pass it in the route definition
371
+ <Route path="/users/:id" component={User} load={loadUser} />;
372
+ ```
373
+
374
+ | key | type | description |
375
+ | -------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
376
+ | params | object | The route parameters (same value as calling `useParams()` inside the route component) |
377
+ | location | `{ pathname, search, hash, query, state, key}` | An object that you can use to get more information about the path (corresponds to [`useLocation()`](#uselocation)) |
378
+ | 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> |
379
+
380
+
381
+ A common pattern is to export the load 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.
382
+
383
+ ```js
384
+ import { lazy } from "solid-js";
385
+ import { Route } from "@solidjs/router";
386
+ import loadUser from "./pages/users/[id].data.js";
387
+ const User = lazy(() => import("/pages/users/[id].js"));
388
+
389
+ // In the Route definition
390
+ <Route path="/users/:id" component={User} load={loadUser} />;
391
+ ```
392
+
393
+ The return value of the `load` function is passed to the page component when called at anytime other than `"preload"`, so you can initialize things in there, or alternatively use our new Data APIs:
394
+
395
+
353
396
  ## Data APIs
354
397
 
398
+ Keep in mind these are completely optional. To use but showcase the power of our load mechanism.
399
+
355
400
  ### `cache`
356
401
 
357
402
  To prevent duplicate fetching and to trigger handle refetching we provide a cache api. That takes a function and returns the same function.
@@ -370,6 +415,36 @@ This cache accomplishes the following:
370
415
  3. We have a reactive refetch mechanism based on key. So we can tell routes that aren't new to retrigger on action revalidation.
371
416
  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.
372
417
 
418
+ Using it with load function might look like:
419
+
420
+ ```js
421
+ import { lazy } from "solid-js";
422
+ import { Route } from "@solidjs/router";
423
+ import { getUser } from ... // the cache function
424
+
425
+ const User = lazy(() => import("./pages/users/[id].js"));
426
+
427
+ // load function
428
+ function loadUser({params, location}) {
429
+ void getUser(params.id)
430
+ }
431
+
432
+ // Pass it in the route definition
433
+ <Route path="/users/:id" component={User} load={loadUser} />;
434
+ ```
435
+
436
+ Inside your page component you:
437
+
438
+ ```jsx
439
+ // pages/users/[id].js
440
+ import { getUser } from ... // the cache function
441
+
442
+ export default function User(props) {
443
+ const user = createAsync(() => getUser(props.params.id));
444
+ return <h1>{user().name}</h1>;
445
+ }
446
+ ```
447
+
373
448
  Cached function has a few useful methods for getting the key that are useful for invalidation.
374
449
  ```ts
375
450
  let id = 5;
@@ -423,16 +498,16 @@ const deleteTodo = action(async (formData: FormData) => {
423
498
  await api.deleteTodo(id)
424
499
  })
425
500
 
426
- <form action={deleteUser} method="post">
501
+ <form action={deleteTodo} method="post">
427
502
  <input type="hidden" name="id" value={todo.id} />
428
503
  <button type="submit">Delete</button>
429
504
  </form>
430
505
  ```
431
506
  Instead with `with` you can write this:
432
507
  ```js
433
- const deleteUser = action(api.deleteUser)
508
+ const deleteUser = action(api.deleteTodo)
434
509
 
435
- <form action={deleteUser.with(todo.id)} method="post">
510
+ <form action={deleteTodo.with(todo.id)} method="post">
436
511
  <button type="submit">Delete</button>
437
512
  </form>
438
513
  ```
@@ -504,59 +579,6 @@ const updateTodo = action(async (todo: Todo) => {
504
579
  })
505
580
  ```
506
581
 
507
- ### Load Functions
508
-
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.
510
-
511
- To do this, we can call our cache function in the load function.
512
-
513
- ```js
514
- import { lazy } from "solid-js";
515
- import { Route } from "@solidjs/router";
516
- import { getUser } from ... // the cache function
517
-
518
- const User = lazy(() => import("./pages/users/[id].js"));
519
-
520
- // load function
521
- function loadUser({params, location}) {
522
- void getUser(params.id)
523
- }
524
-
525
- // Pass it in the route definition
526
- <Route path="/users/:id" component={User} load={loadUser} />;
527
- ```
528
-
529
- The load function is called when the Route is loaded or eagerly when links are hovered. Inside your page component you:
530
-
531
- ```jsx
532
- // pages/users/[id].js
533
- import { getUser } from ... // the cache function
534
-
535
- export default function User(props) {
536
- const user = createAsync(() => getUser(props.params.id));
537
- return <h1>{user().name}</h1>;
538
- }
539
- ```
540
-
541
- As its only argument, the load function is passed an object that you can use to access route information:
542
-
543
- | key | type | description |
544
- | -------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
545
- | params | object | The route parameters (same value as calling `useParams()` inside the route component) |
546
- | location | `{ pathname, search, hash, query, state, key}` | An object that you can use to get more information about the path (corresponds to [`useLocation()`](#uselocation)) |
547
-
548
- 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.
549
-
550
- ```js
551
- import { lazy } from "solid-js";
552
- import { Route } from "@solidjs/router";
553
- import loadUser from "./pages/users/[id].data.js";
554
- const User = lazy(() => import("/pages/users/[id].js"));
555
-
556
- // In the Route definition
557
- <Route path="/users/:id" component={User} load={loadUser} />;
558
- ```
559
-
560
582
  ## Config Based Routing
561
583
 
562
584
  You don't have to use JSX to set up your routes; you can pass an object:
@@ -834,6 +856,41 @@ Related without Outlet component it has to be passed in manually. At which point
834
856
 
835
857
  These have been replaced by a load mechanism. This allows link hover preloads (as the load 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.
836
858
 
859
+ That being said you can reproduce the old pattern largely by turning off preloads at the router level and then injecting your own Context:
860
+
861
+ ```js
862
+ import { lazy } from "solid-js";
863
+ import { Route } from "@solidjs/router";
864
+
865
+ const User = lazy(() => import("./pages/users/[id].js"));
866
+
867
+ // load function
868
+ function loadUser({params, location}) {
869
+ const [user] = createResource(() => params.id, fetchUser);
870
+ return user;
871
+ }
872
+
873
+ // Pass it in the route definition
874
+ <Router preload={false}>
875
+ <Route path="/users/:id" component={User} load={loadUser} />
876
+ </Router>
877
+ ```
878
+
879
+ And then in your component taking the page props and putting them in a Context.
880
+ ```js
881
+ function User(props) {
882
+ <UserContext.Provider value={props.data}>
883
+ {/* my component content */}
884
+ </UserContext.Provider>
885
+ }
886
+
887
+ // Somewhere else
888
+ function UserDetails() {
889
+ const user = useContext(UserContext)
890
+ // render stuff
891
+ }
892
+ ```
893
+
837
894
  ## SPAs in Deployed Environments
838
895
 
839
896
  When deploying applications that use a client side router that does not rely on Server Side Rendering you need to handle redirects to your index page so that loading from other URLs does not cause your CDN or Hosting to return not found for pages that aren't actually there.
package/dist/index.d.ts CHANGED
@@ -4,4 +4,4 @@ export * from "./lifecycle";
4
4
  export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
5
5
  export { mergeSearchString as _mergeSearchString } from "./utils";
6
6
  export * from "./data";
7
- export type { Location, LocationChange, NavigateOptions, Navigator, OutputMatch, Params, RouteSectionProps, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
7
+ export type { Location, LocationChange, NavigateOptions, Navigator, OutputMatch, Params, RouteSectionProps, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
package/dist/index.js CHANGED
@@ -239,13 +239,15 @@ function createRoutes(routeDef, base = "") {
239
239
  const {
240
240
  component,
241
241
  load,
242
- children
242
+ children,
243
+ metadata
243
244
  } = routeDef;
244
245
  const isLeaf = !children || Array.isArray(children) && !children.length;
245
246
  const shared = {
246
247
  key: routeDef,
247
248
  component,
248
- load
249
+ load,
250
+ metadata
249
251
  };
250
252
  return asArray(routeDef.path).reduce((acc, path) => {
251
253
  for (const originalPath of expandOptionals(path)) {
@@ -559,6 +561,12 @@ function createRouteContext(router, parent, outlet, match, params) {
559
561
  load
560
562
  } = match().route;
561
563
  const path = createMemo(() => match().path);
564
+ component && component.preload && component.preload();
565
+ const data = load ? load({
566
+ params,
567
+ location,
568
+ intent: intent || "initial"
569
+ }) : undefined;
562
570
  const route = {
563
571
  parent,
564
572
  pattern,
@@ -567,6 +575,7 @@ function createRouteContext(router, parent, outlet, match, params) {
567
575
  outlet: () => component ? createComponent(component, {
568
576
  params,
569
577
  location,
578
+ data,
570
579
  get children() {
571
580
  return outlet();
572
581
  }
@@ -575,12 +584,6 @@ function createRouteContext(router, parent, outlet, match, params) {
575
584
  return resolvePath(base.path(), to, path());
576
585
  }
577
586
  };
578
- component && component.preload && component.preload();
579
- load && load({
580
- params,
581
- location,
582
- intent: intent || "initial"
583
- });
584
587
  return route;
585
588
  }
586
589
 
@@ -611,6 +614,20 @@ const createRouterComponent = router => props => {
611
614
  };
612
615
  function Routes(props) {
613
616
  const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
617
+ if (isServer) {
618
+ const e = getRequestEvent();
619
+ e && (e.routerMatches || (e.routerMatches = [])).push(matches().map(({
620
+ route,
621
+ path,
622
+ params
623
+ }) => ({
624
+ path: route.originalPath,
625
+ pattern: route.pattern,
626
+ match: path,
627
+ params,
628
+ metadata: route.metadata
629
+ })));
630
+ }
614
631
  const params = createMemoObject(() => {
615
632
  const m = matches();
616
633
  const params = {};
@@ -1,9 +1,9 @@
1
1
  import type { Component, JSX } from "solid-js";
2
- import type { MatchFilters, RouteLoadFunc, RouterIntegration, RouteSectionProps } from "../types";
2
+ import type { MatchFilters, RouteLoadFunc, RouteDefinition, RouterIntegration, RouteSectionProps } from "../types";
3
3
  export type BaseRouterProps = {
4
4
  base?: string;
5
5
  root?: Component<RouteSectionProps>;
6
- children?: JSX.Element;
6
+ children?: JSX.Element | RouteDefinition | RouteDefinition[];
7
7
  };
8
8
  export declare const createRouterComponent: (router: RouterIntegration) => (props: BaseRouterProps) => JSX.Element;
9
9
  export type RouteProps<S extends string> = {
@@ -12,5 +12,6 @@ export type RouteProps<S extends string> = {
12
12
  load?: RouteLoadFunc;
13
13
  matchFilters?: MatchFilters<S>;
14
14
  component?: Component;
15
+ metadata?: Record<string, any>;
15
16
  };
16
17
  export declare const Route: <S extends string>(props: RouteProps<S>) => JSX.Element;
@@ -1,4 +1,5 @@
1
1
  /*@refresh skip*/
2
+ import { getRequestEvent, isServer } from "solid-js/web";
2
3
  import { children, createMemo, createRoot, mergeProps, on, Show } from "solid-js";
3
4
  import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj } from "../routing";
4
5
  import { createMemoObject } from "../utils";
@@ -14,6 +15,17 @@ export const createRouterComponent = (router) => (props) => {
14
15
  };
15
16
  function Routes(props) {
16
17
  const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
18
+ if (isServer) {
19
+ const e = getRequestEvent();
20
+ e &&
21
+ (e.routerMatches || (e.routerMatches = [])).push(matches().map(({ route, path, params }) => ({
22
+ path: route.originalPath,
23
+ pattern: route.pattern,
24
+ match: path,
25
+ params,
26
+ metadata: route.metadata
27
+ })));
28
+ }
17
29
  const params = createMemoObject(() => {
18
30
  const m = matches();
19
31
  const params = {};
package/dist/routing.js CHANGED
@@ -56,12 +56,13 @@ export const useBeforeLeave = (listener) => {
56
56
  onCleanup(s);
57
57
  };
58
58
  export function createRoutes(routeDef, base = "") {
59
- const { component, load, children } = routeDef;
59
+ const { component, load, children, metadata } = routeDef;
60
60
  const isLeaf = !children || (Array.isArray(children) && !children.length);
61
61
  const shared = {
62
62
  key: routeDef,
63
63
  component,
64
- load
64
+ load,
65
+ metadata
65
66
  };
66
67
  return asArray(routeDef.path).reduce((acc, path) => {
67
68
  for (const originalPath of expandOptionals(path)) {
@@ -350,6 +351,10 @@ export function createRouteContext(router, parent, outlet, match, params) {
350
351
  const { base, location } = router;
351
352
  const { pattern, component, load } = match().route;
352
353
  const path = createMemo(() => match().path);
354
+ component &&
355
+ component.preload &&
356
+ component.preload();
357
+ const data = load ? load({ params, location, intent: intent || "initial" }) : undefined;
353
358
  const route = {
354
359
  parent,
355
360
  pattern,
@@ -359,6 +364,7 @@ export function createRouteContext(router, parent, outlet, match, params) {
359
364
  ? createComponent(component, {
360
365
  params,
361
366
  location,
367
+ data,
362
368
  get children() {
363
369
  return outlet();
364
370
  }
@@ -368,9 +374,5 @@ export function createRouteContext(router, parent, outlet, match, params) {
368
374
  return resolvePath(base.path(), to, path());
369
375
  }
370
376
  };
371
- component &&
372
- component.preload &&
373
- component.preload();
374
- load && load({ params, location, intent: intent || "initial" });
375
377
  return route;
376
378
  }
package/dist/types.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { Component, JSX, Signal } from "solid-js";
2
2
  declare module "solid-js/web" {
3
3
  interface RequestEvent {
4
4
  response?: Response;
5
+ routerMatches?: OutputMatch[][];
5
6
  routerCache?: Map<any, any>;
6
7
  initialSubmission?: Submission<any, any>;
7
8
  serverOnly?: boolean;
@@ -47,18 +48,20 @@ export interface RouteLoadFuncArgs {
47
48
  location: Location;
48
49
  intent: Intent;
49
50
  }
50
- export type RouteLoadFunc = (args: RouteLoadFuncArgs) => void;
51
- export interface RouteSectionProps {
51
+ export type RouteLoadFunc<T = unknown> = (args: RouteLoadFuncArgs) => T;
52
+ export interface RouteSectionProps<T = unknown> {
52
53
  params: Params;
53
54
  location: Location;
55
+ data?: T;
54
56
  children?: JSX.Element;
55
57
  }
56
- export type RouteDefinition<S extends string | string[] = any> = {
58
+ export type RouteDefinition<S extends string | string[] = any, T = unknown> = {
57
59
  path?: S;
58
60
  matchFilters?: MatchFilters<S>;
59
- load?: RouteLoadFunc;
61
+ load?: RouteLoadFunc<T>;
60
62
  children?: RouteDefinition | RouteDefinition[];
61
- component?: Component<RouteSectionProps>;
63
+ component?: Component<RouteSectionProps<T>>;
64
+ metadata?: Record<string, any>;
62
65
  };
63
66
  export type MatchFilter = readonly string[] | RegExp | ((s: string) => boolean);
64
67
  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] : [];
@@ -73,10 +76,11 @@ export interface RouteMatch extends PathMatch {
73
76
  route: Route;
74
77
  }
75
78
  export interface OutputMatch {
76
- originalPath: string;
77
- pattern: string;
78
79
  path: string;
80
+ pattern: string;
81
+ match: string;
79
82
  params: Params;
83
+ metadata?: Record<string, any>;
80
84
  }
81
85
  export interface Route {
82
86
  key: unknown;
@@ -86,6 +90,7 @@ export interface Route {
86
90
  load?: RouteLoadFunc;
87
91
  matcher: (location: string) => PathMatch | null;
88
92
  matchFilters?: MatchFilters;
93
+ metadata?: Record<string, any>;
89
94
  }
90
95
  export interface Branch {
91
96
  routes: Route[];
@@ -107,16 +112,6 @@ export interface RouterUtils {
107
112
  go(delta: number): void;
108
113
  beforeLeave: BeforeLeaveLifecycle;
109
114
  }
110
- export interface OutputMatch {
111
- originalPath: string;
112
- pattern: string;
113
- path: string;
114
- params: Params;
115
- }
116
- export interface RouterOutput {
117
- url?: string;
118
- matches: OutputMatch[][];
119
- }
120
115
  export interface RouterContext {
121
116
  base: RouteContext;
122
117
  location: Location;
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "Ryan Turnquist"
7
7
  ],
8
8
  "license": "MIT",
9
- "version": "0.10.1",
9
+ "version": "0.10.3",
10
10
  "homepage": "https://github.com/solidjs/solid-router#readme",
11
11
  "repository": {
12
12
  "type": "git",