@richie-router/react 0.1.4 → 0.1.6

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.
@@ -541,7 +541,7 @@ class Router {
541
541
  return response.head;
542
542
  }
543
543
  async fetchRouteHead(route, params, search) {
544
- const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
544
+ const basePath = prependBasePathToHref(import_core.resolveHostedRoutingConfig(this.routeTree.hostedRouting).headBasePath, this.basePath);
545
545
  const searchParams = new URLSearchParams({
546
546
  routeId: route.fullPath,
547
547
  params: JSON.stringify(params),
@@ -557,7 +557,7 @@ class Router {
557
557
  return await response.json();
558
558
  }
559
559
  async fetchDocumentHead(location) {
560
- const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
560
+ const basePath = prependBasePathToHref(import_core.resolveHostedRoutingConfig(this.routeTree.hostedRouting).headBasePath, this.basePath);
561
561
  const searchParams = new URLSearchParams({
562
562
  href: prependBasePathToHref(location.href, this.basePath)
563
563
  });
@@ -45,6 +45,12 @@ function createTestRouteTree(options) {
45
45
  component: () => null
46
46
  });
47
47
  aboutRoute._setServerHead(options?.serverHead);
48
+ if (options?.headBasePath) {
49
+ rootRoute._setHostedRouting({
50
+ headBasePath: options.headBasePath,
51
+ passthrough: [options.headBasePath]
52
+ });
53
+ }
48
54
  return rootRoute._addFileChildren({
49
55
  index: indexRoute,
50
56
  about: aboutRoute
@@ -129,6 +135,21 @@ function createLinkTestRouteTree(component) {
129
135
  posts: postsRoute
130
136
  });
131
137
  }
138
+ function createStaticAndDynamicSiblingRouteTree() {
139
+ const rootRoute = import_router.createRootRoute({
140
+ component: () => null
141
+ });
142
+ const registerRoute = import_router.createFileRoute("/register")({
143
+ component: () => null
144
+ });
145
+ const usernameRoute = import_router.createFileRoute("/$username")({
146
+ component: () => null
147
+ });
148
+ return rootRoute._addFileChildren({
149
+ register: registerRoute,
150
+ username: usernameRoute
151
+ });
152
+ }
132
153
  function renderLinkMarkup(initialEntry, component) {
133
154
  const history = import_router.createMemoryHistory({
134
155
  initialEntries: [initialEntry]
@@ -234,6 +255,38 @@ import_bun_test.describe("createRouter basePath", () => {
234
255
  globalThis.fetch = originalFetch;
235
256
  }
236
257
  });
258
+ import_bun_test.test("uses the route tree headBasePath for document head requests", async () => {
259
+ const history = import_router.createMemoryHistory({
260
+ initialEntries: ["/project/about"]
261
+ });
262
+ const fetchCalls = [];
263
+ const originalFetch = globalThis.fetch;
264
+ globalThis.fetch = async (input) => {
265
+ fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
266
+ return new Response(JSON.stringify({
267
+ head: [],
268
+ routeHeads: [
269
+ { routeId: "/about", head: [] }
270
+ ]
271
+ }), {
272
+ status: 200,
273
+ headers: {
274
+ "content-type": "application/json"
275
+ }
276
+ });
277
+ };
278
+ try {
279
+ const router = import_router.createRouter({
280
+ routeTree: createTestRouteTree({ serverHead: true, headBasePath: "/meta" }),
281
+ history,
282
+ basePath: "/project"
283
+ });
284
+ await router.load();
285
+ import_bun_test.expect(fetchCalls).toEqual(["/project/meta?href=%2Fproject%2Fabout"]);
286
+ } finally {
287
+ globalThis.fetch = originalFetch;
288
+ }
289
+ });
237
290
  import_bun_test.test("uses one document head request and preserves inline head precedence", async () => {
238
291
  const history = import_router.createMemoryHistory({
239
292
  initialEntries: ["/posts/alpha"]
@@ -534,6 +587,28 @@ import_bun_test.describe("Link active state", () => {
534
587
  import_bun_test.expect(markup).not.toContain('class="active"');
535
588
  });
536
589
  });
590
+ import_bun_test.describe("route matching precedence", () => {
591
+ import_bun_test.test("prefers a static sibling over a dynamic sibling", () => {
592
+ const router = import_router.createRouter({
593
+ routeTree: createStaticAndDynamicSiblingRouteTree(),
594
+ history: import_router.createMemoryHistory({
595
+ initialEntries: ["/register"]
596
+ })
597
+ });
598
+ import_bun_test.expect(router.state.matches.at(-1)?.route.fullPath).toBe("/register");
599
+ import_bun_test.expect(router.state.matches.at(-1)?.params).toEqual({});
600
+ });
601
+ import_bun_test.test("falls back to the dynamic sibling when no static sibling matches", () => {
602
+ const router = import_router.createRouter({
603
+ routeTree: createStaticAndDynamicSiblingRouteTree(),
604
+ history: import_router.createMemoryHistory({
605
+ initialEntries: ["/richie"]
606
+ })
607
+ });
608
+ import_bun_test.expect(router.state.matches.at(-1)?.route.fullPath).toBe("/$username");
609
+ import_bun_test.expect(router.state.matches.at(-1)?.params).toEqual({ username: "richie" });
610
+ });
611
+ });
537
612
  import_bun_test.describe("useMatchRoute", () => {
538
613
  import_bun_test.test("returns matched params for exact matches", () => {
539
614
  const markup = renderLinkMarkup("/posts/alpha", () => {
@@ -14,7 +14,8 @@ import {
14
14
  matchRouteTree,
15
15
  notFound,
16
16
  redirect,
17
- resolveHeadConfig
17
+ resolveHeadConfig,
18
+ resolveHostedRoutingConfig
18
19
  } from "@richie-router/core";
19
20
  import {
20
21
  createBrowserHistory,
@@ -464,7 +465,7 @@ class Router {
464
465
  return response.head;
465
466
  }
466
467
  async fetchRouteHead(route, params, search) {
467
- const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
468
+ const basePath = prependBasePathToHref(resolveHostedRoutingConfig(this.routeTree.hostedRouting).headBasePath, this.basePath);
468
469
  const searchParams = new URLSearchParams({
469
470
  routeId: route.fullPath,
470
471
  params: JSON.stringify(params),
@@ -480,7 +481,7 @@ class Router {
480
481
  return await response.json();
481
482
  }
482
483
  async fetchDocumentHead(location) {
483
- const basePath = this.options.headBasePath ?? prependBasePathToHref("/head-api", this.basePath);
484
+ const basePath = prependBasePathToHref(resolveHostedRoutingConfig(this.routeTree.hostedRouting).headBasePath, this.basePath);
484
485
  const searchParams = new URLSearchParams({
485
486
  href: prependBasePathToHref(location.href, this.basePath)
486
487
  });
@@ -23,6 +23,12 @@ function createTestRouteTree(options) {
23
23
  component: () => null
24
24
  });
25
25
  aboutRoute._setServerHead(options?.serverHead);
26
+ if (options?.headBasePath) {
27
+ rootRoute._setHostedRouting({
28
+ headBasePath: options.headBasePath,
29
+ passthrough: [options.headBasePath]
30
+ });
31
+ }
26
32
  return rootRoute._addFileChildren({
27
33
  index: indexRoute,
28
34
  about: aboutRoute
@@ -107,6 +113,21 @@ function createLinkTestRouteTree(component) {
107
113
  posts: postsRoute
108
114
  });
109
115
  }
116
+ function createStaticAndDynamicSiblingRouteTree() {
117
+ const rootRoute = createRootRoute({
118
+ component: () => null
119
+ });
120
+ const registerRoute = createFileRoute("/register")({
121
+ component: () => null
122
+ });
123
+ const usernameRoute = createFileRoute("/$username")({
124
+ component: () => null
125
+ });
126
+ return rootRoute._addFileChildren({
127
+ register: registerRoute,
128
+ username: usernameRoute
129
+ });
130
+ }
110
131
  function renderLinkMarkup(initialEntry, component) {
111
132
  const history = createMemoryHistory({
112
133
  initialEntries: [initialEntry]
@@ -212,6 +233,38 @@ describe("createRouter basePath", () => {
212
233
  globalThis.fetch = originalFetch;
213
234
  }
214
235
  });
236
+ test("uses the route tree headBasePath for document head requests", async () => {
237
+ const history = createMemoryHistory({
238
+ initialEntries: ["/project/about"]
239
+ });
240
+ const fetchCalls = [];
241
+ const originalFetch = globalThis.fetch;
242
+ globalThis.fetch = async (input) => {
243
+ fetchCalls.push(typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url);
244
+ return new Response(JSON.stringify({
245
+ head: [],
246
+ routeHeads: [
247
+ { routeId: "/about", head: [] }
248
+ ]
249
+ }), {
250
+ status: 200,
251
+ headers: {
252
+ "content-type": "application/json"
253
+ }
254
+ });
255
+ };
256
+ try {
257
+ const router = createRouter({
258
+ routeTree: createTestRouteTree({ serverHead: true, headBasePath: "/meta" }),
259
+ history,
260
+ basePath: "/project"
261
+ });
262
+ await router.load();
263
+ expect(fetchCalls).toEqual(["/project/meta?href=%2Fproject%2Fabout"]);
264
+ } finally {
265
+ globalThis.fetch = originalFetch;
266
+ }
267
+ });
215
268
  test("uses one document head request and preserves inline head precedence", async () => {
216
269
  const history = createMemoryHistory({
217
270
  initialEntries: ["/posts/alpha"]
@@ -512,6 +565,28 @@ describe("Link active state", () => {
512
565
  expect(markup).not.toContain('class="active"');
513
566
  });
514
567
  });
568
+ describe("route matching precedence", () => {
569
+ test("prefers a static sibling over a dynamic sibling", () => {
570
+ const router = createRouter({
571
+ routeTree: createStaticAndDynamicSiblingRouteTree(),
572
+ history: createMemoryHistory({
573
+ initialEntries: ["/register"]
574
+ })
575
+ });
576
+ expect(router.state.matches.at(-1)?.route.fullPath).toBe("/register");
577
+ expect(router.state.matches.at(-1)?.params).toEqual({});
578
+ });
579
+ test("falls back to the dynamic sibling when no static sibling matches", () => {
580
+ const router = createRouter({
581
+ routeTree: createStaticAndDynamicSiblingRouteTree(),
582
+ history: createMemoryHistory({
583
+ initialEntries: ["/richie"]
584
+ })
585
+ });
586
+ expect(router.state.matches.at(-1)?.route.fullPath).toBe("/$username");
587
+ expect(router.state.matches.at(-1)?.params).toEqual({ username: "richie" });
588
+ });
589
+ });
515
590
  describe("useMatchRoute", () => {
516
591
  test("returns matched params for exact matches", () => {
517
592
  const markup = renderLinkMarkup("/posts/alpha", () => {
@@ -150,7 +150,6 @@ export interface RouterOptions<TRouteTree extends AnyRoute> {
150
150
  defaultErrorComponent?: AnyComponent;
151
151
  scrollRestoration?: boolean;
152
152
  scrollToTopSelectors?: string[];
153
- headBasePath?: string;
154
153
  trailingSlash?: 'always' | 'never' | 'preserve';
155
154
  parseSearch?: (searchStr: string) => Record<string, unknown>;
156
155
  stringifySearch?: (search: Record<string, unknown>) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@richie-router/react",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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.3"
16
+ "@richie-router/core": "^0.1.5"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "react": "^19"