Skip to content

Commit d78613c

Browse files
committed
refactor(projects): refactor simple-router
1 parent 6f3adca commit d78613c

File tree

19 files changed

+724
-582
lines changed

19 files changed

+724
-582
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { RouterProvider as Provider } from 'react-router-dom';
2+
import { RouterContext } from './hooks/useRouter';
3+
4+
const RouterProvider = ({ router, fallback }: { router: any; fallback?: React.ReactNode }) => {
5+
return (
6+
<RouterContext.Provider value={router}>
7+
<Provider
8+
fallbackElement={fallback}
9+
router={router.reactRouter}
10+
/>
11+
</RouterContext.Provider>
12+
);
13+
};
14+
15+
export default RouterProvider;

packages/simple-router/src/context.ts

Whitespace-only changes.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import type { RouteLocationNamedRaw, RouteLocationNormalizedLoaded } from './types';
2+
3+
/**
4+
* Flags so we can combine them when checking for multiple errors. This is the internal version of
5+
* {@link NavigationFailureType}.
6+
*
7+
* @internal
8+
*/
9+
export enum ErrorTypes {
10+
// they must be literals to be used as values, so we can't write
11+
// 1 << 2
12+
MATCHER_NOT_FOUND = 1,
13+
NAVIGATION_GUARD_REDIRECT = 2,
14+
NAVIGATION_ABORTED = 4,
15+
NAVIGATION_CANCELLED = 8,
16+
NAVIGATION_DUPLICATED = 16
17+
}
18+
19+
export interface MatcherError extends Error {
20+
type: ErrorTypes.MATCHER_NOT_FOUND;
21+
location: RouteLocationNamedRaw;
22+
currentLocation?: RouteLocationNormalizedLoaded;
23+
}
24+
25+
/**
26+
* Internal error used to detect a redirection.
27+
*
28+
* @internal
29+
*/
30+
export interface NavigationRedirectError extends Omit<NavigationFailure, 'to' | 'type'> {
31+
type: ErrorTypes.NAVIGATION_GUARD_REDIRECT;
32+
to: RouteLocationNormalizedLoaded;
33+
}
34+
35+
// Possible internal errors
36+
type RouterError = NavigationFailure | NavigationRedirectError | MatcherError;
37+
38+
/** Extended Error that contains extra information regarding a failed navigation. */
39+
export interface NavigationFailure extends Error {
40+
/** Type of the navigation. One of {@link NavigationFailureType} */
41+
type: ErrorTypes.NAVIGATION_CANCELLED | ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED;
42+
/** Route location we were navigating from */
43+
from: RouteLocationNormalizedLoaded;
44+
/** Route location we were navigating to */
45+
to: RouteLocationNormalizedLoaded;
46+
}
47+
48+
// DEV only debug messages
49+
const ErrorTypeMessages = {
50+
[ErrorTypes.MATCHER_NOT_FOUND]({ location, currentLocation }: MatcherError) {
51+
return `No match for\n ${JSON.stringify(location)}${
52+
currentLocation ? `\nwhile being at\n${JSON.stringify(currentLocation)}` : ''
53+
}`;
54+
},
55+
[ErrorTypes.NAVIGATION_GUARD_REDIRECT]({ from, to }: NavigationRedirectError) {
56+
return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
57+
},
58+
[ErrorTypes.NAVIGATION_ABORTED]({ from, to }: NavigationFailure) {
59+
return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
60+
},
61+
[ErrorTypes.NAVIGATION_CANCELLED]({ from, to }: NavigationFailure) {
62+
return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
63+
},
64+
[ErrorTypes.NAVIGATION_DUPLICATED]({ from }: NavigationFailure) {
65+
return `Avoided redundant navigation to current location: "${from.fullPath}".`;
66+
}
67+
};
68+
69+
const NavigationFailureSymbol = Symbol(import.meta.env.MODE === 'development' ? 'navigation failure' : '');
70+
71+
/**
72+
* Creates a typed NavigationFailure object.
73+
*
74+
* @param type - NavigationFailureType
75+
* @param params - { from, to }
76+
* @internal
77+
*/
78+
export function createRouterError<E extends RouterError>(type: E['type'], params: Omit<E, 'type' | keyof Error>): E {
79+
// keep full error messages in cjs versions
80+
if (import.meta.env.MODE === 'development') {
81+
return Object.assign(
82+
new Error(ErrorTypeMessages[type](params as any)),
83+
{
84+
type,
85+
[NavigationFailureSymbol]: true
86+
} as { type: typeof type },
87+
params
88+
) as E;
89+
}
90+
return Object.assign(
91+
new Error('Route not found'),
92+
{
93+
type,
94+
[NavigationFailureSymbol]: true
95+
} as { type: typeof type },
96+
params
97+
) as E;
98+
}
99+
100+
const propertiesToLog = ['query', 'hash'] as const;
101+
102+
function stringifyRoute(to: RouteLocationNormalizedLoaded): string {
103+
if (typeof to === 'string') return to;
104+
if (to.path !== null) return to.path;
105+
const location = {} as Record<string, unknown>;
106+
for (const key of propertiesToLog) {
107+
if (key in to) location[key] = to[key];
108+
}
109+
return JSON.stringify(location, null, 2);
110+
}

packages/simple-router/src/hooks/useRouter/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { createContext, useContext } from 'react';
22

