Skip to content

Commit 85a7b2a

Browse files
authored
feat: add support for route handle (#37)
1 parent 58f6234 commit 85a7b2a

12 files changed

Lines changed: 117 additions & 4 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
node_modules
44
dist
55
.idea
6+
cache
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useMatches } from 'react-router-dom'
2+
3+
export function Breadcrumbs() {
4+
const matches = useMatches()
5+
6+
const crumbs: Array<string> = matches
7+
.filter((match) => Boolean((match.handle as any)?.crumb))
8+
.map((match) => (match.handle as any)?.crumb(match.params))
9+
10+
return (
11+
<nav>
12+
<ol className="flex space-x-2 text-gray-600 text-sm">
13+
{crumbs.map((crumb, index) => (
14+
<>
15+
<li key={index}>{crumb}</li>
16+
{index < crumbs.length - 1 && <li className="text-gray-300">/</li>}
17+
</>
18+
))}
19+
</ol>
20+
</nav>
21+
)
22+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Link, Outlet } from 'react-router-dom'
2+
3+
export default function UsersLayout() {
4+
return <Outlet />
5+
}
6+
7+
export const handle = {
8+
crumb: () => (
9+
<Link to="/users" className="text-blue-500">
10+
Users
11+
</Link>
12+
),
13+
}

example/src/routes/__panel/users/$user.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { NavLink, Outlet, useParams } from 'react-router-dom'
2+
import { Breadcrumbs } from '../../../components/Breadcrumbs'
23

