1import type { RouteNode } from './Route'; 2import { matchDeepDynamicRouteName, matchDynamicName } from './matchers'; 3 4export type Screen = 5 | string 6 | { 7 path: string; 8 screens: Record<string, Screen>; 9 _route?: RouteNode; 10 initialRouteName?: string; 11 }; 12 13// `[page]` -> `:page` 14// `page` -> `page` 15function convertDynamicRouteToReactNavigation(segment: string): string { 16 // NOTE(EvanBacon): To support shared routes we preserve group segments. 17 if (segment === 'index') { 18 return ''; 19 } 20 21 const rest = matchDeepDynamicRouteName(segment); 22 if (rest != null) { 23 return '*' + rest; 24 } 25 const dynamicName = matchDynamicName(segment); 26 27 if (dynamicName != null) { 28 return `:${dynamicName}`; 29 } 30 31 return segment; 32} 33 34function parseRouteSegments(segments: string): string { 35 return ( 36 // NOTE(EvanBacon): When there are nested routes without layouts 37 // the node.route will be something like `app/home/index` 38 // this needs to be split to ensure each segment is parsed correctly. 39 segments 40 .split('/') 41 // Convert each segment to a React Navigation format. 42 .map(convertDynamicRouteToReactNavigation) 43 // Remove any empty paths from groups or index routes. 44 .filter(Boolean) 45 // Join to return as a path. 46 .join('/') 47 ); 48} 49 50function convertRouteNodeToScreen(node: RouteNode, metaOnly: boolean): Screen { 51 const path = parseRouteSegments(node.route); 52 if (!node.children.length) { 53 if (!metaOnly) { 54 return { 55 path, 56 screens: {}, 57 _route: node, 58 }; 59 } 60 return path; 61 } 62 const screens = getReactNavigationScreensConfig(node.children, metaOnly); 63 64 const screen: Screen = { 65 path, 66 screens, 67 // NOTE(EvanBacon): This is bad because it forces all Layout Routes 68 // to be loaded into memory. We should move towards a system where 69 // the initial route name is either loaded asynchronously in the Layout Route 70 // or defined via a file system convention. 71 initialRouteName: node.initialRouteName, 72 }; 73 74 if (!metaOnly) { 75 screen._route = node; 76 } 77 78 return screen; 79} 80 81export function getReactNavigationScreensConfig( 82 nodes: RouteNode[], 83 metaOnly: boolean 84): Record<string, Screen> { 85 return Object.fromEntries( 86 nodes.map((node) => [node.route, convertRouteNodeToScreen(node, metaOnly)] as const) 87 ); 88} 89 90export function getReactNavigationConfig( 91 routes: RouteNode, 92 metaOnly: boolean 93): { 94 initialRouteName?: string; 95 screens: Record<string, Screen>; 96} { 97 return { 98 initialRouteName: routes.initialRouteName, 99 screens: getReactNavigationScreensConfig(routes.children, metaOnly), 100 }; 101} 102