optional splat routes (#13547)

Co-authored-by: SteelRazor47 <36779933+SteelRazor47@users.noreply.github.com>
This commit is contained in:
Leonardo Benini
2025-11-05 17:17:09 +01:00
committed by GitHub
parent 423b6d94dc
commit 1762f73bd9
4 changed files with 35 additions and 9 deletions

View File

@@ -0,0 +1,6 @@
---
"@medusajs/admin-vite-plugin": patch
"@medusajs/dashboard": patch
---
feat(admin-vite-plugin,dashboard): support for react-router's splat and optional segments

View File

@@ -4,7 +4,11 @@ export function getRoute(file: string): string {
const importPath = normalizePath(file) const importPath = normalizePath(file)
return importPath return importPath
.replace(/.*\/admin\/(routes)/, "") .replace(/.*\/admin\/(routes)/, "")
.replace(/\[([^\]]+)\]/g, ":$1") .replace("[[*]]", "*?") // optional splat
.replace("[*]", "*") // splat
.replace(/\(([^\[\]\)]+)\)/g, "$1?") // optional static, (foo)
.replace(/\[\[([^\]]+)\]\]/g, ":$1?") // optional dynamic, [[foo]]
.replace(/\[([^\]]+)\]/g, ":$1") // dynamic, [foo]
.replace( .replace(
new RegExp( new RegExp(
`/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join( `/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join(

View File

@@ -42,6 +42,13 @@ type DashboardAppProps = {
plugins: DashboardPlugin[] plugins: DashboardPlugin[]
} }
/**
* Matches segments that are optional and at the end of the path.
* Example: /path/to/:id?
* Such paths can be added to the menu items without the optional segment.
*/
const OPTIONAL_LAST_SEGMENT_MATCH = /\/([^\/])+\?$/
export class DashboardApp { export class DashboardApp {
private widgets: WidgetMap private widgets: WidgetMap
private menus: MenuMap private menus: MenuMap
@@ -129,10 +136,11 @@ export class DashboardApp {
allMenuItems.sort((a, b) => a.path.length - b.path.length) allMenuItems.sort((a, b) => a.path.length - b.path.length)
allMenuItems.forEach((item) => { allMenuItems.forEach((item) => {
if (item.path.includes("/:")) { item.path = item.path.replace(OPTIONAL_LAST_SEGMENT_MATCH, "")
if (item.path.includes("/:") || item.path.endsWith("/*")) {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
console.warn( console.warn(
`[@medusajs/dashboard] Menu item for path "${item.path}" can't be added to the sidebar as it contains a parameter.` `[@medusajs/dashboard] Menu item for path "${item.path}" can't be added to the sidebar as it contains a mandatory parameter.`
) )
} }
return return

View File

@@ -37,9 +37,10 @@ const createBranchRoute = (segment: string): RouteObject => ({
const createLeafRoute = ( const createLeafRoute = (
Component: ComponentType, Component: ComponentType,
loader?: LoaderFunction, loader?: LoaderFunction,
handle?: object handle?: object,
path: string = ""
): RouteObject => ({ ): RouteObject => ({
path: "", path,
ErrorBoundary: ErrorBoundary, ErrorBoundary: ErrorBoundary,
async lazy() { async lazy() {
const result: { const result: {
@@ -149,7 +150,6 @@ const addRoute = (
if (isComponentSegment || remainingSegments.length === 0) { if (isComponentSegment || remainingSegments.length === 0) {
route.children ||= [] route.children ||= []
const leaf = createLeafRoute(Component, loader)
if (handle) { if (handle) {
route.handle = handle route.handle = handle
@@ -159,9 +159,17 @@ const addRoute = (
route.loader = loader route.loader = loader
} }
leaf.children = processParallelRoutes(parallelRoutes, currentFullPath) // Since splat segments must occur at the end of a route, react-router enforces the segment to be a leaf.
route.children.push(leaf) // Therefore we can't create a child leaf route with `path: ""` and must instead modify the route itself
if (currentSegment === "*?" || currentSegment === "*") {
const leaf = createLeafRoute(Component, loader, handle, currentSegment)
leaf.children = processParallelRoutes(parallelRoutes, currentFullPath)
Object.assign(route, leaf)
} else {
const leaf = createLeafRoute(Component, loader)
leaf.children = processParallelRoutes(parallelRoutes, currentFullPath)
route.children.push(leaf)
}
if (remainingSegments.length > 0) { if (remainingSegments.length > 0) {
addRoute( addRoute(
remainingSegments, remainingSegments,