34
export default function () {
45
const { user } = useParams()
6+
57
return (
68
<div>
7-
<h1 className="text-2xl">User: {user}</h1>
9+
<Breadcrumbs />
10+
11+
<h1 className="text-2xl mt-8">User: {user}</h1>
812

913
<div className="mt-8">
1014
<div className="flex border-b border-gray-200 text-gray-500">
@@ -41,3 +45,9 @@ export default function () {
4145
</div>
4246
)
4347
}
48+
49+
export const handle = {
50+
crumb: ({ user }: { user: string }) => (
51+
<span className="capitalize">{user}</span>
52+
),
53+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
export default function () {
22
return <h2 className="text-2xl">Overview</h2>
33
}
4+
5+
export const handle = {
6+
crumb: () => 'Overview',
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
export default function () {
22
return <h2 className="text-2xl">Profile</h2>
33
}
4+
5+
export const handle = {
6+
crumb: () => 'Profile',
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
export default function () {
22
return <h2 className="text-2xl">Settings</h2>
33
}
4+
5+
export const handle = {
6+
crumb: () => 'Settings',
7+
}

src/__snapshots__/buildRouteTree.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ RouteNode {
9696
},
9797
],
9898
"isDirectory": true,
99-
"layoutPath": undefined,
99+
"layoutPath": "example/src/routes/__panel/users.tsx",
100100
"name": "users",
101101
"path": "example/src/routes/__panel/users",
102102
},

src/__snapshots__/generateRoutesModule.test.ts.snap

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import { ErrorElement as EXAMPLE_SRC_ROUTES___PANEL_POSTS_$SLUG_ERROR_ELEMENT }
77
import { action as example_src_routes___panel_posts_create_ACTION } from '/example/src/routes/__panel/posts/create.tsx';
88
import { loader as example_src_routes___panel_posts_index_LOADER } from '/example/src/routes/__panel/posts/index.tsx';
99
import { ErrorBoundary as EXAMPLE_SRC_ROUTES___PANEL_POSTS_INDEX_ERROR_ELEMENT } from '/example/src/routes/__panel/posts/index.tsx';
10+
import { handle as example_src_routes___panel_users_HANDLE } from '/example/src/routes/__panel/users';
11+
import { handle as example_src_routes___panel_users_$user_HANDLE } from '/example/src/routes/__panel/users/$user';
12+
import { handle as example_src_routes___panel_users_$user_index_HANDLE } from '/example/src/routes/__panel/users/$user/index.tsx';
13+
import { handle as example_src_routes___panel_users_$user_profile_HANDLE } from '/example/src/routes/__panel/users/$user/profile.tsx';
14+
import { handle as example_src_routes___panel_users_$user_settings_HANDLE } from '/example/src/routes/__panel/users/$user/settings.tsx';
1015
1116
export const routes = [{
1217
\\"path\\": \\"/\\",
@@ -49,6 +54,7 @@ export const routes = [{
4954
]
5055
},
5156
{
57+
\\"element\\": React.createElement(React.lazy(() => import(\\"/example/src/routes/__panel/users.tsx\\"))),
5258
\\"path\\": \\"users\\",
5359
\\"children\\": [
5460
{
@@ -57,23 +63,28 @@ export const routes = [{
5763
\\"children\\": [
5864
{
5965
\\"index\\": true,
66+
\\"handle\\": example_src_routes___panel_users_$user_index_HANDLE,
6067
\\"element\\": React.createElement(React.lazy(() => import(\\"/example/src/routes/__panel/users/$user/index.tsx\\")))
6168
},
6269
{
6370
\\"path\\": \\"profile\\",
71+
\\"handle\\": example_src_routes___panel_users_$user_profile_HANDLE,
6472
\\"element\\": React.createElement(React.lazy(() => import(\\"/example/src/routes/__panel/users/$user/profile.tsx\\")))
6573
},
6674
{
6775
\\"path\\": \\"settings\\",
76+
\\"handle\\": example_src_routes___panel_users_$user_settings_HANDLE,
6877
\\"element\\": React.createElement(React.lazy(() => import(\\"/example/src/routes/__panel/users/$user/settings.tsx\\")))
6978
}
70-
]
79+
],
80+
\\"handle\\": example_src_routes___panel_users_$user_HANDLE
7181
},
7282
{
7383
\\"index\\": true,
7484
\\"element\\": React.createElement(React.lazy(() => import(\\"/example/src/routes/__panel/users/index.tsx\\")))
7585
}
76-
]
86+
],
87+
\\"handle\\": example_src_routes___panel_users_HANDLE
7788
}
7889
]
7990
},

src/generateRoutesModule.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
hasAction,
77
hasErrorBoundary,
88
hasErrorElement,
9+
hasHandle,
910
hasLoader,
1011
normalizeFilenameToRoute,
1112
toAbsolutePath,
@@ -40,12 +41,20 @@ function createRouteObject(node: RouteNode) {
4041
}
4142

4243
function createLayoutRoute(node: RouteNode): RouteObject {
44+
let handle: string | undefined = undefined
45+
46+
if (node.layoutPath) {
47+
const code = fs.readFileSync(toAbsolutePath(node.layoutPath), 'utf8')
48+
handle = resolveHandle(node.path, code)
49+
}
50+
4351
return {
4452
element: node.layoutPath && createRouteElement(node.layoutPath),
4553
path: node.name.startsWith('__')
4654
? undefined
4755
: normalizeFilenameToRoute(node.name),
4856
children: node.children.map((child) => createRouteObject(child)),
57+
handle,
4958
}
5059
}
5160

@@ -62,6 +71,7 @@ function createPageRoute(node: RouteNode): RouteObject {
6271
loader: resolveLoader(node.path, code) as LoaderFunction | undefined,
6372
action: resolveAction(node.path, code) as ActionFunction | undefined,
6473
errorElement: resolveErrorElement(node.path, code),
74+
handle: resolveHandle(node.path, code),
6575
element: createRouteElement(node.path),
6676
}
6777
}
@@ -117,3 +127,15 @@ function resolveErrorElement(filePath: string, code: string) {
117127

118128
return undefined
119129
}
130+
131+
function resolveHandle(filePath: string, code: string) {
132+
if (hasHandle(code)) {
133+
const importName = createImportName(filePath, 'HANDLE')
134+
135+
imports.push(`import { handle as ${importName} } from '/${filePath}';`)
136+
137+
return `::${importName}::`
138+
}
139+
140+
return undefined
141+
}

0 commit comments

Comments
 (0)