@richie-router/react 0.1.2 → 0.1.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/dist/cjs/router.cjs +281 -81
- package/dist/cjs/router.test.cjs +413 -0
- package/dist/esm/router.mjs +281 -81
- package/dist/esm/router.test.mjs +418 -0
- package/dist/types/router.d.ts +12 -0
- package/package.json +2 -2
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
// packages/react/src/router.test.ts
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
import {
|
|
4
|
+
createFileRoute,
|
|
5
|
+
createMemoryHistory,
|
|
6
|
+
createRootRoute,
|
|
7
|
+
createRouter
|
|
8
|
+
} from "./router.mjs";
|
|
9
|
+
function createTestRouteTree(options) {
|
|
10
|
+
const rootRoute = createRootRoute({
|
|
11
|
+
component: () => null
|
|
12
|
+
});
|
|
13
|
+
const indexRoute = createFileRoute("/")({
|
|
14
|
+
component: () => null
|
|
15
|
+
});
|
|
16
|
+
const aboutRoute = createFileRoute("/about")({
|
|
17
|
+
component: () => null
|
|
18
|
+
});
|
|
19
|
+
aboutRoute._setServerHead(options?.serverHead);
|
|
20
|
+
return rootRoute._addFileChildren({
|
|
21
|
+
index: indexRoute,
|
|
22
|
+
about: aboutRoute
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function createNestedHeadRouteTree() {
|
|
26
|
+
const rootRoute = createRootRoute({
|
|
27
|
+
component: () => null
|
|
28
|
+
});
|
|
29
|
+
const postsRoute = createFileRoute("/posts")({
|
|
30
|
+
component: () => null,
|
|
31
|
+
head: [
|
|
32
|
+
{ tag: "title", children: "Posts" }
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
const postRoute = createFileRoute("/posts/$postId")({
|
|
36
|
+
component: () => null
|
|
37
|
+
});
|
|
38
|
+
rootRoute._setServerHead(true);
|
|
39
|
+
postRoute._setServerHead(true);
|
|
40
|
+
postsRoute._addFileChildren({
|
|
41
|
+
post: postRoute
|
|
42
|
+
});
|
|
43
|
+
return rootRoute._addFileChildren({
|
|
44
|
+
posts: postsRoute
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function createRootServerHeadTree() {
|
|
48
|
+
const rootRoute = createRootRoute({
|
|
49
|
+
component: () => null
|
|
50
|
+
});
|
|
51
|
+
const indexRoute = createFileRoute("/")({
|
|
52
|
+
component: () => null
|
|
53
|
+
});
|
|
54
|
+
const aboutRoute = createFileRoute("/about")({
|
|
55
|
+
component: () => null
|
|
56
|
+
});
|
|
57
|
+
rootRoute._setServerHead(true);
|
|
58
|
+
return rootRoute._addFileChildren({
|
|
59
|
+
index: indexRoute,
|
|
60
|
+
about: aboutRoute
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function createNestedServerHeadTree() {
|
|
64
|
+
const rootRoute = createRootRoute({
|
|
65
|
+
component: () => null
|
|
66
|
+
});
|
|
67
|
+
const postsRoute = createFileRoute("/posts")({
|
|
68
|
+
component: () => null
|
|
69
|
+
});
|
|
70
|
+
const postRoute = createFileRoute("/posts/$postId")({
|
|
71
|
+
component: () => null
|
|
72
|
+
});
|
|
73
|
+
rootRoute._setServerHead(true);
|
|
74
|
+
postsRoute._setServerHead(true);
|
|
75
|
+
postRoute._setServerHead(true);
|
|
76
|
+
postsRoute._addFileChildren({
|
|
77
|
+
post: postRoute
|
|
78
|
+
});
|
|
79
|
+
return rootRoute._addFileChildren({
|
|
80
|
+
posts: postsRoute
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
describe("createRouter basePath", () => {
|
|
84
|
+
test("strips the basePath from the current history location", () => {
|
|
85
|
+
const history = createMemoryHistory({
|
|
86
|
+
initialEntries: ["/project/about?tab=team#bio"]
|
|
87
|
+
});
|
|
88
|
+
const router = createRouter({
|
|
89
|
+
routeTree: createTestRouteTree(),
|
|
90
|
+
history,
|
|
91
|
+
basePath: "/project"
|
|
92
|
+
});
|
|
93
|
+
expect(router.state.location.pathname).toBe("/about");
|
|
94
|
+
expect(router.state.location.href).toBe("/about?tab=team#bio");
|
|
95
|
+
expect(router.state.matches.at(-1)?.route.fullPath).toBe("/about");
|
|
96
|
+
});
|
|
97
|
+
test("prefixes generated hrefs and history writes with the basePath", async () => {
|
|
98
|
+
const history = createMemoryHistory({
|
|
99
|
+
initialEntries: ["/project"]
|
|
100
|
+
});
|
|
101
|
+
const router = createRouter({
|
|
102
|
+
routeTree: createTestRouteTree(),
|
|
103
|
+
history,
|
|
104
|
+
basePath: "/project"
|
|
105
|
+
});
|
|
106
|
+
expect(router.buildHref({ to: "/about" })).toBe("/project/about");
|
|
107
|
+
await router.navigate({
|
|
108
|
+
to: "/about",
|
|
109
|
+
search: {
|
|
110
|
+
tab: "team"
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
expect(history.location.href).toBe("/project/about?tab=team");
|
|
114
|
+
expect(router.state.location.href).toBe("/about?tab=team");
|
|
115
|
+
});
|
|
116
|
+
test("uses the basePath for the default head API endpoint", async () => {
|
|
117
|
+
const history = createMemoryHistory({
|
|
118
|
+
initialEntries: ["/project/about"]
|
|
119
|
+
});
|
|
120
|
+
const fetchCalls = [];
|
|
121
|
+
const originalFetch = globalThis.fetch;
|
|
122
|
+
globalThis.fetch = async (input) => {
|
|
123
|
+
fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
|
|
124
|
+
return new Response(JSON.stringify({
|
|
125
|
+
head: [],
|
|
126
|
+
routeHeads: [
|
|
127
|
+
{ routeId: "/about", head: [] }
|
|
128
|
+
]
|
|
129
|
+
}), {
|
|
130
|
+
status: 200,
|
|
131
|
+
headers: {
|
|
132
|
+
"content-type": "application/json"
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
try {
|
|
137
|
+
const router = createRouter({
|
|
138
|
+
routeTree: createTestRouteTree({ serverHead: true }),
|
|
139
|
+
history,
|
|
140
|
+
basePath: "/project"
|
|
141
|
+
});
|
|
142
|
+
await router.load();
|
|
143
|
+
expect(fetchCalls).toHaveLength(1);
|
|
144
|
+
expect(fetchCalls[0]).toBe("/project/head-api?href=%2Fproject%2Fabout");
|
|
145
|
+
} finally {
|
|
146
|
+
globalThis.fetch = originalFetch;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
test("uses one document head request and preserves inline head precedence", async () => {
|
|
150
|
+
const history = createMemoryHistory({
|
|
151
|
+
initialEntries: ["/posts/alpha"]
|
|
152
|
+
});
|
|
153
|
+
const fetchCalls = [];
|
|
154
|
+
const originalFetch = globalThis.fetch;
|
|
155
|
+
globalThis.fetch = async (input) => {
|
|
156
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
157
|
+
fetchCalls.push(url);
|
|
158
|
+
return new Response(JSON.stringify({
|
|
159
|
+
head: [
|
|
160
|
+
{ tag: "title", children: "Site" },
|
|
161
|
+
{ tag: "title", children: "Alpha" }
|
|
162
|
+
],
|
|
163
|
+
routeHeads: [
|
|
164
|
+
{
|
|
165
|
+
routeId: "__root__",
|
|
166
|
+
head: [{ tag: "title", children: "Site" }],
|
|
167
|
+
staleTime: 60000
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
routeId: "/posts/$postId",
|
|
171
|
+
head: [{ tag: "title", children: "Alpha" }],
|
|
172
|
+
staleTime: 1e4
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
staleTime: 1e4
|
|
176
|
+
}), {
|
|
177
|
+
status: 200,
|
|
178
|
+
headers: {
|
|
179
|
+
"content-type": "application/json"
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
try {
|
|
184
|
+
const router = createRouter({
|
|
185
|
+
routeTree: createNestedHeadRouteTree(),
|
|
186
|
+
history
|
|
187
|
+
});
|
|
188
|
+
await router.load();
|
|
189
|
+
expect(fetchCalls).toEqual(["/head-api?href=%2Fposts%2Falpha"]);
|
|
190
|
+
expect(router.state.head).toEqual([
|
|
191
|
+
{ tag: "title", children: "Alpha" }
|
|
192
|
+
]);
|
|
193
|
+
} finally {
|
|
194
|
+
globalThis.fetch = originalFetch;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
test("reuses a fresh root server head across navigations without refetching", async () => {
|
|
198
|
+
const history = createMemoryHistory({
|
|
199
|
+
initialEntries: ["/"]
|
|
200
|
+
});
|
|
201
|
+
const fetchCalls = [];
|
|
202
|
+
const originalFetch = globalThis.fetch;
|
|
203
|
+
globalThis.fetch = async (input) => {
|
|
204
|
+
fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
|
|
205
|
+
return new Response(JSON.stringify({
|
|
206
|
+
head: [
|
|
207
|
+
{ tag: "title", children: "Site" }
|
|
208
|
+
],
|
|
209
|
+
routeHeads: [
|
|
210
|
+
{
|
|
211
|
+
routeId: "__root__",
|
|
212
|
+
head: [{ tag: "title", children: "Site" }],
|
|
213
|
+
staleTime: 60000
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
staleTime: 60000
|
|
217
|
+
}), {
|
|
218
|
+
status: 200,
|
|
219
|
+
headers: {
|
|
220
|
+
"content-type": "application/json"
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
try {
|
|
225
|
+
const router = createRouter({
|
|
226
|
+
routeTree: createRootServerHeadTree(),
|
|
227
|
+
history
|
|
228
|
+
});
|
|
229
|
+
await router.load();
|
|
230
|
+
await router.navigate({
|
|
231
|
+
to: "/about"
|
|
232
|
+
});
|
|
233
|
+
expect(fetchCalls).toEqual(["/head-api?href=%2F"]);
|
|
234
|
+
} finally {
|
|
235
|
+
globalThis.fetch = originalFetch;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
test("seeds the route head cache from the dehydrated snapshot", async () => {
|
|
239
|
+
const history = createMemoryHistory({
|
|
240
|
+
initialEntries: ["/about"]
|
|
241
|
+
});
|
|
242
|
+
const fetchCalls = [];
|
|
243
|
+
const originalFetch = globalThis.fetch;
|
|
244
|
+
const globalWithWindow = globalThis;
|
|
245
|
+
const originalWindow = globalWithWindow.window;
|
|
246
|
+
globalThis.fetch = async (input) => {
|
|
247
|
+
fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
|
|
248
|
+
return new Response(JSON.stringify({
|
|
249
|
+
head: [
|
|
250
|
+
{ tag: "title", children: "Site" }
|
|
251
|
+
],
|
|
252
|
+
routeHeads: [
|
|
253
|
+
{
|
|
254
|
+
routeId: "__root__",
|
|
255
|
+
head: [{ tag: "title", children: "Site" }],
|
|
256
|
+
staleTime: 60000
|
|
257
|
+
}
|
|
258
|
+
],
|
|
259
|
+
staleTime: 60000
|
|
260
|
+
}), {
|
|
261
|
+
status: 200,
|
|
262
|
+
headers: {
|
|
263
|
+
"content-type": "application/json"
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
globalWithWindow.window = {
|
|
268
|
+
__RICHIE_ROUTER_HEAD__: {
|
|
269
|
+
href: "/about",
|
|
270
|
+
head: [
|
|
271
|
+
{ tag: "title", children: "Site" }
|
|
272
|
+
],
|
|
273
|
+
routeHeads: [
|
|
274
|
+
{
|
|
275
|
+
routeId: "__root__",
|
|
276
|
+
head: [{ tag: "title", children: "Site" }],
|
|
277
|
+
staleTime: 60000
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
try {
|
|
283
|
+
const router = createRouter({
|
|
284
|
+
routeTree: createRootServerHeadTree(),
|
|
285
|
+
history
|
|
286
|
+
});
|
|
287
|
+
await router.load();
|
|
288
|
+
expect(fetchCalls).toHaveLength(0);
|
|
289
|
+
expect(router.state.head).toEqual([
|
|
290
|
+
{ tag: "title", children: "Site" }
|
|
291
|
+
]);
|
|
292
|
+
} finally {
|
|
293
|
+
globalThis.fetch = originalFetch;
|
|
294
|
+
if (originalWindow === undefined) {
|
|
295
|
+
Reflect.deleteProperty(globalWithWindow, "window");
|
|
296
|
+
} else {
|
|
297
|
+
originalWindow.__RICHIE_ROUTER_HEAD__ = undefined;
|
|
298
|
+
globalWithWindow.window = originalWindow;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
test("reuses the initial merged head snapshot without fetching when the branch has no inline head", async () => {
|
|
303
|
+
const history = createMemoryHistory({
|
|
304
|
+
initialEntries: ["/about"]
|
|
305
|
+
});
|
|
306
|
+
const fetchCalls = [];
|
|
307
|
+
const originalFetch = globalThis.fetch;
|
|
308
|
+
const globalWithWindow = globalThis;
|
|
309
|
+
const originalWindow = globalWithWindow.window;
|
|
310
|
+
globalThis.fetch = async (input) => {
|
|
311
|
+
fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
|
|
312
|
+
return new Response("{}", {
|
|
313
|
+
status: 500
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
globalWithWindow.window = {
|
|
317
|
+
__RICHIE_ROUTER_HEAD__: {
|
|
318
|
+
href: "/about",
|
|
319
|
+
head: [
|
|
320
|
+
{ tag: "title", children: "About from SSR" }
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
try {
|
|
325
|
+
const router = createRouter({
|
|
326
|
+
routeTree: createTestRouteTree({ serverHead: true }),
|
|
327
|
+
history
|
|
328
|
+
});
|
|
329
|
+
await router.load();
|
|
330
|
+
expect(fetchCalls).toHaveLength(0);
|
|
331
|
+
expect(router.state.head).toEqual([
|
|
332
|
+
{ tag: "title", children: "About from SSR" }
|
|
333
|
+
]);
|
|
334
|
+
} finally {
|
|
335
|
+
globalThis.fetch = originalFetch;
|
|
336
|
+
if (originalWindow === undefined) {
|
|
337
|
+
Reflect.deleteProperty(globalWithWindow, "window");
|
|
338
|
+
} else {
|
|
339
|
+
originalWindow.__RICHIE_ROUTER_HEAD__ = undefined;
|
|
340
|
+
globalWithWindow.window = originalWindow;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
test("uses the merged document head without route fallback requests when the branch has no inline head", async () => {
|
|
345
|
+
const history = createMemoryHistory({
|
|
346
|
+
initialEntries: ["/posts/alpha"]
|
|
347
|
+
});
|
|
348
|
+
const fetchCalls = [];
|
|
349
|
+
const originalFetch = globalThis.fetch;
|
|
350
|
+
globalThis.fetch = async (input) => {
|
|
351
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
352
|
+
fetchCalls.push(url);
|
|
353
|
+
return new Response(JSON.stringify({
|
|
354
|
+
head: [
|
|
355
|
+
{ tag: "meta", name: "description", content: "Nested server head" },
|
|
356
|
+
{ tag: "title", children: "Alpha" }
|
|
357
|
+
],
|
|
358
|
+
staleTime: 1e4
|
|
359
|
+
}), {
|
|
360
|
+
status: 200,
|
|
361
|
+
headers: {
|
|
362
|
+
"content-type": "application/json"
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
try {
|
|
367
|
+
const router = createRouter({
|
|
368
|
+
routeTree: createNestedServerHeadTree(),
|
|
369
|
+
history
|
|
370
|
+
});
|
|
371
|
+
await router.load();
|
|
372
|
+
expect(fetchCalls).toEqual(["/head-api?href=%2Fposts%2Falpha"]);
|
|
373
|
+
expect(router.state.head).toEqual([
|
|
374
|
+
{ tag: "meta", name: "description", content: "Nested server head" },
|
|
375
|
+
{ tag: "title", children: "Alpha" }
|
|
376
|
+
]);
|
|
377
|
+
} finally {
|
|
378
|
+
globalThis.fetch = originalFetch;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
test("keeps loadRouteHead as the route-scoped override path", async () => {
|
|
382
|
+
const history = createMemoryHistory({
|
|
383
|
+
initialEntries: ["/about"]
|
|
384
|
+
});
|
|
385
|
+
const fetchCalls = [];
|
|
386
|
+
const loadRouteHeadCalls = [];
|
|
387
|
+
const originalFetch = globalThis.fetch;
|
|
388
|
+
globalThis.fetch = async (input) => {
|
|
389
|
+
fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
|
|
390
|
+
return new Response("{}", {
|
|
391
|
+
status: 500
|
|
392
|
+
});
|
|
393
|
+
};
|
|
394
|
+
try {
|
|
395
|
+
const router = createRouter({
|
|
396
|
+
routeTree: createTestRouteTree({ serverHead: true }),
|
|
397
|
+
history,
|
|
398
|
+
loadRouteHead: async ({ routeId }) => {
|
|
399
|
+
loadRouteHeadCalls.push(routeId);
|
|
400
|
+
return {
|
|
401
|
+
head: [
|
|
402
|
+
{ tag: "title", children: "About override" }
|
|
403
|
+
],
|
|
404
|
+
staleTime: 1000
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
await router.load();
|
|
409
|
+
expect(loadRouteHeadCalls).toEqual(["/about"]);
|
|
410
|
+
expect(fetchCalls).toHaveLength(0);
|
|
411
|
+
expect(router.state.head).toEqual([
|
|
412
|
+
{ tag: "title", children: "About override" }
|
|
413
|
+
]);
|
|
414
|
+
} finally {
|
|
415
|
+
globalThis.fetch = originalFetch;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
});
|
package/dist/types/router.d.ts
CHANGED
|
@@ -125,6 +125,7 @@ export interface RouterState {
|
|
|
125
125
|
export interface RouterOptions<TRouteTree extends AnyRoute> {
|
|
126
126
|
routeTree: TRouteTree;
|
|
127
127
|
history?: RouterHistory;
|
|
128
|
+
basePath?: string;
|
|
128
129
|
defaultPreload?: 'intent' | 'render' | false;
|
|
129
130
|
defaultPreloadDelay?: number;
|
|
130
131
|
defaultPendingMs?: number;
|
|
@@ -170,6 +171,8 @@ export declare class Router<TRouteTree extends AnyRoute> {
|
|
|
170
171
|
private readonly headCache;
|
|
171
172
|
private readonly parseSearch;
|
|
172
173
|
private readonly stringifySearch;
|
|
174
|
+
private readonly basePath;
|
|
175
|
+
private initialHeadSnapshot?;
|
|
173
176
|
private started;
|
|
174
177
|
private unsubscribeHistory?;
|
|
175
178
|
constructor(options: RouterOptions<TRouteTree>);
|
|
@@ -184,6 +187,8 @@ export declare class Router<TRouteTree extends AnyRoute> {
|
|
|
184
187
|
preloadRoute<TTo extends RoutePaths>(options: NavigateOptions<TTo>): Promise<void>;
|
|
185
188
|
invalidate(): Promise<void>;
|
|
186
189
|
buildHref<TTo extends RoutePaths>(options: NavigateOptions<TTo>): string;
|
|
190
|
+
private buildLocation;
|
|
191
|
+
private buildLocationHref;
|
|
187
192
|
private readLocation;
|
|
188
193
|
private applyTrailingSlash;
|
|
189
194
|
private notify;
|
|
@@ -192,14 +197,21 @@ export declare class Router<TRouteTree extends AnyRoute> {
|
|
|
192
197
|
private resolveSearch;
|
|
193
198
|
resolveLocation(location: ParsedLocation, options?: {
|
|
194
199
|
request?: Request;
|
|
200
|
+
initialHeadSnapshot?: DehydratedHeadState;
|
|
195
201
|
}): Promise<{
|
|
196
202
|
matches: InternalRouteMatch[];
|
|
197
203
|
head: HeadConfig;
|
|
198
204
|
error: unknown;
|
|
199
205
|
}>;
|
|
200
206
|
private resolveLocationHead;
|
|
207
|
+
private getRouteHeadCacheKey;
|
|
208
|
+
private getCachedRouteHead;
|
|
209
|
+
private setRouteHeadCache;
|
|
210
|
+
private seedHeadCacheFromRouteHeads;
|
|
211
|
+
private cacheRouteHeadsFromDocument;
|
|
201
212
|
private loadRouteHead;
|
|
202
213
|
private fetchRouteHead;
|
|
214
|
+
private fetchDocumentHead;
|
|
203
215
|
private commitLocation;
|
|
204
216
|
private restoreScroll;
|
|
205
217
|
private handleHistoryChange;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@richie-router/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "React runtime, components, and hooks for Richie Router",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@richie-router/core": "^0.1.
|
|
16
|
+
"@richie-router/core": "^0.1.3"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"react": "^19"
|