Skip to content

Commit 6bb14c2

Browse files
committed
Refactor, add router.use, route.bind
1 parent 8a4b2b7 commit 6bb14c2

5 files changed

Lines changed: 223 additions & 116 deletions

File tree

src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
export { createRouter } from './router';
2+
export { createRoute, createMergedRoute } from './route';
23

3-
export { shouldUpdate, normalizeLocation, onImmediate } from './utils';
4+
export {
5+
shouldUpdate,
6+
reduceStore,
7+
normalizeLocation,
8+
getQueryParams,
9+
historyChanger,
10+
createHistory,
11+
} from './utils';

src/route.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { combine, createEvent, createStore } from 'effector';
2+
import { compile as createCompile, match as createMatch } from 'path-to-regexp';
3+
import {
4+
CompileConfig,
5+
MergedRoute,
6+
Params,
7+
Route,
8+
RouteConfig,
9+
Router,
10+
} from './types';
11+
import { reduceStore } from './utils';
12+
13+
export const createRoute = <R, P extends Params = Params>(
14+
router: R extends Router<infer Q, infer S> ? Router<Q, S> : never,
15+
config: RouteConfig
16+
): Route<P, R> => {
17+
const { path, matchOptions } = config;
18+
const match = createMatch<P>(path, matchOptions);
19+
const navigate = createEvent<P | void>();
20+
const redirect = createEvent<P | void>();
21+
const $params = createStore<P | null>(null);
22+
const $visible = $params.map(Boolean);
23+
24+
reduceStore($params, router.pathname, (_, pathname) => {
25+
const result = match(pathname);
26+
return result ? result.params : null;
27+
});
28+
29+
const compile = ({
30+
params,
31+
query,
32+
hash,
33+
options,
34+
}: CompileConfig<P> = {}): string => {
35+
const queryString = String(new URLSearchParams(query));
36+
const pathname = createCompile<P>(path, options)(params);
37+
const search = queryString ? `?${queryString}` : '';
38+
const hashSign = !hash || hash.startsWith('#') ? '' : `#${hash}`;
39+
return `${pathname}${search}${hashSign}${hash ?? ''}`;
40+
};
41+
42+
navigate.watch(params =>
43+
router.navigate(compile({ params: params as P | undefined }))
44+
);
45+
46+
redirect.watch(params =>
47+
router.redirect(compile({ params: params as P | undefined }))
48+
);
49+
50+
const route: Route<P, R> = {
51+
visible: $visible,
52+
params: $params,
53+
config,
54+
compile,
55+
router,
56+
navigate,
57+
redirect,
58+
bind: (
59+
paramName,
60+
bindConfig: {
61+
router: Router;
62+
parse?: (rawParam?: string) => string | undefined;
63+
format?: (path?: string) => string | undefined;
64+
}
65+
) => {
66+
const { router: childRouter, parse, format } = bindConfig;
67+
68+
combine([$visible, childRouter.pathname]).watch(
69+
$params,
70+
([visible, childPath], params) => {
71+
const param = parse
72+
? parse(params?.[paramName] as string)
73+
: (params?.[paramName] as string);
74+
if (visible && param !== childPath) {
75+
if (param) {
76+
childRouter.navigate(param);
77+
return;
78+
}
79+
const rawParam = format ? format(childPath) : childPath;
80+
const newParams: P = { ...(params as P), [paramName]: rawParam };
81+
router.redirect(compile({ params: newParams }));
82+
}
83+
}
84+
);
85+
86+
$params.on(childRouter.pathname, (params, childPath) => {
87+
const rawParam = format ? format(childPath) : childPath;
88+
if (params?.[paramName] !== rawParam) {
89+
const newParams: P = { ...(params as P), [paramName]: rawParam };
90+
router.navigate(compile({ params: newParams }));
91+
}
92+
});
93+
94+
return route;
95+
},
96+
};
97+
98+
return route;
99+
};
100+
101+
export const createMergedRoute = (routes: Route[]): MergedRoute => {
102+
const $someVisible = combine(
103+
routes.map(route => route.visible)
104+
).map(statuses => statuses.some(Boolean));
105+
106+
const $visible = reduceStore(
107+
createStore(false),
108+
$someVisible,
109+
(visible, someVisible) => visible || someVisible
110+
).reset($someVisible);
111+
112+
const configs = routes.map(route => route.config);
113+
114+
return {
115+
visible: $visible,
116+
routes,
117+
configs,
118+
};
119+
};

src/router.ts

