@solidjs/router 0.9.1 → 0.10.0-beta.1

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.
@@ -1,20 +1,19 @@
1
1
  import type { Component, JSX } from "solid-js";
2
- import type { Location, LocationChangeSignal, MatchFilters, Navigator, RouteDataFunc, RouteDefinition, RouterIntegration } from "./types";
2
+ import type { Location, LocationChangeSignal, MatchFilters, Navigator, RouteLoadFunc, RouterIntegration, RouteSectionProps } from "./types";
3
3
  declare module "solid-js" {
4
4
  namespace JSX {
5
5
  interface AnchorHTMLAttributes<T> {
6
6
  state?: string;
7
7
  noScroll?: boolean;
8
8
  replace?: boolean;
9
- link?: boolean;
9
+ preload?: boolean;
10
10
  }
11
11
  }
12
12
  }
13
13
  export type RouterProps = {
14
14
  base?: string;
15
- data?: RouteDataFunc;
15
+ root?: Component<RouteSectionProps>;
16
16
  children: JSX.Element;
17
- out?: object;
18
17
  } & ({
19
18
  url?: never;
20
19
  source?: RouterIntegration | LocationChangeSignal;
@@ -23,27 +22,14 @@ export type RouterProps = {
23
22
  url: string;
24
23
  });
25
24
  export declare const Router: (props: RouterProps) => JSX.Element;
26
- export interface RoutesProps {
27
- base?: string;
28
- children: JSX.Element;
29
- }
30
- export declare const Routes: (props: RoutesProps) => JSX.Element;
31
- export declare const useRoutes: (routes: RouteDefinition | RouteDefinition[] | Readonly<RouteDefinition[]>, base?: string) => () => JSX.Element;
32
25
  export type RouteProps<S extends string> = {
33
- path: S | S[];
26
+ path?: S | S[];
34
27
  children?: JSX.Element;
35
- data?: RouteDataFunc;
28
+ load?: RouteLoadFunc;
36
29
  matchFilters?: MatchFilters<S>;
37
- } & ({
38
- element?: never;
39
- component: Component;
40
- } | {
41
- component?: never;
42
- element?: JSX.Element;
43
- preload?: () => void;
44
- });
30
+ component?: Component;
31
+ };
45
32
  export declare const Route: <S extends string>(props: RouteProps<S>) => JSX.Element;
