Skip to content

Instantly share code, notes, and snippets.

@heiso
Last active October 13, 2023 22:57
Show Gist options
  • Save heiso/322050c72dde922293bb565c61d6c8d7 to your computer and use it in GitHub Desktop.
Save heiso/322050c72dde922293bb565c61d6c8d7 to your computer and use it in GitHub Desktop.
Generate remix routes

copy/past generate-remix-routes.ts next to remix.config and generate routes with npx tsx generate-remix-routes.ts

import { readConfig } from '@remix-run/dev/dist/config.js'
import type { ConfigRoute, RouteManifest } from '@remix-run/dev/dist/config/routes.js'
import { writeFileSync } from 'fs'
import { format, resolveConfig } from 'prettier'
const outputPath = `routes.ts`
function buildPath(routes: RouteManifest, route: ConfigRoute): string {
const result = []
if (route.parentId) {
result.push(buildPath(routes, routes[route.parentId]))
}
if (route.path) {
result.push(route.path)
}
return result.join('/')
}
function buildParams(path: string, input: boolean = false) {
return path
.replace(/'/g, '')
.split('/')
.filter((part) => part.startsWith(':'))
.map((part) => part.replace(':', ''))
.map(
(param) =>
`${param}: string${input ? ' | number' : ''}${param.endsWith('?') ? ' | undefined' : ''}`,
)
.join(',')
}
;(async () => {
const { routes } = await readConfig()
const paths = [
...new Set(
Object.keys(routes).map((key) => {
let path = buildPath(routes, routes[key])
if (path === '') {
path = '/'
}
return `'${path}'`
}),
),
]
const content = `
export const routerPaths = {${paths
.map((path) => {
if (!path.includes('/:')) {
return `${path}: ${path}`
}
return `${path}(params: {${buildParams(path, true)}}) {
return buildPath(${path}, params)
}`
})
.join(',')}} as const
export const unsafeRouterPaths = {${paths
.map((path) => {
return `${path}(params: Record<string, unknown>) {
return unsafeBuildPath(${path}, params)
}`
})
.join(',')}} as const
export type RouterPath = keyof typeof routerPaths
export type RouterParams = {${paths.map((path) => `${path}: {${buildParams(path)}}`).join(',')}}
type InputParams = {${paths.map((path) => `${path}: {${buildParams(path, true)}}`).join(',')}}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function buildPath<TRouterPath extends RouterPath>(route: TRouterPath, params: InputParams[TRouterPath]) {
return route
.replace(/'/g, '')
.split('/')
.map((part) =>
part.startsWith(':') ? params[part.replace(':', '').replace('?', '') as keyof InputParams[TRouterPath]] : part,
)
.filter((part) => part !== undefined)
.join('/')
}
function unsafeBuildPath<TRouterPath extends RouterPath>(
route: TRouterPath,
params: Record<string, unknown>,
) {
return route
.replace(/'/g, '')
.split('/')
.map((part) => (part.startsWith(':') ? params[part.replace(':', '').replace('?', '')] : part))
.filter((part) => part !== undefined)
.join('/')
}
`
try {
const formatted = await format(content, {
...(await resolveConfig(process.cwd())),
parser: 'typescript',
})
writeFileSync(outputPath, formatted)
} catch (err) {
console.log(content)
}
})()
import { redirect, type LoaderArgs } from '@remix-run/node'
import { routerPaths, type RouterParams, type RouterPath } from '../routes'
const THIS_PATH = '/my/:awesome?/route' satisfies RouterPath
export async function loader(args: LoaderArgs) {
// Can type params
const params = args.params as RouterParams[typeof THIS_PATH]
// params.awesome?: string | undefined | null
const count = Number(params.awesome) || 0
// Can build path with params
return redirect(routerPaths[THIS_PATH]({ awesome: count + 1 }))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment