diff --git a/.changeset/quiet-topology-order.md b/.changeset/quiet-topology-order.md new file mode 100644 index 0000000..e0ec1c9 --- /dev/null +++ b/.changeset/quiet-topology-order.md @@ -0,0 +1,6 @@ +--- +"rsbuild-plugin-react-router": patch +--- + +Preserve route topology declaration order during development so reordering route +entries is detected as a topology change. diff --git a/src/route-watch.ts b/src/route-watch.ts index e726fec..29a49fe 100644 --- a/src/route-watch.ts +++ b/src/route-watch.ts @@ -61,19 +61,20 @@ export const createRouteManifestSnapshot = ( routes: Record ): Set => new Set( - Object.entries(routes) - .sort(([left], [right]) => left.localeCompare(right)) - .map(([routeId, route]) => - JSON.stringify([ - routeId, - route.id, - route.parentId ?? null, - route.path ?? null, - route.index ?? null, - route.caseSensitive ?? null, - route.file, - ]) - ) + // React Router uses sibling declaration order as a match tiebreaker, so the + // snapshot must preserve route-manifest insertion order. + Object.entries(routes).map(([routeId, route], order) => + JSON.stringify([ + order, + routeId, + route.id, + route.parentId ?? null, + route.path ?? null, + route.index ?? null, + route.caseSensitive ?? null, + route.file, + ]) + ) ); export const ensureDevRestartMarker = async ( diff --git a/tests/route-watch.test.ts b/tests/route-watch.test.ts index a9740e4..2315c76 100644 --- a/tests/route-watch.test.ts +++ b/tests/route-watch.test.ts @@ -170,27 +170,39 @@ describe('route watch topology snapshot', () => { ); }); - it('is stable for equivalent route manifests with different object insertion order', () => { + it('changes when sibling declaration order changes', () => { const first = createRouteManifestSnapshot({ root: { id: 'root', path: '', file: 'root.tsx' }, - 'routes/demo': { - id: 'routes/demo', + 'routes/a': { + id: 'routes/a', parentId: 'root', - path: 'demo', - file: 'routes/demo.tsx', + path: ':value', + file: 'routes/a.tsx', + }, + 'routes/b': { + id: 'routes/b', + parentId: 'root', + path: ':value', + file: 'routes/b.tsx', }, }); const second = createRouteManifestSnapshot({ - 'routes/demo': { - id: 'routes/demo', + root: { id: 'root', path: '', file: 'root.tsx' }, + 'routes/b': { + id: 'routes/b', parentId: 'root', - path: 'demo', - file: 'routes/demo.tsx', + path: ':value', + file: 'routes/b.tsx', + }, + 'routes/a': { + id: 'routes/a', + parentId: 'root', + path: ':value', + file: 'routes/a.tsx', }, - root: { id: 'root', path: '', file: 'root.tsx' }, }); - expect(second).toEqual(first); + expect(second).not.toEqual(first); }); });