Lines changed: 41 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { combine, createEvent, createStore } from 'effector';
2-
import { createMemoryHistory, History, State, Update } from 'history';
3-
import { compile as createCompile, match as createMatch } from 'path-to-regexp';
1+
import { createEvent, createStore } from 'effector';
2+
import {
3+
BrowserHistory,
4+
HashHistory,
5+
MemoryHistory,
6+
State,
7+
Update,
8+
} from 'history';
49

510
import {
6-
CompileConfig,
711
Delta,
812
MergedRoute,
913
Params,
@@ -14,86 +18,27 @@ import {
1418
RouterConfig,
1519
ToLocation,
1620
} from './types';
17-
18-
import { shouldUpdate, normalizeLocation, onImmediate } from './utils';
19-
20-
const createRoute = <R, P extends Params = Params>(
21-
router: R extends Router<infer Q, infer S> ? Router<Q, S> : never,
22-
config: RouteConfig
23-
): Route<P, R> => {
24-
const { path, matchOptions } = config;
25-
const match = createMatch<P>(path, matchOptions);
26-
const navigate = createEvent<P | void>();
27-
const redirect = createEvent<P | void>();
28-
const $params = createStore<P | null>(null);
29-
const $visible = $params.map(Boolean);
30-
31-
onImmediate($params, router.pathname, (_, pathname) => {
32-
const result = match(pathname);
33-
return result ? result.params : null;
34-
}).reset(router.pathname);
35-
36-
const compile = ({
37-
params,
38-
query,
39-
hash,
40-
options,
41-
}: CompileConfig<P> = {}): string => {
42-
const queryString = String(new URLSearchParams(query));
43-
const pathname = createCompile<P>(config.path, options)(params);
44-
const search = queryString ? `?${queryString}` : '';
45-
const hashSign = !hash || hash.startsWith('#') ? '' : `#${hash}`;
46-
return `${pathname}${search}${hashSign}${hash ?? ''}`;
47-
};
48-
49-
navigate.watch(params =>
50-
router.navigate(compile({ params: params as P | undefined }))
51-
);
52-
53-
redirect.watch(params =>
54-
router.redirect(compile({ params: params as P | undefined }))
55-
);
56-
57-
return {
58-
visible: $visible,
59-
params: $params,
60-
config,
61-
compile,
62-
router,
63-
navigate,
64-
redirect,
65-
};
66-
};
67-
68-
const createMergedRoute = (routes: Route[]): MergedRoute => {
69-
const $someVisible = combine(
70-
routes.map(route => route.visible)
71-
).map(statuses => statuses.some(Boolean));
72-
73-
const $visible = onImmediate(
74-
createStore(false),
75-
$someVisible,
76-
(visible, someVisible) => visible || someVisible
77-
).reset($someVisible);
78-
79-
const configs = routes.map(route => route.config);
80-
81-
return {
82-
visible: $visible,
83-
routes,
84-
configs,
85-
};
86-
};
21+
import {
22+
createHistory,
23+
getQueryParams,
24+
historyChanger,
25+
reduceStore,
26+
} from './utils';
27+
import { createRoute, createMergedRoute } from './route';
8728

8829
export const createRouter = <Q extends Query = Query, S extends State = State>({
8930
history: userHistory,
31+
root,
9032
}: RouterConfig<S> = {}): Router<Q, S> => {
91-
const history = (userHistory ?? createMemoryHistory()) as History<S>;
33+
let history = userHistory! ?? createHistory<S>(root);
34+
9235
const historyUpdated = createEvent<Update<S>>();
9336
const $historyUpdate = createStore<Update<S>>({
9437
location: history.location,
9538
action: history.action,
96-
});
39+
}).on(historyUpdated, (_, update) => update);
40+
41+
let unlisten = history.listen(historyUpdated);
9742