3-
import type Router from '../../router';
4-
5-
export const RouterContext = createContext<Router>({} as Router);
3+
export const RouterContext = createContext<any>({});
64
export function useRouter() {
75
const router = useContext(RouterContext);
86
if (!router) {

packages/simple-router/src/index.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
import CreateRouter from './router';
2-
1+
import { createRouter } from './router';
32
import { useRouter } from './hooks/useRouter';
3+
import RouterProvider from './Component';
44
import type { RouteRecordNormalized } from './matcher/types';
55

6-
export default CreateRouter;
7-
8-
export { useRouter };
6+
export { useRouter, createRouter, RouterProvider };
97

108
export type { RouteRecordNormalized };
119

12-
export type { CreateRouter as Router };
13-
1410
export type {
1511
LocationQueryValueRaw,
1612
RouteLocationNamedRaw,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2+
// @ts-nocheck
3+
4+
import type { RouteLocationNormalizedLoaded } from './types/route';
5+
6+
export const START_LOCATION_NORMALIZED: RouteLocationNormalizedLoaded = {
7+
path: '/',
8+
name: undefined,
9+
query: {},
10+
hash: '',
11+
fullPath: '/',
12+
matched: [],
13+
meta: {},
14+
state: null,
15+
redirectedFrom: undefined
16+
};

packages/simple-router/src/matcher/index.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class CreateRouterMatcher {
2727
this.initializeRoutes();
2828
this.removeRoute = this.removeRoute.bind(this);
2929
}
30+
3031
/** - Initializes the routes by adding each route from the initial routes array. */
3132
initializeRoutes() {
3233
this.initRoutes.forEach(route => this.addRoute(route));
@@ -87,7 +88,7 @@ class CreateRouterMatcher {
8788
// Avoid adding a record that doesn't display anything. This allows passing through records without a component to
8889
// not be reached and pass through the catch all route
8990

90-
if ((matcher.record.component && Object.keys(matcher.record.component).length) || matcher.record.name) {
91+
if (matcher.record.name) {
9192
this.insertMatcher(matcher);
9293
}
9394
}
@@ -125,13 +126,13 @@ class CreateRouterMatcher {
125126
getRecordMatcher(name: string) {
126127
return this.matcherMap.get(name);
127128
}
129+
128130
resolve(location: RouteLocationNamedRaw | Location, currentLocation: RouteLocationNamedRaw) {
129131
let matcher: RouteRecordRaw | undefined;
130132
let query: Record<string, any> = {};
131133
let path: string = '';
132134
let name: string | undefined;
133135
let fullPath: string = '';
134-
let component: string | undefined;
135136

136137
if ('name' in location) {
137138
matcher = this.matcherMap.get(location.name);
@@ -148,7 +149,6 @@ class CreateRouterMatcher {
148149

149150
fullPath += queryParams ? `?${queryParams}` : '';
150151
path = matcher.record.path;
151-
component = matcher.record.component;
152152
} else if (location.pathname) {
153153
// no need to resolve the path with the matcher as it was provided
154154
// this also allows the user to control the encoding
@@ -165,7 +165,6 @@ class CreateRouterMatcher {
165165
if (matcher) {
166166
name = matcher.record.name;
167167
fullPath = location.pathname + location.search;
168-
component = matcher.record.component;
169168
}
170169
// location is a relative path
171170
} else {
@@ -186,13 +185,13 @@ class CreateRouterMatcher {
186185
matched.unshift(parentMatcher.record);
187186
parentMatcher = parentMatcher.parent;
188187
}
188+
189189
return {
190190
fullPath,
191-
state: location.state,
191+
state: location?.state || null,
192192
name,
193193
path,
194194
hash: location.hash,
195-
component,
196195
redirect: matcher.record.redirect,
197196
matched,
198197
query,

packages/simple-router/src/matcher/shared.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,12 @@ export function normalizeRouteRecord(record: ElegantConstRoute): RouteRecordNorm
4141
redirect: record.redirect || (record.children && record.children[0].path),
4242
path: record.path || '',
4343
name: record.name,
44-
4544
meta: record.meta || {},
4645
children:
4746
record.children?.map(child => {
4847
child.redirect ||= child.children && child.children[0].path;
4948
return child;
50-
}) || [],
51-
component: record.component
49+
}) || []
5250
};
5351
}
5452

@@ -67,6 +65,7 @@ export function checkChildMissingNameWithEmptyPath(mainNormalizedRecord: RouteRe
6765
);
6866
}
6967
}
68+
7069
export function getQueryParams(search: string): LocationQuery {
7170
const params: LocationQuery = {};
7271
const queryParams = new URLSearchParams(search);

packages/simple-router/src/matcher/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2+
// @ts-nocheck
3+
14
import type { ElegantConstRoute } from '@ohh-889/react-auto-route';
25
import type { _RouteRecordBase } from '../types';
36

47
export interface RouteRecordNormalized {
58
/** {@inheritDoc _RouteRecordBase.path} */
69
path: _RouteRecordBase['path'];
7-
810
/** {@inheritDoc _RouteRecordBase.name} */
911
name: _RouteRecordBase['name'];
10-
/** {@inheritDoc RouteRecordMultipleViews.components} */
11-
component: string | undefined;
12+
1213
redirect?: string;
1314
/** Nested route records. */
1415
children: ElegantConstRoute[];
1516
/** {@inheritDoc _RouteRecordBase.meta} */
16-
1717
meta: Exclude<_RouteRecordBase['meta'], void>;
1818
}
1919

0 commit comments

Comments
 (0)