46
- export declare const Outlet: () => JSX.Element;
47
33
  export interface AnchorProps extends Omit<JSX.AnchorHTMLAttributes<HTMLAnchorElement>, "state"> {
48
34
  href: string;
49
35
  replace?: boolean | undefined;
@@ -2,24 +2,24 @@
2
2
  import { children, createMemo, createRoot, mergeProps, on, Show, splitProps } from "solid-js";
3
3
  import { isServer, getRequestEvent } from "solid-js/web";
4
4
  import { pathIntegration, staticIntegration } from "./integration";
5
- import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj, useHref, useLocation, useNavigate, useResolvedPath, useRoute, useRouter } from "./routing";
6
- import { joinPaths, normalizePath, createMemoObject } from "./utils";
5
+ import { createBranches, createRouteContext, createRouterContext, getRouteMatches, RouteContextObj, RouterContextObj, useHref, useLocation, useNavigate, useResolvedPath } from "./routing";
6
+ import { normalizePath, createMemoObject } from "./utils";
7
7
  export const Router = (props) => {
8
8
  let e;
9
- const { source, url, base, data, out } = props;
9
+ const { source, url, base } = props;
10
10
  const integration = source ||
11
11
  (isServer
12
12
  ? staticIntegration({ value: url || ((e = getRequestEvent()) && e.request.url) || "" })
13
13
  : pathIntegration());
14
- const routerState = createRouterContext(integration, base, data, out);
15
- return (<RouterContextObj.Provider value={routerState}>{props.children}</RouterContextObj.Provider>);
16
- };
17
- export const Routes = (props) => {
18
- const router = useRouter();
19
- const parentRoute = useRoute();
20
14
  const routeDefs = children(() => props.children);
21
- const branches = createMemo(() => createBranches(routeDefs(), joinPaths(parentRoute.pattern, props.base || ""), Outlet));
22
- const matches = createMemo(() => getRouteMatches(branches(), router.location.pathname));
15
+ const branches = createMemo(() => createBranches(props.root ? { component: props.root, children: routeDefs() } : routeDefs(), props.base || ""));
16
+ const routerState = createRouterContext(integration, branches, base);
17
+ return (<RouterContextObj.Provider value={routerState}>
18
+ <Routes routerState={routerState} branches={branches()}/>
19
+ </RouterContextObj.Provider>);
20
+ };
21
+ function Routes(props) {
22
+ const matches = createMemo(() => getRouteMatches(props.branches, props.routerState.location.pathname));
23
23
  const params = createMemoObject(() => {
24
24
  const m = matches();
25
25
  const params = {};
@@ -28,14 +28,6 @@ export const Routes = (props) => {
28
28
  }
29
29
  return params;
30
30
  });
31
- if (router.out) {
32
- router.out.matches.push(matches().map(({ route, path, params }) => ({
33
- originalPath: route.originalPath,
34
- pattern: route.pattern,
35
- path,
36
- params
37
- })));
38
- }
39
31
  const disposers = [];
40
32
  let root;
41
33
  const routeStates = createMemo(on(matches, (nextMatches, prevMatches, prev) => {
@@ -54,7 +46,7 @@ export const Routes = (props) => {
54
46
  }
55
47
  createRoot(dispose => {
56
48
  disposers[i] = dispose;
57
- next[i] = createRouteContext(router, next[i - 1] || parentRoute, () => routeStates()[i + 1], () => matches()[i], params);
49
+ next[i] = createRouteContext(props.routerState, next[i - 1] || props.routerState.base, createOutlet(() => routeStates()[i + 1]), () => matches()[i], params);
58
50
  });
59
51
  }
60
52
  }
@@ -68,9 +60,11 @@ export const Routes = (props) => {
68
60
  return (<Show when={routeStates() && root} keyed>
69
61
  {route => <RouteContextObj.Provider value={route}>{route.outlet()}</RouteContextObj.Provider>}
70
62
  </Show>);
71
- };
72
- export const useRoutes = (routes, base) => {
73
- return () => <Routes base={base}>{routes}</Routes>;
63
+ }
64
+ const createOutlet = (child) => {
65
+ return () => (<Show when={child()} keyed>
66
+ {child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
67
+ </Show>);
74
68
  };
75
69
  export const Route = (props) => {
76
70
  const childRoutes = children(() => props.children);
@@ -80,12 +74,6 @@ export const Route = (props) => {
80
74
  }
81
75
  });
82
76
  };
83
- export const Outlet = () => {
84
- const route = useRoute();
85
- return (<Show when={route.child} keyed>
86
- {child => <RouteContextObj.Provider value={child}>{child.outlet()}</RouteContextObj.Provider>}
87
- </Show>);
88
- };
89
77
  export function A(props) {
90
78
  props = mergeProps({ inactiveClass: "inactive", activeClass: "active" }, props);
91
79
  const [, rest] = splitProps(props, [
@@ -107,7 +95,7 @@ export function A(props) {
107
95
  const loc = normalizePath(location.pathname).toLowerCase();
108
96
  return props.end ? path === loc : loc.startsWith(path);
109
97
  });
110
- return (<a link {...rest} href={href() || props.href} state={JSON.stringify(props.state)} classList={{
98
+ return (<a {...rest} href={href() || props.href} state={JSON.stringify(props.state)} classList={{
111
99
  ...(props.class && { [props.class]: true }),
112
100
  [props.inactiveClass]: !isActive(),
113
101
  [props.activeClass]: isActive(),
@@ -0,0 +1,8 @@
1
+ import { Submission } from "../types";
2
+ export type Action<T, U> = (vars: T) => Promise<U>;
3
+ export declare function useSubmissions<T, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U>[] & {
4
+ pending: boolean;
5
+ };
6
+ export declare function useSubmission<T, U>(fn: Action<T, U>, filter?: (arg: T) => boolean): Submission<T, U>;
7
+ export declare function useAction<T, U>(action: Action<T, U>): Action<T, U>;
8
+ export declare function action<T, U = void>(fn: (args: T) => Promise<U>, name?: string): Action<T, U>;
@@ -0,0 +1,113 @@
1
+ import { $TRACK, createMemo, createSignal } from "solid-js";
2
+ import { isServer } from "solid-js/web";
3
+ import { registerAction, useRouter } from "../routing";
4
+ import { redirectStatusCodes } from "../utils";
5
+ import { revalidate } from "./cache";
6
+ export function useSubmissions(fn, filter) {
7
+ const router = useRouter();
8
+ const subs = createMemo(() => router.submissions[0]().filter(s => s.url === fn.toString() && (!filter || filter(s.input))));
9
+ return new Proxy([], {
10
+ get(_, property) {
11
+ if (property === $TRACK)
12
+ return subs();
13
+ if (property === "pending")
14
+ return subs().some(sub => !sub.result);
15
+ return subs()[property];
16
+ }
17
+ });
18
+ }
19
+ export function useSubmission(fn, filter) {
20
+ const submissions = useSubmissions(fn, filter);
21
+ return {
22
+ get clear() {
23
+ return submissions[0]?.clear;
24
+ },
25
+ get retry() {
26
+ return submissions[0]?.retry;
27
+ },
28
+ get url() {
29
+ return submissions[0]?.url;
30
+ },
31
+ get input() {
32
+ return submissions[0]?.input;
33
+ },
34
+ get result() {
35
+ return submissions[0]?.result;
36
+ },
37
+ get pending() {
38
+ return submissions[0]?.pending;
39
+ }
40
+ };
41
+ }
42
+ export function useAction(action) {
43
+ const router = useRouter();
44
+ return action.bind(router);
45
+ }
46
+ export function action(fn, name) {
47
+ function mutate(variables) {
48
+ const p = fn(variables);
49
+ const [result, setResult] = createSignal();
50
+ let submission;
51
+ const router = this;
52
+ router.submissions[1](s => [
53
+ ...s,
54
+ (submission = {
55
+ input: variables,
56
+ url,
57
+ get result() {
58
+ return result()?.data;
59
+ },
60
+ get pending() {
61
+ return !result();
62
+ },
63
+ clear() {
64
+ router.submissions[1](v => v.filter(i => i.input !== variables));
65
+ },
66
+ retry() {
67
+ setResult(undefined);
68
+ const p = fn(variables);
69
+ p.then(async (data) => {
70
+ const keys = handleResponse(data, router.navigatorFactory());
71
+ await revalidate(keys);
72
+ data ? setResult({ data }) : submission.clear();
73
+ return data;
74
+ }).catch(error => {
75
+ setResult({ data: error });
76
+ });
77
+ return p;
78
+ }
79
+ })
80
+ ]);
81
+ p.then(async (data) => {
82
+ const keys = handleResponse(data, router.navigatorFactory());
83
+ await revalidate(keys);
84
+ data ? setResult({ data }) : submission.clear();
85
+ return data;
86
+ }).catch(error => {
87
+ setResult({ data: error });
88
+ });
89
+ return p;
90
+ }
91
+ const url = fn.url || `action:${name}` || !isServer ? `action:${fn.name}` : "";
92
+ mutate.toString = () => {
93
+ if (!url)
94
+ throw new Error("Client Actions need explicit names if server rendered");
95
+ return url;
96
+ };
97
+ if (!isServer)
98
+ registerAction(url, mutate);
99
+ return mutate;
100
+ }
101
+ function handleResponse(response, navigate) {
102
+ if (response instanceof Response && redirectStatusCodes.has(response.status)) {
103
+ const locationUrl = response.headers.get("Location") || "/";
104
+ if (locationUrl.startsWith("http")) {
105
+ window.location.href = locationUrl;
106
+ }
107
+ else {
108
+ navigate(locationUrl);
109
+ }
110
+ }
111
+ // return keys
112
+ return;
113
+ }
@@ -0,0 +1,3 @@
1
+ import { type ReconcileOptions } from "solid-js/store";
2
+ export declare function revalidate(key?: string | any[] | void): Promise<void>;
3
+ export declare function cache<T extends (...args: any) => U | Response, U>(fn: T, name: string, options?: ReconcileOptions): T;
@@ -0,0 +1,114 @@
1
+ import { createSignal, getOwner, onCleanup, sharedConfig, startTransition } from "solid-js";
2
+ import { createStore, reconcile } from "solid-js/store";
3
+ import { getRequestEvent, isServer } from "solid-js/web";
4
+ import { useNavigate, getIntent } from "../routing";
5
+ import { redirectStatusCodes } from "../utils";
6
+ const LocationHeader = "Location";
7
+ const PRELOAD_TIMEOUT = 5000;
8
+ let cacheMap = new Map();
9
+ function getCache() {
10
+ if (!isServer)
11
+ return cacheMap;
12
+ const req = getRequestEvent() || sharedConfig.context;
13
+ return req.routerCache || (req.routerCache = new Map());
14
+ }
15
+ export function revalidate(key) {
16
+ return startTransition(() => {
17
+ const now = Date.now();
18
+ for (let k of cacheMap.keys()) {
19
+ if (key === undefined || k === key) {
20
+ const set = cacheMap.get(k)[3];
21
+ revalidateSignals(set, now);
22
+ cacheMap.delete(k);
23
+ }
24
+ }
25
+ });
26
+ }
27
+ function revalidateSignals(set, time) {
28
+ for (let s of set)
29
+ s[1](time);
30
+ }
31
+ export function cache(fn, name, options) {
32
+ const [store, setStore] = createStore({});
33
+ return ((...args) => {
34
+ const cache = getCache();
35
+ const intent = getIntent();
36
+ const owner = getOwner();
37
+ const navigate = owner ? useNavigate() : undefined;
38
+ const now = Date.now();
39
+ const key = name + (args.length ? ":" + args.join(":") : "");
40
+ let cached = cache.get(key);
41
+ let version;
42
+ if (owner) {
43
+ version = createSignal(now, {
44
+ equals: (p, v) => v - p < 50 // margin of error
45
+ });
46
+ onCleanup(() => cached[3].delete(version));
47
+ version[0](); // track it;
48
+ }
49
+ if (cached && (isServer || intent === "native" || Date.now() - cached[0] < PRELOAD_TIMEOUT)) {
50
+ version && cached[3].add(version);
51
+ if (cached[2] === "preload" && intent !== "preload") {
52
+ cached[0] = now;
53
+ cached[1] =
54
+ "then" in cached[1]
55
+ ? cached[1].then(handleResponse)
56
+ : handleResponse(cached[1]);
57
+ cached[2] = intent;
58
+ }
59
+ if (!isServer && intent === "navigate") {
60
+ startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
61
+ }
62
+ return cached[1];
63
+ }
64
+ let res = !isServer && sharedConfig.context && sharedConfig.load
65
+ ? sharedConfig.load(key) // hydrating
66
+ : fn(...args);
67
+ // serialize on server
68
+ if (isServer && sharedConfig.context && !sharedConfig.context.noHydrate) {
69
+ sharedConfig.context && sharedConfig.context.serialize(key, res);
70
+ }
71
+ if (intent !== "preload") {
72
+ res =
73
+ "then" in res
74
+ ? res.then(handleResponse)
75
+ : handleResponse(res);
76
+ }
77
+ if (cached) {
78
+ cached[0] = now;
79
+ cached[1] = res;
80
+ cached[2] = intent;
81
+ version && cached[3].add(version);
82
+ if (!isServer && intent === "navigate") {
83
+ startTransition(() => revalidateSignals(cached[3], cached[0])); // update version
84
+ }
85
+ }
86
+ else
87
+ cache.set(key, (cached = [now, res, intent, new Set(version ? [version] : [])]));
88
+ return res;
89
+ function handleRedirect(response) {
90
+ startTransition(() => {
91
+ let url = response.headers.get(LocationHeader);
92
+ if (url && url.startsWith("/")) {
93
+ navigate(url, {
94
+ replace: true
95
+ });
96
+ }
97
+ else if (!isServer && url) {
98
+ window.location.href = url;
99
+ }
100
+ });
101
+ }
102
+ function handleResponse(v) {
103
+ if (v instanceof Response && redirectStatusCodes.has(v.status)) {
104
+ if (navigate)
105
+ isServer ? handleRedirect(v) : setTimeout(() => handleRedirect(v), 0);
106
+ return;
107
+ }
108
+ if (isServer)
109
+ return v;
110
+ setStore(key, reconcile(v, options));
111
+ return store[key];
112
+ }
113
+ });
114
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
3
+ */
4
+ import { type Accessor } from "solid-js";
5
+ export declare function createAsync<T>(fn: () => Promise<T>): Accessor<T | undefined>;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * This is mock of the eventual Solid 2.0 primitive. It is not fully featured.
3
+ */
4
+ import { createResource, sharedConfig } from "solid-js";
5
+ import { isServer } from "solid-js/web";
6
+ export function createAsync(fn) {
7
+ const [resource] = createResource(() => subFetch(fn), v => v);
8
+ return () => resource();
9
+ }
10
+ // mock promise while hydrating to prevent fetching
11
+ class MockPromise {
12
+ static all() {
13
+ return new MockPromise();
14
+ }
15
+ static allSettled() {
16
+ return new MockPromise();
17
+ }
18
+ static any() {
19
+ return new MockPromise();
20
+ }
21
+ static race() {
22
+ return new MockPromise();
23
+ }
24
+ static reject() {
25
+ return new MockPromise();
26
+ }
27
+ static resolve() {
28
+ return new MockPromise();
29
+ }
30
+ catch() {
31
+ return new MockPromise();
32
+ }
33
+ then() {
34
+ return new MockPromise();
35
+ }
36
+ finally() {
37
+ return new MockPromise();
38
+ }
39
+ }
40
+ function subFetch(fn) {
41
+ if (isServer || !sharedConfig.context)
42
+ return fn();
43
+ const ogFetch = fetch;
44
+ const ogPromise = Promise;
45
+ try {
46
+ window.fetch = () => new MockPromise();
47
+ Promise = MockPromise;
48
+ return fn();
49
+ }
50
+ finally {
51
+ window.fetch = ogFetch;
52
+ Promise = ogPromise;
53
+ }
54
+ }
@@ -0,0 +1,3 @@
1
+ export { createAsync } from "./createAsync";
2
+ export { action, useSubmission, useSubmissions } from "./action";
3
+ export { cache, revalidate } from "./cache";
@@ -0,0 +1,3 @@
1
+ export { createAsync } from "./createAsync";
2
+ export { action, useSubmission, useSubmissions } from "./action";
3
+ export { cache, revalidate } from "./cache";
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./components";
2
2
  export * from "./integration";
3
3
  export * from "./lifecycle";
4
- export { useRouteData, useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
4
+ export { useHref, useIsRouting, useLocation, useMatch, useNavigate, useParams, useResolvedPath, useSearchParams, useBeforeLeave, } from "./routing";
5
5
  export { mergeSearchString as _mergeSearchString } from "./utils";
6
- export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteDataFunc, RouteDataFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";
6
+ export * from "./data";
7
+ export type { Location, LocationChange, LocationChangeSignal, NavigateOptions, Navigator, OutputMatch, Params, RouteSectionProps, RouteLoadFunc, RouteLoadFuncArgs, RouteDefinition, RouterIntegration, RouterOutput, RouterUtils, SetParams, BeforeLeaveEventArgs } from "./types";