9843
const navigate = createEvent<ToLocation<S>>();
9944
const redirect = createEvent<ToLocation<S>>();
@@ -108,45 +53,20 @@ export const createRouter = <Q extends Query = Query, S extends State = State>({
10853
const $hash = $location.map(location => location.hash);
10954
const $state = $location.map(location => location.state);
11055
const $key = $location.map(location => location.key);
111-
const $resource = $location.map(
112-
({ pathname, search, hash }) => `${pathname}${search}${hash}`
113-
);
114-
const $query = $search.map(
115-
// @ts-ignore
116-
search => Object.fromEntries(new URLSearchParams(search)) as Q
117-
);
56+
const $href = $location.map(history.createHref);
57+
const $query = $search.map<Q>(getQueryParams);
11858

11959
const $hasMatches = createStore(false);
12060
const $noMatches = $hasMatches.map(hasMatches => !hasMatches);
12161

122-
history.listen(historyUpdated);
123-
124-
$location.on(historyUpdated, (_, update) => update.location);
125-
126-
$location.watch(navigate, (location, toLocation) => {
127-
const newLocation = normalizeLocation<S>(toLocation);
128-
if (shouldUpdate(location, newLocation)) {
129-
const { state, ...path } = newLocation;
130-
history.push(path, state);
131-
}
132-
});
133-
134-
$location.watch(redirect, (location, toLocation) => {
135-
const newLocation = normalizeLocation<S>(toLocation);
136-
if (shouldUpdate(location, newLocation)) {
137-
const { state, ...path } = newLocation;
138-
history.replace(path, state);
139-
}
140-
});
141-
142-
shift.watch(delta => {
143-
history.go(delta);
144-
});
62+
$location.watch(navigate, historyChanger<S>(history.push));
63+
$location.watch(redirect, historyChanger<S>(history.replace));
64+
shift.watch(history.go);
14565

14666
const connectRoute = <P extends Params = Params>(
14767
route: Route<P, unknown> | MergedRoute
14868
): void => {
149-
onImmediate(
69+
reduceStore(
15070
$hasMatches,
15171
route.visible,
15272
(hasMatches, visible) => hasMatches || visible
@@ -171,8 +91,9 @@ export const createRouter = <Q extends Query = Query, S extends State = State>({
17191
hash: $hash,
17292
state: $state,
17393
key: $key,
174-
resource: $resource,
94+
href: $href,
17595
query: $query,
96+
17697
hasMatches: $hasMatches,
17798
noMatches: $noMatches,
17899

@@ -197,6 +118,18 @@ export const createRouter = <Q extends Query = Query, S extends State = State>({
197118
route.visible = route.visible.map(visible => !visible);
198119
return route;
199120
},
121+
122+
use: (
123+
givenHistory: BrowserHistory<S> | HashHistory<S> | MemoryHistory<S>
124+
) => {
125+
const { location, action } = givenHistory;
126+
const defaultState = { location, action };
127+
$historyUpdate.defaultState = defaultState;
128+
unlisten();
129+
unlisten = givenHistory.listen(historyUpdated);
130+
history = givenHistory;
131+
historyUpdated(defaultState);
132+
},
200133
};
201134

202135
return router;

src/types.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { Event, Store } from 'effector';
22
import {
33
Action,
4+
BrowserHistory,
45
Hash,
6+
HashHistory,
57
History,
8+
InitialEntry,
69
Key,
710
Location,
811
MemoryHistory,
@@ -34,13 +37,14 @@ export type ToLocation<S extends State = State> =
3437
| string
3538
| { to?: To; state?: S };
3639
export type Delta = number;
37-
export type Resource = string;
40+
export type Href = string;
3841
export type Pattern = string;
3942
export interface Query extends ObjectString {}
4043
export interface Params extends ObjectUnknown {}
4144

42-
export type RouterConfig<S extends State> = {
43-
history?: History<S> | MemoryHistory<S>;
45+
export type RouterConfig<S extends State = State> = {
46+
history?: BrowserHistory<S> | HashHistory<S> | MemoryHistory<S>;
47+
root?: InitialEntry;
4448
};
4549

4650
export type RouteConfig = {
@@ -63,6 +67,14 @@ export type Route<P extends Params = Params, R = Router> = {
6367
router: R extends Router<infer Q, infer S> ? Router<Q, S> : never;
6468
navigate: Event<P | void>;
6569
redirect: Event<P | void>;
70+
bind: (
71+
param: keyof P,
72+
bindConfig: {
73+
router: Router;
74+
parse?: (rawParam?: string) => string | undefined;
75+
format?: (path?: string) => string | undefined;
76+
}
77+
) => Route<P, R>;
6678
};
6779

6880
export type MergedRoute = {
@@ -87,7 +99,7 @@ export type Router<Q extends Query = Query, S extends State = State> = {
8799
hash: Store<Hash>;
88100
state: Store<S>;
89101
key: Store<Key>;
90-
resource: Store<Resource>;
102+
href: Store<Href>;
91103
query: Store<Q>;
92104
hasMatches: Store<boolean>;
93105
noMatches: Store<boolean>;
@@ -96,4 +108,7 @@ export type Router<Q extends Query = Query, S extends State = State> = {
96108
) => Route<P, Router<Q, S>>;
97109
merge: <T extends Route[]>(routes: T) => MergedRoute;
98110
none: <T extends Route[]>(routes: T) => MergedRoute;
111+
use: (
112+
givenHistory: BrowserHistory<S> | HashHistory<S> | MemoryHistory<S>
113+
) => void;
99114
};

0 commit comments

Comments
 (0)