Skip to content

Commit 19a98fa

Browse files
authored
fix: order routes before navigating in preview (#5311)
Ref #5223 Navigating within preview didn't work well because we relied on order of creation and more inclusive routes like `/*` could match before `/my-page`. Here fixed it with routes sorting. The algorithm is very simplified but covers our usecases within builder.
1 parent 395ebc4 commit 19a98fa

File tree

3 files changed

+97
-1
lines changed

3 files changed

+97
-1
lines changed

apps/builder/app/canvas/interceptor.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
$selectedPageHash,
1212
} from "~/shared/nano-states";
1313
import { $currentSystem, updateCurrentSystem } from "~/shared/system";
14+
import { comparePatterns } from "./shared/routing-priority";
1415

1516
const isAbsoluteUrl = (href: string) => {
1617
try {
@@ -55,7 +56,11 @@ const switchPageAndUpdateSystem = (href: string, formData?: FormData) => {
5556
}
5657
}
5758
const pageHref = new URL(href, "https://any-valid.url");
58-
for (const page of [pages.homePage, ...pages.pages]) {
59+
// sort pages before matching to not depend on order of page creation
60+
const sortedPages = [pages.homePage, ...pages.pages].toSorted(
61+
(leftPage, rightPage) => comparePatterns(leftPage.path, rightPage.path)
62+
);
63+
for (const page of sortedPages) {
5964
const pagePath = getPagePath(page.id, pages);
6065
const params = matchPathnamePattern(pagePath, pageHref.pathname);
6166
if (params) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { test, expect } from "vitest";
2+
import { comparePatterns } from "./routing-priority";
3+
4+
test("sort top-level patterns", () => {
5+
const patterns = ["/foo*", "/:foo", "/foo"];
6+
const expected = ["/foo", "/:foo", "/foo*"];
7+
expect(patterns.toSorted(comparePatterns)).toEqual(expected);
8+
});
9+
10+
test("sort static paths", () => {
11+
const patterns = ["/a/z", "/a/b/c", "/a/c", "/a/b"];
12+
const expected = ["/a/b", "/a/c", "/a/z", "/a/b/c"];
13+
expect(patterns.toSorted(comparePatterns)).toEqual(expected);
14+
});
15+
16+
test("sort mixed static, dynamic, spread at multiple levels", () => {
17+
const patterns = [
18+
"/foo",
19+
"/:id",
20+
"/bar*",
21+
"/foo/bar",
22+
"/foo/:id",
23+
"/foo/bar*",
24+
];
25+
const expected = [
26+
// static first-segment
27+
"/foo",
28+
"/foo/bar",
29+
"/foo/:id",
30+
"/foo/bar*",
31+
// dynamic then spread at top level
32+
"/:id",
33+
"/bar*",
34+
];
35+
expect(patterns.toSorted(comparePatterns)).toEqual(expected);
36+
});
37+
38+
test("sort deeply nested mixed segments", () => {
39+
const patterns = ["/u/bar", "/u/:id", "/u/bar/b", "/u/:id/c", "/u/bar/*"];
40+
const expected = [
41+
// static second-segment
42+
"/u/bar",
43+
"/u/bar/b",
44+
"/u/bar/*",
45+
// dynamic second-segment
46+
"/u/:id",
47+
"/u/:id/c",
48+
];
49+
expect(patterns.toSorted(comparePatterns)).toEqual(expected);
50+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const STATIC = 1;
2+
const DYNAMIC = 2;
3+
const SPREAD = 3;
4+
5+
const getSegmentScore = (segment: string) => {
6+
// give spread the least priority
7+
if (segment.endsWith("*")) {
8+
return SPREAD;
9+
}
10+
// sort dynamic segments before splat
11+
if (segment.startsWith(":")) {
12+
return DYNAMIC;
13+
}
14+
// sort static routes before dynamic routes
15+
return STATIC;
16+
};
17+
18+
export const comparePatterns = (leftPattern: string, rightPattern: string) => {
19+
const leftSegments = leftPattern.split("/");
20+
const rightSegments = rightPattern.split("/");
21+
const commonLength = Math.min(leftSegments.length, rightSegments.length);
22+
23+
// compare each segment first
24+
for (let index = 0; index < commonLength; index++) {
25+
const leftScore = getSegmentScore(leftSegments[index]);
26+
const rightScore = getSegmentScore(rightSegments[index]);
27+
if (leftScore !== rightScore) {
28+
return leftScore - rightScore;
29+
}
30+
}
31+
32+
// compare amount of segments
33+
const leftLength = leftSegments.length;
34+
const rightLength = rightSegments.length;
35+
if (leftLength !== rightLength) {
36+
return leftLength - rightLength;
37+
}
38+
39+
// sort alphabetically
40+
return leftPattern.localeCompare(rightPattern);
41+
};

0 commit comments

Comments
 (0)