space-router 1.0.0 → 1.1.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 CHANGED
@@ -6,7 +6,7 @@
6
6
  <h4 align="center">Framework agnostic router for single page apps</h4>
7
7
  <br />
8
8
 
9
- Space Router packs all the features you need to keep your app in sync with the url. It's distinct from many other routers in that there is only **a single callback**. This callback can be used to re-render your applocation, update a store and perform other actions on each url change. Space Router is **stateless**, it doesn't store the current route leaving state completely up to you to handle.
9
+ Space Router packs all the features you need to keep your app in sync with the url. It's distinct from many other routers in that there is only **a single callback**. This callback can be used to re-render your application, update a store and perform other actions on each url change. Space Router is **stateless**, it doesn't store the current route leaving state completely up to you to handle.
10
10
 
11
11
  In summary, Space Router:
12
12
 
package/dist/history.js CHANGED
@@ -53,7 +53,7 @@ export function createHistory(options = {}) {
53
53
  schedule();
54
54
  }
55
55
  else if (mode === 'memory') {
56
- if (replace) {
56
+ if (replace && memory.length) {
57
57
  memory[memory.length - 1] = url;
58
58
  }
59
59
  else {
@@ -64,7 +64,7 @@ export function createHistory(options = {}) {
64
64
  }
65
65
  function getUrl() {
66
66
  if (mode === 'memory') {
67
- return memory[memory.length - 1];
67
+ return memory[memory.length - 1] ?? '';
68
68
  }
69
69
  const hash = getHash();
70
70
  if (mode === 'hash') {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { qs } from './qs.ts';
2
2
  export { createHistory } from './history.ts';
3
- export { createRouter, merge } from './router.ts';
3
+ export { createMatcher, createRouter, merge } from './router.ts';
4
4
  export type { Qs } from './qs.ts';
5
5
  export type { Mode, History, CreateHistoryOptions } from './history.ts';
6
- export type { RouterOptions, Route, NavigateTarget, To, Redirect, RouteDefinition, Router } from './router.ts';
6
+ export type { MatcherOptions, RouteData, RouterOptions, Route, NavigateTarget, To, Redirect, RouteDefinition, Matcher, Router, } from './router.ts';
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { qs } from "./qs.js";
2
2
  export { createHistory } from "./history.js";
3
- export { createRouter, merge } from "./router.js";
3
+ export { createMatcher, createRouter, merge } from "./router.js";
package/dist/match.d.ts CHANGED
@@ -8,9 +8,4 @@ export interface MatchedRoute {
8
8
  search: string;
9
9
  hash: string;
10
10
  }
11
- interface RouteEntry {
12
- pattern: string;
13
- }
14
- export declare function match(routes: RouteEntry[], url: string | undefined, qs: Qs): MatchedRoute | undefined;
15
11
  export declare function matchOne(pattern: string, url: string, qs?: Qs): MatchedRoute | undefined;
16
- export {};
package/dist/match.js CHANGED
@@ -1,12 +1,3 @@
1
- export function match(routes, url, qs) {
2
- if (!url)
3
- return;
4
- for (let i = 0; i < routes.length; i++) {
5
- const m = matchOne(routes[i].pattern, url, qs);
6
- if (m)
7
- return m;
8
- }
9
- }
10
1
  export function matchOne(pattern, url, qs) {
11
2
  if (!pattern)
12
3
  return;
package/dist/router.d.ts CHANGED
@@ -6,8 +6,11 @@ export interface RouterOptions {
6
6
  qs?: Qs;
7
7
  sync?: boolean;
8
8
  }
9
+ export interface MatcherOptions {
10
+ qs?: Qs;
11
+ }
9
12
  export interface Route<Data = Record<string, unknown>> extends MatchedRoute {
10
- data: Data[];
13
+ data: RouteData<Data>[];
11
14
  }
12
15
  export interface NavigateTarget {
13
16
  url?: string;
@@ -20,6 +23,10 @@ export interface NavigateTarget {
20
23
  }
21
24
  export type To = string | NavigateTarget;
22
25
  export type Redirect<Data = Record<string, unknown>> = To | ((route: Route<Data>) => To);
26
+ export type RouteData<Data = Record<string, unknown>> = Data & {
27
+ path: string;
28
+ redirect?: Redirect<Data>;
29
+ };
23
30
  export type RouteDefinition<Data = Record<string, unknown>> = Data & {
24
31
  path?: string;
25
32
  redirect?: Redirect<Data>;
@@ -32,11 +39,15 @@ export interface Router<Data = Record<string, unknown>> {
32
39
  match(url: string): Route<Data> | undefined;
33
40
  getUrl(): string;
34
41
  }
35
- interface FlatRoute {
42
+ export interface Matcher<Data = Record<string, unknown>> {
43
+ match(url: string): Route<Data> | undefined;
44
+ }
45
+ interface FlatRoute<Data = Record<string, unknown>> {
36
46
  pattern: string;
37
- data: Array<Record<string, unknown>>;
47
+ data: RouteData<Data>[];
38
48
  }
39
49
  export declare function createRouter<Data = Record<string, unknown>>(options?: RouterOptions): Router<Data>;
40
- export declare function flatten(routeMap: RouteDefinition[]): FlatRoute[];
50
+ export declare function createMatcher<Data = Record<string, unknown>>(routeMap: RouteDefinition<Data>[], options?: MatcherOptions): Matcher<Data>;
51
+ export declare function flatten<Data = Record<string, unknown>>(routeMap: RouteDefinition<Data>[]): FlatRoute<Data>[];
41
52
  export declare function merge(curr: Partial<Route> | NavigateTarget | undefined, to: NavigateTarget): NavigateTarget;
42
53
  export {};
package/dist/router.js CHANGED
@@ -1,4 +1,4 @@
1
- import { match as findMatch } from "./match.js";
1
+ import { matchOne } from "./match.js";
2
2
  import { createHistory } from "./history.js";
3
3
  import { qs as defaultQs } from "./qs.js";
4
4
  const PARAM_RE = /:([A-Za-z0-9_]+)([+*?])?/g;
@@ -8,13 +8,13 @@ export function createRouter(options = {}) {
8
8
  const qs = options.qs || defaultQs;
9
9
  const sync = options.sync || false;
10
10
  const history = createHistory({ mode, sync });
11
- let routes = [];
11
+ let matcher = createMatcher([], { qs });
12
12
  const router = {
13
13
  listen(routeMap, cb) {
14
- routes = flatten(routeMap);
14
+ matcher = createMatcher(routeMap, { qs });
15
15
  let redirects = 0;
16
16
  return history.listen((url) => {
17
- const route = router.match(url);
17
+ const route = matcher.match(url);
18
18
  if (!route) {
19
19
  redirects = 0;
20
20
  return;
@@ -84,11 +84,7 @@ export function createRouter(options = {}) {
84
84
  return url;
85
85
  },
86
86
  match(url) {
87
- const route = findMatch(routes, url, qs);
88
- if (route) {
89
- return { ...route, data: data(routes, route) };
90
- }
91
- return undefined;
87
+ return matcher.match(url);
92
88
  },
93
89
  getUrl() {
94
90
  return history.getUrl();
@@ -96,15 +92,37 @@ export function createRouter(options = {}) {
96
92
  };
97
93
  return router;
98
94
  }
95
+ export function createMatcher(routeMap, options = {}) {
96
+ const routes = flatten(routeMap);
97
+ const qs = options.qs || defaultQs;
98
+ return {
99
+ match(url) {
100
+ if (!url)
101
+ return undefined;
102
+ for (const route of routes) {
103
+ const m = matchOne(route.pattern, url, qs);
104
+ if (m) {
105
+ return { ...m, data: route.data };
106
+ }
107
+ }
108
+ return undefined;
109
+ },
110
+ };
111
+ }
99
112
  export function flatten(routeMap) {
100
113
  const routes = [];
101
114
  const parentData = [];
102
115
  function addLevel(level) {
103
116
  level.forEach((route) => {
104
117
  const { path = '', routes: children, ...routeData } = route;
105
- routes.push({ pattern: path, data: parentData.concat([routeData]) });
118
+ const segment = { path, ...routeData };
119
+ // pathless routes only contribute data to their children — they have
120
+ // no pattern of their own to match
121
+ if (path) {
122
+ routes.push({ pattern: path, data: parentData.concat([segment]) });
123
+ }
106
124
  if (children) {
107
- parentData.push(routeData);
125
+ parentData.push(segment);
108
126
  addLevel(children);
109
127
  parentData.pop();
110
128
  }
@@ -113,14 +131,6 @@ export function flatten(routeMap) {
113
131
  addLevel(routeMap);
114
132
  return routes;
115
133
  }
116
- function data(routes, matchingRoute) {
117
- for (let i = 0; i < routes.length; i++) {
118
- if (routes[i].pattern === matchingRoute.pattern) {
119
- return routes[i].data;
120
- }
121
- }
122
- return [];
123
- }
124
134
  export function merge(curr, to) {
125
135
  const c = (curr || {});
126
136
  const pathname = to.pathname || c.pattern || c.pathname;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "space-router",
3
3
  "description": "All the routing essentials.",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -47,16 +47,16 @@
47
47
  "release:docs": "hugo -s docs && gh-pages -d docs/public"
48
48
  },
49
49
  "devDependencies": {
50
- "ava": "^7.0.0",
50
+ "ava": "^8.0.1",
51
51
  "c8": "^11.0.0",
52
52
  "gh-pages": "^6.3.0",
53
- "oxfmt": "^0.46.0",
54
- "oxlint": "^1.61.0",
53
+ "oxfmt": "^0.57.0",
54
+ "oxlint": "^1.72.0",
55
55
  "typescript": "^6.0.3"
56
56
  },
57
57
  "ava": {
58
- "extensions": {
59
- "ts": "module"
60
- }
58
+ "extensions": [
59
+ "ts"
60
+ ]
61
61
  }
62
62
  }
package/src/history.ts CHANGED
@@ -65,7 +65,7 @@ export function createHistory(options: CreateHistoryOptions = {}): History {
65
65
  location[replace ? 'replace' : 'assign']('#' + url)
66
66
  if (same) schedule()
67
67
  } else if (mode === 'memory') {
68
- if (replace) {
68
+ if (replace && memory.length) {
69
69
  memory[memory.length - 1] = url
70
70
  } else {
71
71
  memory.push(url)
@@ -76,7 +76,7 @@ export function createHistory(options: CreateHistoryOptions = {}): History {
76
76
 
77
77
  function getUrl(): string {
78
78
  if (mode === 'memory') {
79
- return memory[memory.length - 1]
79
+ return memory[memory.length - 1] ?? ''
80
80
  }
81
81
 
82
82
  const hash = getHash()
package/src/index.ts CHANGED
@@ -1,7 +1,18 @@
1
1
  export { qs } from './qs.ts'
2
2
  export { createHistory } from './history.ts'
3
- export { createRouter, merge } from './router.ts'
3
+ export { createMatcher, createRouter, merge } from './router.ts'
4
4
 
5
5
  export type { Qs } from './qs.ts'
6
6
  export type { Mode, History, CreateHistoryOptions } from './history.ts'
7
- export type { RouterOptions, Route, NavigateTarget, To, Redirect, RouteDefinition, Router } from './router.ts'
7
+ export type {
8
+ MatcherOptions,
9
+ RouteData,
10
+ RouterOptions,
11
+ Route,
12
+ NavigateTarget,
13
+ To,
14
+ Redirect,
15
+ RouteDefinition,
16
+ Matcher,
17
+ Router,
18
+ } from './router.ts'
package/src/match.ts CHANGED
@@ -10,18 +10,6 @@ export interface MatchedRoute {
10
10
  hash: string
11
11
  }
12
12
 
13
- interface RouteEntry {
14
- pattern: string
15
- }
16
-
17
- export function match(routes: RouteEntry[], url: string | undefined, qs: Qs): MatchedRoute | undefined {
18
- if (!url) return
19
- for (let i = 0; i < routes.length; i++) {
20
- const m = matchOne(routes[i].pattern, url, qs)
21
- if (m) return m
22
- }
23
- }
24
-
25
13
  export function matchOne(pattern: string, url: string, qs?: Qs): MatchedRoute | undefined {
26
14
  if (!pattern) return
27
15
 
package/src/router.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { match as findMatch, type MatchedRoute } from './match.ts'
1
+ import { matchOne, type MatchedRoute } from './match.ts'
2
2
  import { createHistory, type Mode } from './history.ts'
3
3
  import { qs as defaultQs, type Qs } from './qs.ts'
4
4
 
@@ -8,8 +8,12 @@ export interface RouterOptions {
8
8
  sync?: boolean
9
9
  }
10
10
 
11
+ export interface MatcherOptions {
12
+ qs?: Qs
13
+ }
14
+
11
15
  export interface Route<Data = Record<string, unknown>> extends MatchedRoute {
12
- data: Data[]
16
+ data: RouteData<Data>[]
13
17
  }
14
18
 
15
19
  export interface NavigateTarget {
@@ -26,6 +30,11 @@ export type To = string | NavigateTarget
26
30
 
27
31
  export type Redirect<Data = Record<string, unknown>> = To | ((route: Route<Data>) => To)
28
32
 
33
+ export type RouteData<Data = Record<string, unknown>> = Data & {
34
+ path: string
35
+ redirect?: Redirect<Data>
36
+ }
37
+
29
38
  export type RouteDefinition<Data = Record<string, unknown>> = Data & {
30
39
  path?: string
31
40
  redirect?: Redirect<Data>
@@ -40,9 +49,13 @@ export interface Router<Data = Record<string, unknown>> {
40
49
  getUrl(): string
41
50
  }
42
51
 
43
- interface FlatRoute {
52
+ export interface Matcher<Data = Record<string, unknown>> {
53
+ match(url: string): Route<Data> | undefined
54
+ }
55
+
56
+ interface FlatRoute<Data = Record<string, unknown>> {
44
57
  pattern: string
45
- data: Array<Record<string, unknown>>
58
+ data: RouteData<Data>[]
46
59
  }
47
60
 
48
61
  const PARAM_RE = /:([A-Za-z0-9_]+)([+*?])?/g
@@ -54,19 +67,19 @@ export function createRouter<Data = Record<string, unknown>>(options: RouterOpti
54
67
  const sync = options.sync || false
55
68
  const history = createHistory({ mode, sync })
56
69
 
57
- let routes: FlatRoute[] = []
70
+ let matcher = createMatcher<Data>([], { qs })
58
71
 
59
72
  const router: Router<Data> = {
60
73
  listen(routeMap, cb) {
61
- routes = flatten(routeMap as RouteDefinition[])
74
+ matcher = createMatcher(routeMap, { qs })
62
75
  let redirects = 0
63
76
  return history.listen((url) => {
64
- const route = router.match(url)
77
+ const route = matcher.match(url)
65
78
  if (!route) {
66
79
  redirects = 0
67
80
  return
68
81
  }
69
- for (const r of route.data as Array<{ redirect?: Redirect<Data> }>) {
82
+ for (const r of route.data) {
70
83
  if (r.redirect) {
71
84
  if (++redirects > MAX_REDIRECTS) {
72
85
  redirects = 0
@@ -138,11 +151,7 @@ export function createRouter<Data = Record<string, unknown>>(options: RouterOpti
138
151
  },
139
152
 
140
153
  match(url) {
141
- const route = findMatch(routes, url, qs)
142
- if (route) {
143
- return { ...route, data: data(routes, route) } as Route<Data>
144
- }
145
- return undefined
154
+ return matcher.match(url)
146
155
  },
147
156
 
148
157
  getUrl() {
@@ -153,22 +162,48 @@ export function createRouter<Data = Record<string, unknown>>(options: RouterOpti
153
162
  return router
154
163
  }
155
164
 
156
- export function flatten(routeMap: RouteDefinition[]): FlatRoute[] {
157
- const routes: FlatRoute[] = []
158
- const parentData: Array<Record<string, unknown>> = []
159
- function addLevel(level: RouteDefinition[]) {
165
+ export function createMatcher<Data = Record<string, unknown>>(
166
+ routeMap: RouteDefinition<Data>[],
167
+ options: MatcherOptions = {},
168
+ ): Matcher<Data> {
169
+ const routes = flatten(routeMap)
170
+ const qs = options.qs || defaultQs
171
+
172
+ return {
173
+ match(url) {
174
+ if (!url) return undefined
175
+ for (const route of routes) {
176
+ const m = matchOne(route.pattern, url, qs)
177
+ if (m) {
178
+ return { ...m, data: route.data } as Route<Data>
179
+ }
180
+ }
181
+ return undefined
182
+ },
183
+ }
184
+ }
185
+
186
+ export function flatten<Data = Record<string, unknown>>(routeMap: RouteDefinition<Data>[]): FlatRoute<Data>[] {
187
+ const routes: FlatRoute<Data>[] = []
188
+ const parentData: RouteData<Data>[] = []
189
+ function addLevel(level: RouteDefinition<Data>[]) {
160
190
  level.forEach((route) => {
161
191
  const {
162
192
  path = '',
163
193
  routes: children,
164
194
  ...routeData
165
- } = route as RouteDefinition & {
195
+ } = route as RouteDefinition<Data> & {
166
196
  path?: string
167
- routes?: RouteDefinition[]
197
+ routes?: RouteDefinition<Data>[]
198
+ }
199
+ const segment = { path, ...routeData } as RouteData<Data>
200
+ // pathless routes only contribute data to their children — they have
201
+ // no pattern of their own to match
202
+ if (path) {
203
+ routes.push({ pattern: path, data: parentData.concat([segment]) })
168
204
  }
169
- routes.push({ pattern: path, data: parentData.concat([routeData]) })
170
205
  if (children) {
171
- parentData.push(routeData)
206
+ parentData.push(segment)
172
207
  addLevel(children)
173
208
  parentData.pop()
174
209
  }
@@ -178,15 +213,6 @@ export function flatten(routeMap: RouteDefinition[]): FlatRoute[] {
178
213
  return routes
179
214
  }
180
215
 
181
- function data(routes: FlatRoute[], matchingRoute: { pattern: string }) {
182
- for (let i = 0; i < routes.length; i++) {
183
- if (routes[i].pattern === matchingRoute.pattern) {
184
- return routes[i].data
185
- }
186
- }
187
- return []
188
- }
189
-
190
216
  export function merge(curr: Partial<Route> | NavigateTarget | undefined, to: NavigateTarget): NavigateTarget {
191
217
  const c = (curr || {}) as Partial<Route> & NavigateTarget
192
218
  const pathname = to.pathname || c.pattern || c.pathname