├── .github └── ISSUE_TEMPLATE │ ├── bug_report.yaml │ └── config.yaml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc.js ├── .npmrc.example ├── .prettierrc.json ├── .release-it.json ├── LICENSE ├── commitlint.config.js ├── examples ├── react-location │ ├── basic │ │ ├── .gitignore │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── main.tsx │ │ │ └── pages │ │ │ │ ├── 404.tsx │ │ │ │ ├── _app.tsx │ │ │ │ ├── about.tsx │ │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── data-loaders │ │ ├── .gitignore │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── main.tsx │ │ │ └── pages │ │ │ │ ├── 404.tsx │ │ │ │ ├── _app.tsx │ │ │ │ ├── about.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── posts │ │ │ │ ├── [slug].tsx │ │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── modals │ │ ├── .gitignore │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── main.tsx │ │ │ └── pages │ │ │ │ ├── 404.tsx │ │ │ │ ├── _app.tsx │ │ │ │ ├── about.tsx │ │ │ │ ├── about │ │ │ │ └── location.tsx │ │ │ │ ├── gallery.tsx │ │ │ │ ├── gallery │ │ │ │ └── [id].tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index │ │ │ │ └── welcome.tsx │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── nested-layouts │ │ ├── .gitignore │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ ├── main.tsx │ │ └── pages │ │ │ ├── 404.tsx │ │ │ ├── _app.tsx │ │ │ ├── about.tsx │ │ │ ├── index.tsx │ │ │ └── posts │ │ │ ├── [slug].tsx │ │ │ ├── _layout.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config.ts ├── react-router-custom-path │ ├── .gitignore │ ├── client │ │ └── src │ │ │ ├── main.tsx │ │ │ ├── pages │ │ │ ├── (auth) │ │ │ │ ├── _layout.tsx │ │ │ │ ├── login.tsx │ │ │ │ └── register.tsx │ │ │ ├── +modal.tsx │ │ │ ├── 404.tsx │ │ │ ├── _app.tsx │ │ │ ├── about.tsx │ │ │ ├── index.tsx │ │ │ ├── posts │ │ │ │ ├── [id] │ │ │ │ │ ├── -[pid].tsx │ │ │ │ │ ├── deep.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── _layout.tsx │ │ │ │ └── index.tsx │ │ │ └── splat │ │ │ │ └── [...all].tsx │ │ │ ├── router.ts │ │ │ └── routes.tsx │ ├── index.html │ ├── package.json │ ├── readme.md │ ├── tsconfig.json │ └── vite.config.ts ├── react-router-custom │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── main.tsx │ │ ├── pages │ │ │ ├── (auth) │ │ │ │ ├── _layout.tsx │ │ │ │ ├── login.tsx │ │ │ │ └── register.tsx │ │ │ ├── +modal.tsx │ │ │ ├── 404.tsx │ │ │ ├── _app.tsx │ │ │ ├── about.tsx │ │ │ ├── index.tsx │ │ │ ├── posts │ │ │ │ ├── [id] │ │ │ │ │ ├── -[pid].tsx │ │ │ │ │ ├── deep.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── _layout.tsx │ │ │ │ └── index.tsx │ │ │ └── splat │ │ │ │ └── [...all].tsx │ │ ├── router.ts │ │ └── routes.tsx │ ├── tsconfig.json │ └── vite.config.ts ├── react-router-mdx │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── main.tsx │ │ ├── pages │ │ │ ├── (auth) │ │ │ │ ├── _layout.tsx │ │ │ │ ├── login.tsx │ │ │ │ └── register.tsx │ │ │ ├── +modal.tsx │ │ │ ├── 404.tsx │ │ │ ├── _app.tsx │ │ │ ├── about.tsx │ │ │ ├── content.mdx │ │ │ ├── index.mdx │ │ │ ├── posts │ │ │ │ ├── [id] │ │ │ │ │ ├── -[pid].tsx │ │ │ │ │ ├── deep.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── _layout.tsx │ │ │ │ └── index.tsx │ │ │ └── splat │ │ │ │ └── [...all].tsx │ │ └── router.ts │ ├── tsconfig.json │ └── vite.config.ts ├── react-router-route-modals │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── main.tsx │ │ ├── pages │ │ │ ├── (auth) │ │ │ │ ├── _layout.tsx │ │ │ │ ├── login.tsx │ │ │ │ └── register.tsx │ │ │ ├── +modal.tsx │ │ │ ├── 404.tsx │ │ │ ├── _app.tsx │ │ │ ├── about.tsx │ │ │ ├── gallery.tsx │ │ │ ├── gallery │ │ │ │ └── [id].tsx │ │ │ ├── index.tsx │ │ │ ├── posts │ │ │ │ ├── [id] │ │ │ │ │ ├── -[pid].tsx │ │ │ │ │ ├── deep.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── _layout.tsx │ │ │ │ └── index.tsx │ │ │ └── splat │ │ │ │ └── [...all].tsx │ │ └── router.ts │ ├── tsconfig.json │ └── vite.config.ts ├── react-router │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── main.tsx │ │ ├── pages │ │ │ ├── (auth) │ │ │ │ ├── _layout.tsx │ │ │ │ ├── login.tsx │ │ │ │ └── register.tsx │ │ │ ├── +modal.tsx │ │ │ ├── 404.tsx │ │ │ ├── _app.tsx │ │ │ ├── about.tsx │ │ │ ├── index.tsx │ │ │ ├── posts │ │ │ │ ├── [id] │ │ │ │ │ ├── -[pid].tsx │ │ │ │ │ ├── deep.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── _layout.tsx │ │ │ │ └── index.tsx │ │ │ └── splat │ │ │ │ └── [...all].tsx │ │ └── router.ts │ ├── tsconfig.json │ └── vite.config.ts ├── solid-router │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── main.tsx │ │ ├── pages │ │ │ ├── (auth) │ │ │ │ ├── _layout.tsx │ │ │ │ ├── login.tsx │ │ │ │ └── register.tsx │ │ │ ├── +modal.tsx │ │ │ ├── -[lang] │ │ │ │ └── lang.tsx │ │ │ ├── 404.tsx │ │ │ ├── _app.tsx │ │ │ ├── about.tsx │ │ │ ├── index.tsx │ │ │ └── posts │ │ │ │ ├── [id] │ │ │ │ ├── -[pid].tsx │ │ │ │ ├── deep.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── _layout.tsx │ │ │ │ └── index.tsx │ │ └── router.ts │ ├── tsconfig.json │ └── vite.config.ts └── tanstack-react-router │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── src │ ├── main.tsx │ ├── pages │ │ ├── (auth) │ │ │ ├── _layout.tsx │ │ │ ├── login.tsx │ │ │ └── register.tsx │ │ ├── 404.tsx │ │ ├── _app.tsx │ │ ├── about.tsx │ │ ├── index.tsx │ │ └── posts │ │ │ ├── [id].tsx │ │ │ ├── _layout.tsx │ │ │ └── index.tsx │ └── routes.gen.tsx │ ├── tsconfig.json │ └── vite.config.ts ├── explorer ├── .gitignore ├── .prettierrc.js ├── index.html ├── package.json ├── postcss.config.js ├── public │ └── favicon.svg ├── readme.md ├── src │ ├── components │ │ ├── container.tsx │ │ ├── index.ts │ │ └── routes.tsx │ ├── icons │ │ ├── arrow.tsx │ │ ├── directory.tsx │ │ ├── file.tsx │ │ └── index.ts │ ├── main.css │ ├── main.tsx │ ├── pages │ │ ├── (auth) │ │ │ ├── _layout.tsx │ │ │ ├── login.tsx │ │ │ └── register.tsx │ │ ├── +info.tsx │ │ ├── 404.tsx │ │ ├── _app.tsx │ │ ├── about.tsx │ │ ├── blog.w.o.layout.tsx │ │ ├── blog │ │ │ ├── [slug].tsx │ │ │ ├── _layout.tsx │ │ │ ├── index.tsx │ │ │ └── tags.tsx │ │ ├── docs │ │ │ └── -[lang] │ │ │ │ ├── index.tsx │ │ │ │ └── resources.tsx │ │ └── index.tsx │ ├── router.ts │ └── utils │ │ ├── class-names.ts │ │ └── index.ts ├── tailwind.config.js ├── tsconfig.json ├── vercel.json └── vite.config.ts ├── logo.svg ├── package.json ├── packages ├── generouted │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── core.test.ts │ │ ├── core.ts │ │ ├── react-location.tsx │ │ ├── react-router-lazy.tsx │ │ ├── react-router.tsx │ │ ├── solid-router-lazy.tsx │ │ └── solid-router.tsx │ ├── tsconfig.json │ └── vitest.config.ts ├── react-router │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── client │ │ │ ├── components.tsx │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── core.ts │ │ ├── index-lazy.ts │ │ ├── index.ts │ │ ├── plugin │ │ │ ├── generate.ts │ │ │ ├── index.ts │ │ │ ├── options.ts │ │ │ └── template.ts │ │ └── react.js │ ├── tsconfig.json │ └── tsup.config.ts ├── solid-router │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── client │ │ │ ├── components.tsx │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── core.ts │ │ ├── index-lazy.ts │ │ ├── index.ts │ │ └── plugin │ │ │ ├── generate.ts │ │ │ ├── index.ts │ │ │ ├── options.ts │ │ │ └── template.ts │ ├── tsconfig.json │ └── tsup.config.ts └── tanstack-react-router │ ├── package.json │ ├── readme.md │ ├── src │ ├── format.ts │ ├── generate.ts │ ├── index.ts │ ├── options.ts │ └── template.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── readme.md ├── shared └── core │ ├── package.json │ └── src │ └── index.ts └── turbo.json /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Submit a bug report 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for taking the time to fill out this bug report! 8 | 9 | - type: textarea 10 | id: description 11 | attributes: 12 | label: Describe the Bug 13 | description: Provide a clear and concise description of the challenge you are running into 14 | validations: 15 | required: true 16 | 17 | - type: input 18 | id: generouted 19 | attributes: 20 | label: Generouted Version 21 | validations: 22 | required: true 23 | 24 | - type: input 25 | id: link 26 | attributes: 27 | label: Your Example Website or App or Reproduction 28 | description: | 29 | Which website or app were you using when the bug happened? 30 | 31 | - Your bug will may get fixed much faster if we can run your code and it doesn't have dependencies other than generouted and the router 32 | - To create a shareable code example you can use Stackblitz (https://stackblitz.com/) — Please avoid localhost URLs 33 | - Please read these tips for providing a minimal example: https://stackoverflow.com/help/mcve 34 | placeholder: | 35 | https://stackblitz.com/edit/...... 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | id: steps 41 | attributes: 42 | label: Steps to Reproduce the Bug or Issue 43 | description: Describe the steps we have to take to reproduce the behavior 44 | placeholder: | 45 | 1. Go to '...' 46 | 2. Click on '....' 47 | 3. Scroll down to '....' 48 | 4. See error 49 | validations: 50 | required: true 51 | 52 | - type: textarea 53 | id: expected 54 | attributes: 55 | label: Expected Behavior 56 | description: Provide a clear and concise description of what you expected to happen. 57 | placeholder: | 58 | As a user, I expected ___ behavior but i am seeing ___ 59 | validations: 60 | required: true 61 | 62 | - type: textarea 63 | id: screenshots_or_videos 64 | attributes: 65 | label: Screenshots or Videos 66 | description: | 67 | If applicable, add screenshots or a video to help explain your problem — For more information on the supported file image/file types and the file size limits, please refer to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files 68 | placeholder: | 69 | You can drag your video or image files inside of this editor ↓ 70 | 71 | - type: textarea 72 | id: platform 73 | attributes: 74 | label: Platform 75 | value: | 76 | - OS: [e.g. macOS, Windows, Linux] 77 | - Browser: [e.g. Chrome, Safari, Firefox] 78 | - Browser version: [e.g. 91.1] 79 | validations: 80 | required: true 81 | 82 | - type: textarea 83 | id: additional 84 | attributes: 85 | label: Additional context 86 | description: Add any other context about the problem here 87 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yaml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | dist 3 | node_modules 4 | .npmrc 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | pnpm commitlint --edit "$1" 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm lint-staged 2 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | './{examples,packages,plugins,shared}/**/*.{ts,tsx}': () => 'pnpm type-check', 3 | '*.{css,html,json,md,mdx,js,jsx,ts,tsx}': 'prettier --write', 4 | } 5 | -------------------------------------------------------------------------------- /.npmrc.example: -------------------------------------------------------------------------------- 1 | hoist-workspace-packages=true 2 | link-workspace-packages=true 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "printWidth": 120, 4 | "semi": false, 5 | "singleQuote": true, 6 | "tabWidth": 2, 7 | "trailingComma": "all", 8 | "useTabs": false 9 | } 10 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm": false, 3 | "git": { 4 | "tagName": "v${version}", 5 | "tagAnnotation": "v${version}", 6 | "requireCleanWorkingDir": true, 7 | "commit": true, 8 | "commitMessage": "release: v${version}", 9 | "changelog": "echo '## Commits'; git log --pretty=format:'- %s %h by @%cn' ${from}...${to}; echo '\n\n**Changelog**: https://${repo.host}/${repo.repository}/compare/${latestTag}...TAG'", 10 | "push": true, 11 | "skipChecks": true 12 | }, 13 | "github": { 14 | "release": true, 15 | "releaseName": "v${version}", 16 | "releaseNotes": "echo \"${changelog}\" | sed 's|TAG|v${version}|'", 17 | "web": true, 18 | "skipChecks": true 19 | }, 20 | "hooks": { 21 | "before:init": ["pnpm build --filter './packages/*'"], 22 | "after:bump": ["pnpm install"] 23 | }, 24 | "plugins": { 25 | "@release-it-plugins/workspaces": { 26 | "workspaces": ["packages/*"], 27 | "additionalManifests": { 28 | "dependencyUpdates": ["examples/*/package.json", "explorer/package.json"], 29 | "versionUpdates": [] 30 | }, 31 | "skipChecks": true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Omar Elhawary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | ['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'release', 'revert', 'style', 'test'], 8 | ], 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-location/basic/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/react-location/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Basic example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-location/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/react-location-basic", 3 | "private": true, 4 | "description": "Generated file-based routes for React Location and Vite basic example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build" 10 | }, 11 | "dependencies": { 12 | "@tanstack/react-location": "^3.7.4", 13 | "generouted": "workspace:*", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^19.0.8", 19 | "@types/react-dom": "^19.0.3", 20 | "@vitejs/plugin-react": "^4.3.4", 21 | "typescript": "^5.7.3", 22 | "vite": "^6.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/react-location/basic/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import { Routes } from 'generouted/react-location' 3 | 4 | const container = document.getElementById('app')! 5 | createRoot(container).render() 6 | -------------------------------------------------------------------------------- /examples/react-location/basic/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/basic/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@tanstack/react-location' 2 | 3 | export default function App({ children }: { children: React.ReactNode }) { 4 | return ( 5 |
6 |
7 | Home 8 | About 9 |
10 | 11 |
{children}
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /examples/react-location/basic/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Basic

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/basic/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return

Home - Basic

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "types": ["vite/client"] 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/react-location/basic/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ plugins: [react()] }) 5 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Data Loaders 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/react-location-data-loaders", 3 | "private": true, 4 | "description": "Generated file-based routes for React Location and Vite data loaders example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build" 10 | }, 11 | "dependencies": { 12 | "@tanstack/react-location": "^3.7.4", 13 | "generouted": "workspace:*", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^19.0.8", 19 | "@types/react-dom": "^19.0.3", 20 | "@vitejs/plugin-react": "^4.3.4", 21 | "typescript": "^5.7.3", 22 | "vite": "^6.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import { Routes } from 'generouted/react-location' 3 | 4 | const container = document.getElementById('app')! 5 | createRoot(container).render() 6 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@tanstack/react-location' 2 | 3 | export default function App({ children }: { children: React.ReactNode }) { 4 | return ( 5 |
6 |
7 | Home 8 | About 9 | Posts 10 |
11 | 12 |
{children}
13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Data Loaders

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return

Home - Data Loaders

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/src/pages/posts/[slug].tsx: -------------------------------------------------------------------------------- 1 | import { LoaderFn, MakeGenerics, useMatch } from '@tanstack/react-location' 2 | 3 | type Post = { 4 | id: string 5 | userId: string 6 | title?: string 7 | body?: string 8 | } 9 | 10 | type Route = MakeGenerics<{ LoaderData: Post; Params: { slug: string } }> 11 | 12 | export const Loader: LoaderFn = async ({ params }) => { 13 | return fetch(`https://jsonplaceholder.typicode.com/posts/${params.slug}`).then((response) => response.json()) 14 | } 15 | 16 | export const Pending = () =>

Loading...

17 | export const Failure = () =>

Something went wrong...

18 | 19 | export default function Post() { 20 | const { data } = useMatch() 21 | 22 | return ( 23 | <> 24 |

Post @ {data.id}

25 | 26 | 27 | Loader data 28 |
{JSON.stringify(data, null, 2)}
29 |
30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/src/pages/posts/index.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@tanstack/react-location' 2 | 3 | export default function Index() { 4 | return ( 5 | <> 6 |

Posts Index

7 | 8 |
    9 |
  • 10 | Post 1 11 |
  • 12 |
  • 13 | Post 2 14 |
  • 15 |
  • 16 | Post 3 17 |
  • 18 |
19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "types": ["vite/client"] 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/react-location/data-loaders/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ plugins: [react()] }) 5 | -------------------------------------------------------------------------------- /examples/react-location/modals/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/react-location/modals/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Modals example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-location/modals/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/react-location-modals", 3 | "private": true, 4 | "description": "Generated file-based routes for React Location and Vite modals example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build" 10 | }, 11 | "dependencies": { 12 | "@tanstack/react-location": "^3.7.4", 13 | "generouted": "workspace:*", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^19.0.8", 19 | "@types/react-dom": "^19.0.3", 20 | "@vitejs/plugin-react": "^4.3.4", 21 | "typescript": "^5.7.3", 22 | "vite": "^6.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/react-location/modals/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import { Routes } from 'generouted/react-location' 3 | 4 | const container = document.getElementById('app')! 5 | createRoot(container).render() 6 | -------------------------------------------------------------------------------- /examples/react-location/modals/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/modals/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@tanstack/react-location' 2 | 3 | export default function App({ children }: { children: React.ReactNode }) { 4 | return ( 5 |
6 |
7 | Home 8 | About 9 | Gallery 10 |
11 | 12 |
{children}
13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /examples/react-location/modals/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Outlet } from '@tanstack/react-location' 2 | 3 | export default function About() { 4 | return ( 5 | <> 6 |

About - Modals

7 | Location → 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /examples/react-location/modals/src/pages/about/location.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from '@tanstack/react-location' 2 | 3 | export default function Location() { 4 | const navigate = useNavigate() 5 | 6 | const onDismiss = () => navigate({ to: '..' }) 7 | 8 | return ( 9 |
12 |
13 |
14 |

Location

15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /examples/react-location/modals/src/pages/gallery.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Outlet } from '@tanstack/react-location' 2 | 3 | const timestamps = [...Array(8)].map((_, index) => index + 1) 4 | 5 | export default function Gallery() { 6 | return ( 7 | <> 8 |

Gallery - Modals

9 |
    10 | {timestamps.map((timestamp) => ( 11 |
  • 12 | Item — {timestamp} → 13 |
  • 14 | ))} 15 |
16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /examples/react-location/modals/src/pages/gallery/[id].tsx: -------------------------------------------------------------------------------- 1 | import { useMatch, useNavigate } from '@tanstack/react-location' 2 | 3 | export default function GalleryId() { 4 | const { params } = useMatch() 5 | const navigate = useNavigate() 6 | 7 | const { id } = params 8 | 9 | const onDismiss = () => navigate({ to: '..' }) 10 | 11 | return ( 12 |
15 |
16 |
17 |

Gallery Item — {id}

18 |
19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-location/modals/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Outlet } from '@tanstack/react-location' 2 | 3 | export default function Home() { 4 | return ( 5 | <> 6 |

Home - Modals

7 | Welcome modal → 8 |
9 |
10 | Gallery Item — 1 → 11 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /examples/react-location/modals/src/pages/index/welcome.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from '@tanstack/react-location' 2 | 3 | export default function Welcome() { 4 | const navigate = useNavigate() 5 | 6 | const onDismiss = () => navigate({ to: '..' }) 7 | 8 | return ( 9 |
12 |
13 |
14 |

Welcome!

15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /examples/react-location/modals/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "types": ["vite/client"] 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/react-location/modals/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ plugins: [react()] }) 5 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Nested Layouts 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/react-location-nested-layouts", 3 | "private": true, 4 | "description": "Generated file-based routes for React Location and Vite nested layouts example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build" 10 | }, 11 | "dependencies": { 12 | "@tanstack/react-location": "^3.7.4", 13 | "generouted": "workspace:*", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^19.0.8", 19 | "@types/react-dom": "^19.0.3", 20 | "@vitejs/plugin-react": "^4.3.4", 21 | "typescript": "^5.7.3", 22 | "vite": "^6.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import { Routes } from 'generouted/react-location' 3 | 4 | const container = document.getElementById('app')! 5 | createRoot(container).render() 6 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@tanstack/react-location' 2 | 3 | export default function App({ children }: { children: React.ReactNode }) { 4 | return ( 5 |
6 |
7 | Home 8 | About 9 | Posts 10 |
11 | 12 |
{children}
13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Nested Layouts

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return

Home - Nested Layouts

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/src/pages/posts/[slug].tsx: -------------------------------------------------------------------------------- 1 | import { LoaderFn, MakeGenerics, useMatch } from '@tanstack/react-location' 2 | 3 | type Post = { 4 | id: string 5 | userId: string 6 | title?: string 7 | body?: string 8 | } 9 | 10 | type Route = MakeGenerics<{ LoaderData: Post; Params: { slug: string } }> 11 | 12 | export const Loader: LoaderFn = async ({ params }) => { 13 | return fetch(`https://jsonplaceholder.typicode.com/posts/${params.slug}`).then((response) => response.json()) 14 | } 15 | 16 | export default function Post() { 17 | const { data } = useMatch() 18 | 19 | return ( 20 | <> 21 |

Post @ {data.id}

22 | 23 | 24 | Loader data 25 |
{JSON.stringify(data, null, 2)}
26 |
27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/src/pages/posts/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Outlet } from '@tanstack/react-location' 2 | 3 | export const Loader = () => { 4 | return Promise.resolve({ source: 'from `src/pages/posts.tsx` layout data loader' }) 5 | } 6 | 7 | export default function PostsLayout() { 8 | return ( 9 | <> 10 |

Posts Layout

11 | 12 |
13 |
    14 |
  • 15 | Posts Index 16 |
  • 17 |
  • 18 | Post 1 19 |
  • 20 |
  • 21 | Post 2 22 |
  • 23 |
  • 24 | Post 3 25 |
  • 26 |
27 | 28 |
29 | 30 |
31 |
32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/src/pages/posts/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMatch } from '@tanstack/react-location' 2 | 3 | export default function Index() { 4 | const { data } = useMatch() 5 | 6 | return ( 7 | <> 8 |

Posts Index

9 | 10 | 11 | Loader data 12 |
{JSON.stringify(data, null, 2)}
13 |
14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "types": ["vite/client"] 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/react-location/nested-layouts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ plugins: [react()] }) 5 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | 3 | import { Routes } from './routes' 4 | 5 | const container = document.getElementById('app')! 6 | createRoot(container).render() 7 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function Layout() { 4 | return ( 5 |
6 |

Auth Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/(auth)/login.tsx: -------------------------------------------------------------------------------- 1 | export default function Login() { 2 | return

Login

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/(auth)/register.tsx: -------------------------------------------------------------------------------- 1 | export default function Register() { 2 | return

Register

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/+modal.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router' 2 | 3 | import { useModals } from '@/router' 4 | 5 | export default function Welcome() { 6 | const location = useLocation() 7 | const modals = useModals() 8 | 9 | const handleClose = () => modals.close() 10 | 11 | return ( 12 |
15 |
16 |
17 |

Global Modal!

18 |

Current pathname: {location.pathname}

19 | 20 | 21 | 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | import { Link, useModals, useNavigate, useParams } from '../router' 4 | 5 | export default function App() { 6 | const navigate = useNavigate() 7 | const modals = useModals() 8 | const { id, pid } = useParams('/posts/:id/:pid?') 9 | 10 | const a = () => navigate('/posts/:id', { params: { id: 'a' } }) 11 | const b = () => navigate('/posts/:id', { params: { id: '' } }) 12 | const c = () => navigate(-1) 13 | const d = () => navigate('/posts/:id/deep', { params: { id: 'd' } }) 14 | const e = () => navigate('/posts/:id/deep', { params: { id: 'e' } }) 15 | 16 | return ( 17 |
18 |
19 | Home 20 | About 21 | Posts 22 | 23 | Posts by id/pid 24 | 25 | 26 | Posts by id 27 | 28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 |
36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Basic

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export const Loader = () => 'Route loader' 2 | export const Action = () => 'Route action' 3 | export const Catch = () =>
Route errorrrrrr
4 | 5 | export default function Home() { 6 | return

Home - Basic

7 | } 8 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/posts/[id]/-[pid].tsx: -------------------------------------------------------------------------------- 1 | export default function IdPid() { 2 | return

IdPid

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/posts/[id]/deep.tsx: -------------------------------------------------------------------------------- 1 | export default function IdDeep() { 2 | return

IdDeep

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/posts/[id]/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMatch } from 'react-router' 2 | 3 | import { useParams } from '@/router' 4 | 5 | export default function Id() { 6 | // const { params } = useMatch('/posts/$id') 7 | const { id } = useParams('/posts/:id') 8 | const match = useMatch('/posts/:id') 9 | 10 | return

Id

11 | } 12 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/posts/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function About() { 4 | return ( 5 |
6 |

Posts Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/posts/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return

Index

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/pages/splat/[...all].tsx: -------------------------------------------------------------------------------- 1 | export default function SplatAll() { 2 | return

All

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/router.ts: -------------------------------------------------------------------------------- 1 | // Generouted, changes to this file will be overridden 2 | /* eslint-disable */ 3 | 4 | import { components, hooks, utils } from '@generouted/react-router/client' 5 | 6 | export type Path = 7 | | `/` 8 | | `/about` 9 | | `/login` 10 | | `/posts` 11 | | `/posts/:id` 12 | | `/posts/:id/:pid?` 13 | | `/posts/:id/deep` 14 | | `/register` 15 | | `/splat/*` 16 | 17 | export type Params = { 18 | '/posts/:id': { id: string } 19 | '/posts/:id/:pid?': { id: string; pid?: string } 20 | '/posts/:id/deep': { id: string } 21 | '/splat/*': { '*': string } 22 | } 23 | 24 | export type ModalPath = `/modal` 25 | 26 | export const { Link, Navigate } = components() 27 | export const { useModals, useNavigate, useParams } = hooks() 28 | export const { redirect } = utils() 29 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/client/src/routes.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, JSX, Suspense } from 'react' 2 | import { createBrowserRouter, Outlet, RouterProvider, useLocation } from 'react-router' 3 | import type { ActionFunction, RouteObject, LoaderFunction } from 'react-router' 4 | 5 | import { generateModalRoutes, generatePreservedRoutes, generateRegularRoutes } from '@generouted/react-router/core' 6 | 7 | type Element = () => JSX.Element 8 | type Module = { default: Element; Loader?: LoaderFunction; Action?: ActionFunction; Catch?: Element; Pending?: Element } 9 | 10 | const PRESERVED = import.meta.glob('/client/src/pages/(_app|404).{jsx,tsx}', { eager: true }) 11 | const MODALS = import.meta.glob>('/client/src/pages/**/[+]*.{jsx,tsx}', { eager: true }) 12 | const ROUTES = import.meta.glob( 13 | ['/client/src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', '!/client/src/pages/**/(_!(layout)*(/*)?|_app|404)*'], 14 | { eager: true }, 15 | ) 16 | 17 | const preservedRoutes = generatePreservedRoutes>(PRESERVED) 18 | const modalRoutes = generateModalRoutes(MODALS) 19 | 20 | const regularRoutes = generateRegularRoutes>(ROUTES, (module, key) => { 21 | const index = /index\.(jsx|tsx|mdx)$/.test(key) && !key.includes('pages/index') ? { index: true } : {} 22 | const Default = module?.default || Fragment 23 | const Page = () => (module?.Pending ? } children={} /> : ) 24 | return { ...index, Component: Page, ErrorBoundary: module?.Catch, loader: module?.Loader, action: module?.Action } 25 | }) 26 | 27 | const _app = preservedRoutes?.['_app'] 28 | const _404 = preservedRoutes?.['404'] 29 | 30 | const Default = _app?.default || Outlet 31 | 32 | const Modals = () => { 33 | const Modal = modalRoutes[useLocation().state?.modal] || Fragment 34 | return 35 | } 36 | 37 | const Layout = () => ( 38 | <> 39 | 40 | 41 | ) 42 | 43 | const App = () => (_app?.Pending ? } children={} /> : ) 44 | 45 | const app = { Component: _app?.default ? App : Layout, ErrorBoundary: _app?.Catch, loader: _app?.Loader } 46 | const fallback = { path: '*', Component: _404?.default || Fragment } 47 | 48 | export const routes: RouteObject[] = [{ ...app, children: [...regularRoutes, fallback] }] 49 | const router = createBrowserRouter(routes) 50 | export const Routes = () => 51 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Basic example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/react-router-custom-path", 3 | "private": true, 4 | "description": "Generated file-based routes for React Router and Vite custom path example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build", 10 | "type-check": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@generouted/react-router": "^1.20.0", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0", 16 | "react-router": "^7.1.5" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^19.0.8", 20 | "@types/react-dom": "^19.0.3", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "prettier": "^3.4.2", 23 | "typescript": "^5.7.3", 24 | "vite": "^6.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/readme.md: -------------------------------------------------------------------------------- 1 | # Generouted + React Router Custom Integration + Custom Path Example 2 | 3 | Although it's recommended to use the default integrations, in some cases, full stack projects or monorepo tooling like Nx might require certain project structure. This example uses `src/pages` inside a `client` directory in the project root. 4 | 5 | ## What's different from the default integrations 6 | 7 | - Source files are located at [`./client/src`](./client/src) instead of `./src` _(default)_ 8 | - Pages located at [`./client/src/pages`](./client/src/pages) instead of `./src/pages` _(default)_ 9 | 10 |
11 | 12 | - Custom integration added at [`./client/src/routes.tsx`](./client/src/routes.tsx) 13 | - Based on one of [`packages/generouted/src`](/packages/generouted/src) integrations 14 | - Copied from [`packages/generouted/src/react-router.tsx`](/packages/generouted/src/react-router.tsx) 15 | - Only `import.meta.glob` patterns for routes, preserved routes and modals are updated to be prefixed with `/client` 16 | - Exports `` component that's imported at app entry [`./client/src/main.tsx`](./client/src/main.tsx) 17 | 18 |
19 | 20 | - Custom config for Vite plugin at [`./vite.config.ts`](./vite.config.ts) 21 | - Both `source` and `output` have the default values but prefixed with `./client` 22 | 23 | ## Preview 24 | 25 | Run this example online via [StackBlitz](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router-custom-path): 26 | 27 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router-custom-path) 28 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "paths": { "@/*": ["client/src/*"] }, 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["./client/src"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-router-custom-path/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import generouted from '@generouted/react-router/plugin' 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | react(), 8 | generouted({ 9 | source: { routes: './client/src/pages/**/[\\w[-]*.{jsx,tsx}', modals: './client/src/pages/**/[+]*.{jsx,tsx}' }, 10 | output: './client/src/router.ts', 11 | }), 12 | ], 13 | resolve: { alias: { '@': '/client/src' } }, 14 | }) 15 | -------------------------------------------------------------------------------- /examples/react-router-custom/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/react-router-custom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Basic example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-router-custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/react-router-custom", 3 | "private": true, 4 | "description": "Generated file-based routes for React Router and Vite custom example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build", 10 | "type-check": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@generouted/react-router": "^1.20.0", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0", 16 | "react-router": "^7.1.5" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^19.0.8", 20 | "@types/react-dom": "^19.0.3", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "prettier": "^3.4.2", 23 | "typescript": "^5.7.3", 24 | "vite": "^6.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/react-router-custom/readme.md: -------------------------------------------------------------------------------- 1 | # Generouted + React Router Custom Integration Example 2 | 3 | ## What's different from the default integrations 4 | 5 | - Custom integration added at [`./src/routes.tsx`](./src/routes.tsx) 6 | - Based on one of [`packages/generouted/src`](/packages/generouted/src) integrations 7 | - Copied from [`packages/generouted/src/react-router.tsx`](/packages/generouted/src/react-router.tsx) 8 | - Exports `` component that's imported at app entry [`./src/main.tsx`](./src/main.tsx) 9 | 10 | ## Preview 11 | 12 | Run this example online via [StackBlitz](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router-custom): 13 | 14 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router-custom) 15 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | 3 | import { Routes } from './routes' 4 | 5 | const container = document.getElementById('app')! 6 | createRoot(container).render() 7 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function Layout() { 4 | return ( 5 |
6 |

Auth Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/(auth)/login.tsx: -------------------------------------------------------------------------------- 1 | export default function Login() { 2 | return

Login

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/(auth)/register.tsx: -------------------------------------------------------------------------------- 1 | export default function Register() { 2 | return

Register

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/+modal.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router' 2 | 3 | import { useModals } from '@/router' 4 | 5 | export default function Welcome() { 6 | const location = useLocation() 7 | const modals = useModals() 8 | 9 | const handleClose = () => modals.close() 10 | 11 | return ( 12 |
15 |
16 |
17 |

Global Modal!

18 |

Current pathname: {location.pathname}

19 | 20 | 21 | 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | import { Link, useModals, useNavigate, useParams } from '../router' 4 | 5 | export default function App() { 6 | const navigate = useNavigate() 7 | const modals = useModals() 8 | const { id, pid } = useParams('/posts/:id/:pid?') 9 | 10 | const a = () => navigate('/posts/:id', { params: { id: 'a' } }) 11 | const b = () => navigate('/posts/:id', { params: { id: '' } }) 12 | const c = () => navigate(-1) 13 | const d = () => navigate('/posts/:id/deep', { params: { id: 'd' } }) 14 | const e = () => navigate('/posts/:id/deep', { params: { id: 'e' } }) 15 | 16 | return ( 17 |
18 |
19 | Home 20 | About 21 | Posts 22 | 23 | Posts by id/pid 24 | 25 | 26 | Posts by id 27 | 28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 |
36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Basic

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export const Loader = () => 'Route loader' 2 | export const Action = () => 'Route action' 3 | export const Catch = () =>
Route errorrrrrr
4 | 5 | export default function Home() { 6 | return

Home - Basic

7 | } 8 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/posts/[id]/-[pid].tsx: -------------------------------------------------------------------------------- 1 | export default function IdPid() { 2 | return

IdPid

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/posts/[id]/deep.tsx: -------------------------------------------------------------------------------- 1 | export default function IdDeep() { 2 | return

IdDeep

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/posts/[id]/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMatch } from 'react-router' 2 | 3 | import { useParams } from '@/router' 4 | 5 | export default function Id() { 6 | // const { params } = useMatch('/posts/$id') 7 | const { id } = useParams('/posts/:id') 8 | const match = useMatch('/posts/:id') 9 | 10 | return

Id

11 | } 12 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/posts/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function About() { 4 | return ( 5 |
6 |

Posts Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/posts/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return

Index

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/pages/splat/[...all].tsx: -------------------------------------------------------------------------------- 1 | export default function SplatAll() { 2 | return

All

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/router.ts: -------------------------------------------------------------------------------- 1 | // Generouted, changes to this file will be overridden 2 | /* eslint-disable */ 3 | 4 | import { components, hooks, utils } from '@generouted/react-router/client' 5 | 6 | export type Path = 7 | | `/` 8 | | `/about` 9 | | `/login` 10 | | `/posts` 11 | | `/posts/:id` 12 | | `/posts/:id/:pid?` 13 | | `/posts/:id/deep` 14 | | `/register` 15 | | `/splat/*` 16 | 17 | export type Params = { 18 | '/posts/:id': { id: string } 19 | '/posts/:id/:pid?': { id: string; pid?: string } 20 | '/posts/:id/deep': { id: string } 21 | '/splat/*': { '*': string } 22 | } 23 | 24 | export type ModalPath = `/modal` 25 | 26 | export const { Link, Navigate } = components() 27 | export const { useModals, useNavigate, useParams } = hooks() 28 | export const { redirect } = utils() 29 | -------------------------------------------------------------------------------- /examples/react-router-custom/src/routes.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, JSX, Suspense } from 'react' 2 | import { createBrowserRouter, Outlet, RouterProvider, useLocation } from 'react-router' 3 | import type { ActionFunction, RouteObject, LoaderFunction } from 'react-router' 4 | 5 | import { generateModalRoutes, generatePreservedRoutes, generateRegularRoutes } from '@generouted/react-router/core' 6 | 7 | type Element = () => JSX.Element 8 | type Module = { default: Element; Loader?: LoaderFunction; Action?: ActionFunction; Catch?: Element; Pending?: Element } 9 | 10 | const PRESERVED = import.meta.glob('/src/pages/(_app|404).{jsx,tsx}', { eager: true }) 11 | const MODALS = import.meta.glob>('/src/pages/**/[+]*.{jsx,tsx}', { eager: true }) 12 | const ROUTES = import.meta.glob( 13 | ['/src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', '!/src/pages/**/(_!(layout)*(/*)?|_app|404)*'], 14 | { eager: true }, 15 | ) 16 | 17 | const preservedRoutes = generatePreservedRoutes>(PRESERVED) 18 | const modalRoutes = generateModalRoutes(MODALS) 19 | 20 | const regularRoutes = generateRegularRoutes>(ROUTES, (module, key) => { 21 | const index = /index\.(jsx|tsx|mdx)$/.test(key) && !key.includes('pages/index') ? { index: true } : {} 22 | const Default = module?.default || Fragment 23 | const Page = () => (module?.Pending ? } children={} /> : ) 24 | return { ...index, Component: Page, ErrorBoundary: module?.Catch, loader: module?.Loader, action: module?.Action } 25 | }) 26 | 27 | const _app = preservedRoutes?.['_app'] 28 | const _404 = preservedRoutes?.['404'] 29 | 30 | const Default = _app?.default || Outlet 31 | 32 | const Modals = () => { 33 | const Modal = modalRoutes[useLocation().state?.modal] || Fragment 34 | return 35 | } 36 | 37 | const Layout = () => ( 38 | <> 39 | 40 | 41 | ) 42 | 43 | const App = () => (_app?.Pending ? } children={} /> : ) 44 | 45 | const app = { Component: _app?.default ? App : Layout, ErrorBoundary: _app?.Catch, loader: _app?.Loader } 46 | const fallback = { path: '*', Component: _404?.default || Fragment } 47 | 48 | export const routes: RouteObject[] = [{ ...app, children: [...regularRoutes, fallback] }] 49 | const router = createBrowserRouter(routes) 50 | export const Routes = () => 51 | -------------------------------------------------------------------------------- /examples/react-router-custom/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "paths": { "@/*": ["src/*"] }, 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-router-custom/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import generouted from '@generouted/react-router/plugin' 4 | 5 | export default defineConfig({ 6 | plugins: [react(), generouted()], 7 | resolve: { alias: { '@': '/src' } }, 8 | }) 9 | -------------------------------------------------------------------------------- /examples/react-router-mdx/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/react-router-mdx/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Basic example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-router-mdx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/react-router-mdx", 3 | "private": true, 4 | "description": "Generated file-based routes for React Router + MDX and Vite plugin example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "type": "module", 8 | "scripts": { 9 | "dev": "vite dev --port 3000", 10 | "build": "vite build", 11 | "type-check": "tsc --noEmit" 12 | }, 13 | "dependencies": { 14 | "@generouted/react-router": "^1.20.0", 15 | "@mdx-js/rollup": "^3.1.0", 16 | "react": "^19.0.0", 17 | "react-dom": "^19.0.0", 18 | "react-router": "^7.1.5" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^19.0.8", 22 | "@types/react-dom": "^19.0.3", 23 | "@vitejs/plugin-react": "^4.3.4", 24 | "prettier": "^3.4.2", 25 | "typescript": "^5.7.3", 26 | "vite": "^6.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/react-router-mdx/readme.md: -------------------------------------------------------------------------------- 1 | # Generouted + React Router + MDX Example 2 | 3 | ## What's different from the default setup of [react-router example](/examples/react-router) 4 | 5 | - `@mdx-js/rollup` installation and config at `vite.config.ts` to use `.mdx` files at `src/pages`: 6 | 7 | ```ts 8 | // vite.config.ts 9 | 10 | import { defineConfig } from 'vite' 11 | import react from '@vitejs/plugin-react' 12 | import generouted from '@generouted/react-router/plugin' 13 | import mdx from '@mdx-js/rollup' 14 | 15 | export default defineConfig({ plugins: [{ enforce: 'pre', ...mdx() }, react(), generouted()] }) 16 | ``` 17 | 18 | ## Adding pages 19 | 20 | Add the home page by creating a new file `src/pages/index.mdx` → `/`: 21 | 22 | ```mdx 23 | ### Header 24 | 25 | **Bold**, _italic_ and `inline-code` 26 | ``` 27 | 28 | ## Preview 29 | 30 | Run this example online via [StackBlitz](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router-mdx): 31 | 32 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router-mdx) 33 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import { Routes } from '@generouted/react-router' 3 | 4 | const container = document.getElementById('app')! 5 | createRoot(container).render() 6 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function Layout() { 4 | return ( 5 |
6 |

Auth Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/(auth)/login.tsx: -------------------------------------------------------------------------------- 1 | export default function Login() { 2 | return

Login

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/(auth)/register.tsx: -------------------------------------------------------------------------------- 1 | export default function Register() { 2 | return

Register

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/+modal.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router' 2 | 3 | import { useModals } from '@/router' 4 | 5 | export default function Welcome() { 6 | const location = useLocation() 7 | const modals = useModals() 8 | 9 | const handleClose = () => modals.close() 10 | 11 | return ( 12 |
15 |
16 |
17 |

Global Modal!

18 |

Current pathname: {location.pathname}

19 | 20 | 21 | 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | import { Link, useModals, useNavigate, useParams } from '../router' 4 | 5 | export const Catch = () => { 6 | return
Something went wrong... Caught at _app error boundary
7 | } 8 | 9 | export const Pending = () =>
Loading from _app...
10 | 11 | export default function App() { 12 | const navigate = useNavigate() 13 | const modals = useModals() 14 | const { id, pid } = useParams('/posts/:id/:pid?') 15 | 16 | const a = () => navigate('/posts/:id', { params: { id: 'a' } }) 17 | const b = () => navigate('/posts/:id', { params: { id: '' } }) 18 | const c = () => navigate(-1) 19 | const d = () => navigate('/posts/:id/deep', { params: { id: 'd' } }) 20 | const e = () => navigate('/posts/:id/deep', { params: { id: 'e' } }) 21 | 22 | return ( 23 |
24 |
25 | Home 26 | About 27 | MDX content 28 | Posts 29 | 30 | Posts by id/pid 31 | 32 | 33 | Posts by id 34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 | 42 |
43 |
44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Basic

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/content.mdx: -------------------------------------------------------------------------------- 1 | # MDX content 2 | 3 | Other `MDX`-based page 4 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/index.mdx: -------------------------------------------------------------------------------- 1 | ### MDX content support 2 | 3 | Some `MDX` **content** from `./src/pages/content.mdx` file. 4 | 5 | #### Setup 6 | 7 | - Install `@mdx-js/rollup` 8 | - Set `enforce: 'pre'` to `mdx` plugin at `vite.config.ts` 9 | 10 | ```tsx 11 | // vite.config.ts 12 | 13 | import { defineConfig } from 'vite' 14 | import react from '@vitejs/plugin-react' 15 | import generouted from '@generouted/react-router/plugin' 16 | import mdx from '@mdx-js/rollup' 17 | 18 | export default defineConfig({ 19 | plugins: [{ enforce: 'pre', ...mdx() }, , react(), generouted()], 20 | resolve: { alias: { '@': '/src' } }, 21 | }) 22 | ``` 23 | 24 | #### Adding pages 25 | 26 | Add the home page by creating a new file `src/pages/index.mdx` → `/`: 27 | 28 | ```mdx 29 | ### Header 30 | 31 | **Bold**, _italic_ and `inline-code` 32 | ``` 33 | 34 | #### Preview 35 | 36 | ### Header 37 | 38 | **Bold**, _italic_ and `inline-code` 39 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/posts/[id]/-[pid].tsx: -------------------------------------------------------------------------------- 1 | export default function IdPid() { 2 | return

IdPid

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/posts/[id]/deep.tsx: -------------------------------------------------------------------------------- 1 | export default function IdDeep() { 2 | return

IdDeep

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/posts/[id]/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMatch } from 'react-router' 2 | 3 | import { useParams } from '@/router' 4 | 5 | export default function Id() { 6 | // const { params } = useMatch('/posts/$id') 7 | const { id } = useParams('/posts/:id') 8 | const match = useMatch('/posts/:id') 9 | 10 | return

Id

11 | } 12 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/posts/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function About() { 4 | return ( 5 |
6 |

Posts Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/posts/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return

Index

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/pages/splat/[...all].tsx: -------------------------------------------------------------------------------- 1 | export default function SplatAll() { 2 | return

All

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-mdx/src/router.ts: -------------------------------------------------------------------------------- 1 | // Generouted, changes to this file will be overridden 2 | /* eslint-disable */ 3 | 4 | import { components, hooks, utils } from '@generouted/react-router/client' 5 | 6 | export type Path = 7 | | `/` 8 | | `/about` 9 | | `/content` 10 | | `/login` 11 | | `/posts` 12 | | `/posts/:id` 13 | | `/posts/:id/:pid?` 14 | | `/posts/:id/deep` 15 | | `/register` 16 | | `/splat/*` 17 | 18 | export type Params = { 19 | '/posts/:id': { id: string } 20 | '/posts/:id/:pid?': { id: string; pid?: string } 21 | '/posts/:id/deep': { id: string } 22 | '/splat/*': { '*': string } 23 | } 24 | 25 | export type ModalPath = `/modal` 26 | 27 | export const { Link, Navigate } = components() 28 | export const { useModals, useNavigate, useParams } = hooks() 29 | export const { redirect } = utils() 30 | -------------------------------------------------------------------------------- /examples/react-router-mdx/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "paths": { "@/*": ["src/*"] }, 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-router-mdx/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import generouted from '@generouted/react-router/plugin' 4 | import mdx from '@mdx-js/rollup' 5 | 6 | export default defineConfig({ 7 | plugins: [{ enforce: 'pre', ...mdx() }, react(), generouted()], 8 | resolve: { alias: { '@': '/src' } }, 9 | }) 10 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Route modals example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/react-router-route-modals", 3 | "private": true, 4 | "description": "Generated file-based routes for React Router and Vite plugin route modals example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build", 10 | "type-check": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@generouted/react-router": "^1.20.0", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0", 16 | "react-router": "^7.1.5" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^19.0.8", 20 | "@types/react-dom": "^19.0.3", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "prettier": "^3.4.2", 23 | "typescript": "^5.7.3", 24 | "vite": "^6.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/readme.md: -------------------------------------------------------------------------------- 1 | # Generouted + React Router Route Modal Example 2 | 3 | ## Preview 4 | 5 | Run this example online via [StackBlitz](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router-route-modals): 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router-route-modals) 8 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import { Routes } from '@generouted/react-router' 3 | 4 | const container = document.getElementById('app')! 5 | createRoot(container).render() 6 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function Layout() { 4 | return ( 5 |
6 |

Auth Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/(auth)/login.tsx: -------------------------------------------------------------------------------- 1 | export default function Login() { 2 | return

Login

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/(auth)/register.tsx: -------------------------------------------------------------------------------- 1 | export default function Register() { 2 | return

Register

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/+modal.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router' 2 | 3 | import { useModals } from '@/router' 4 | 5 | export default function Welcome() { 6 | const location = useLocation() 7 | const modals = useModals() 8 | 9 | const handleClose = () => modals.close() 10 | 11 | return ( 12 |
15 |
16 |
17 |

Global Modal!

18 |

Current pathname: {location.pathname}

19 | 20 | 21 | 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | import { Link, useModals, useNavigate, useParams } from '../router' 4 | 5 | export const Catch = () => { 6 | return
Something went wrong... Caught at _app error boundary
7 | } 8 | 9 | export const Pending = () =>
Loading from _app...
10 | 11 | export default function App() { 12 | const navigate = useNavigate() 13 | const modals = useModals() 14 | const { id, pid } = useParams('/posts/:id/:pid?') 15 | 16 | const a = () => navigate('/posts/:id', { params: { id: 'a' } }) 17 | const b = () => navigate('/posts/:id', { params: { id: '' } }) 18 | const c = () => navigate(-1) 19 | const d = () => navigate('/posts/:id/deep', { params: { id: 'd' } }) 20 | const e = () => navigate('/posts/:id/deep', { params: { id: 'e' } }) 21 | 22 | return ( 23 |
24 |
25 | Home 26 | About 27 | Posts 28 | 29 | Posts by id/pid 30 | 31 | 32 | Posts by id 33 | 34 | 35 | Gallery ✨ 36 | 37 | Open a route modal at gallery ✨ 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 |
47 | 48 |
49 |
50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Basic

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/gallery.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from '@/router' 2 | import { Outlet } from 'react-router' 3 | 4 | const timestamps = [...Array(8)].map((_, index) => index + 1) 5 | 6 | export default function Gallery() { 7 | return ( 8 | <> 9 |

Gallery - Modals

10 |
    11 | {timestamps.map((timestamp) => ( 12 |
  • 13 | 14 | Item — {timestamp} → 15 | 16 |
  • 17 | ))} 18 |
19 | 20 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/gallery/[id].tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate, useParams } from '@/router' 2 | 3 | export default function GalleryId() { 4 | const params = useParams('/gallery/:id') 5 | const navigate = useNavigate() 6 | 7 | const onDismiss = () => navigate('/gallery') 8 | 9 | return ( 10 |
13 |
14 |
15 |

Gallery Item — {params.id}

16 |
17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export const Loader = () => 'Route loader' 2 | export const Action = () => 'Route action' 3 | export const Catch = () =>
Something went wrong...
4 | 5 | export const Pending = () =>
Loading...
6 | 7 | export default function Home() { 8 | return

Home - Basic

9 | } 10 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/posts/[id]/-[pid].tsx: -------------------------------------------------------------------------------- 1 | export default function IdPid() { 2 | return

IdPid

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/posts/[id]/deep.tsx: -------------------------------------------------------------------------------- 1 | export default function IdDeep() { 2 | return

IdDeep

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/posts/[id]/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMatch } from 'react-router' 2 | 3 | import { useParams } from '@/router' 4 | 5 | export default function Id() { 6 | // const { params } = useMatch('/posts/$id') 7 | const { id } = useParams('/posts/:id') 8 | const match = useMatch('/posts/:id') 9 | 10 | return

Id

11 | } 12 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/posts/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function About() { 4 | return ( 5 |
6 |

Posts Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/posts/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return

Index

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/pages/splat/[...all].tsx: -------------------------------------------------------------------------------- 1 | export default function SplatAll() { 2 | return

All

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/src/router.ts: -------------------------------------------------------------------------------- 1 | // Generouted, changes to this file will be overridden 2 | /* eslint-disable */ 3 | 4 | import { components, hooks, utils } from '@generouted/react-router/client' 5 | 6 | export type Path = 7 | | `/` 8 | | `/about` 9 | | `/gallery` 10 | | `/gallery/:id` 11 | | `/login` 12 | | `/posts` 13 | | `/posts/:id` 14 | | `/posts/:id/:pid?` 15 | | `/posts/:id/deep` 16 | | `/register` 17 | | `/splat/*` 18 | 19 | export type Params = { 20 | '/gallery/:id': { id: string } 21 | '/posts/:id': { id: string } 22 | '/posts/:id/:pid?': { id: string; pid?: string } 23 | '/posts/:id/deep': { id: string } 24 | '/splat/*': { '*': string } 25 | } 26 | 27 | export type ModalPath = `/modal` 28 | 29 | export const { Link, Navigate } = components() 30 | export const { useModals, useNavigate, useParams } = hooks() 31 | export const { redirect } = utils() 32 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "paths": { "@/*": ["src/*"] }, 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-router-route-modals/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import generouted from '@generouted/react-router/plugin' 4 | 5 | export default defineConfig({ 6 | plugins: [react(), generouted()], 7 | resolve: { alias: { '@': '/src' } }, 8 | }) 9 | -------------------------------------------------------------------------------- /examples/react-router/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/react-router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Basic example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/react-router", 3 | "private": true, 4 | "description": "Generated file-based routes for React Router and Vite plugin example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build", 10 | "type-check": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@generouted/react-router": "^1.20.0", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0", 16 | "react-router": "^7.1.5" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^19.0.8", 20 | "@types/react-dom": "^19.0.3", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "prettier": "^3.4.2", 23 | "typescript": "^5.7.3", 24 | "vite": "^6.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/react-router/readme.md: -------------------------------------------------------------------------------- 1 | # Generouted + React Router Example 2 | 3 | ## Preview 4 | 5 | Run this example online via [StackBlitz](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router): 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/react-router) 8 | -------------------------------------------------------------------------------- /examples/react-router/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | import { Routes } from '@generouted/react-router' 3 | 4 | const container = document.getElementById('app')! 5 | createRoot(container).render() 6 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function Layout() { 4 | return ( 5 |
6 |

Auth Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/(auth)/login.tsx: -------------------------------------------------------------------------------- 1 | export default function Login() { 2 | return

Login

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/(auth)/register.tsx: -------------------------------------------------------------------------------- 1 | export default function Register() { 2 | return

Register

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/+modal.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router' 2 | 3 | import { useModals } from '@/router' 4 | 5 | export default function Welcome() { 6 | const location = useLocation() 7 | const modals = useModals() 8 | 9 | const handleClose = () => modals.close() 10 | 11 | return ( 12 |
15 |
16 |
17 |

Global Modal!

18 |

Current pathname: {location.pathname}

19 | 20 | 21 | 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | import { Link, useModals, useNavigate, useParams } from '../router' 4 | 5 | export const Catch = () => { 6 | return
Something went wrong... Caught at _app error boundary
7 | } 8 | 9 | export const Pending = () =>
Loading from _app...
10 | 11 | export default function App() { 12 | const navigate = useNavigate() 13 | const modals = useModals() 14 | const { id, pid } = useParams('/posts/:id/:pid?') 15 | 16 | const a = () => navigate('/posts/:id', { params: { id: 'a' } }) 17 | const b = () => navigate('/posts/:id', { params: { id: '' } }) 18 | const c = () => navigate(-1) 19 | const d = () => navigate('/posts/:id/deep', { params: { id: 'd' } }) 20 | const e = () => navigate('/posts/:id/deep', { params: { id: 'e' } }) 21 | 22 | return ( 23 |
24 |
25 | Home 26 | About 27 | Posts 28 | 29 | Posts by id/pid 30 | 31 | 32 | Posts by id 33 | 34 | 35 | 36 | 37 |
38 | 39 |
40 | 41 |
42 |
43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Basic

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export const Loader = () => 'Route loader' 2 | export const Action = () => 'Route action' 3 | export const Catch = () =>
Something went wrong...
4 | 5 | export const Pending = () =>
Loading...
6 | 7 | export default function Home() { 8 | return

Home - Basic

9 | } 10 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/posts/[id]/-[pid].tsx: -------------------------------------------------------------------------------- 1 | export default function IdPid() { 2 | return

IdPid

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/posts/[id]/deep.tsx: -------------------------------------------------------------------------------- 1 | export default function IdDeep() { 2 | return

IdDeep

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/posts/[id]/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMatch } from 'react-router' 2 | 3 | import { useParams } from '@/router' 4 | 5 | export default function Id() { 6 | // const { params } = useMatch('/posts/$id') 7 | const { id } = useParams('/posts/:id') 8 | const match = useMatch('/posts/:id') 9 | 10 | return

Id

11 | } 12 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/posts/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | export default function About() { 4 | return ( 5 |
6 |

Posts Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/posts/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return

Index

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router/src/pages/splat/[...all].tsx: -------------------------------------------------------------------------------- 1 | export default function SplatAll() { 2 | return

All

3 | } 4 | -------------------------------------------------------------------------------- /examples/react-router/src/router.ts: -------------------------------------------------------------------------------- 1 | // Generouted, changes to this file will be overridden 2 | /* eslint-disable */ 3 | 4 | import { components, hooks, utils } from '@generouted/react-router/client' 5 | 6 | export type Path = 7 | | `/` 8 | | `/about` 9 | | `/login` 10 | | `/posts` 11 | | `/posts/:id` 12 | | `/posts/:id/:pid?` 13 | | `/posts/:id/deep` 14 | | `/register` 15 | | `/splat/*` 16 | 17 | export type Params = { 18 | '/posts/:id': { id: string } 19 | '/posts/:id/:pid?': { id: string; pid?: string } 20 | '/posts/:id/deep': { id: string } 21 | '/splat/*': { '*': string } 22 | } 23 | 24 | export type ModalPath = `/modal` 25 | 26 | export const { Link, Navigate } = components() 27 | export const { useModals, useNavigate, useParams } = hooks() 28 | export const { redirect } = utils() 29 | -------------------------------------------------------------------------------- /examples/react-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "paths": { "@/*": ["src/*"] }, 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/react-router/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import generouted from '@generouted/react-router/plugin' 4 | 5 | export default defineConfig({ 6 | plugins: [react(), generouted()], 7 | resolve: { alias: { '@': '/src' } }, 8 | }) 9 | -------------------------------------------------------------------------------- /examples/solid-router/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/solid-router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Basic example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/solid-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/solid-router", 3 | "private": true, 4 | "description": "Generated file-based routes for Solid Router and Vite example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build", 10 | "type-check": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@generouted/solid-router": "^1.20.0", 14 | "@solidjs/router": "^0.15.3", 15 | "solid-js": "^1.9.4" 16 | }, 17 | "devDependencies": { 18 | "typescript": "^5.7.3", 19 | "vite": "^6.1.0", 20 | "vite-plugin-solid": "^2.11.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/solid-router/readme.md: -------------------------------------------------------------------------------- 1 | # Generouted + Solid Router Example 2 | 3 | ## Preview 4 | 5 | Run this example online via [StackBlitz](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/solid-router): 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github.com/oedotme/generouted/tree/main/examples/solid-router) 8 | -------------------------------------------------------------------------------- /examples/solid-router/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { render } from 'solid-js/web' 2 | import { Routes } from '@generouted/solid-router' 3 | 4 | render(Routes, document.getElementById('app') as HTMLElement) 5 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { ParentProps } from 'solid-js' 2 | 3 | export default function Layout(props: ParentProps) { 4 | return ( 5 |
6 |

Auth Layout

7 | {props.children} 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/(auth)/login.tsx: -------------------------------------------------------------------------------- 1 | export default function Login() { 2 | return

Login

3 | } 4 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/(auth)/register.tsx: -------------------------------------------------------------------------------- 1 | export default function Register() { 2 | return

Register

3 | } 4 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/+modal.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from '@solidjs/router' 2 | 3 | import { useModals } from '@/router' 4 | 5 | export default function Welcome() { 6 | const location = useLocation() 7 | const modals = useModals() 8 | 9 | const handleClose = () => modals.close() 10 | 11 | return ( 12 |
21 |
22 |
23 |

Global Modal!

24 |

Current pathname: {location.pathname}

25 | 26 | 27 | 28 |
29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/-[lang]/lang.tsx: -------------------------------------------------------------------------------- 1 | export default function Lang() { 2 | return

Lang

3 | } 4 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { ParentProps } from 'solid-js' 2 | 3 | import { A, useModals, useNavigate } from '@/router' 4 | 5 | export const Catch = (props: { error: Error; reset: () => void }) => { 6 | return ( 7 |
8 | Something went wrong: {props.error.message} 9 | Caught at _app error boundary 10 | 11 |
12 | ) 13 | } 14 | 15 | export const Pending = () =>
Loading from _app...
16 | 17 | export default function App(props: ParentProps) { 18 | const navigate = useNavigate() 19 | const modals = useModals() 20 | 21 | const a = () => navigate('/about') 22 | const b = () => navigate('/posts/:id/:pid?', { params: { id: 'xyz' } }) 23 | 24 | return ( 25 |
26 |
27 | Home 28 | About 29 | 30 | Post by Id 31 | 32 | 33 |
34 | 35 |
{props.children}
36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Basic

3 | } 4 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export const Catch = (props: { error: Error; reset: () => void }) => ( 2 |
3 | Couldn't load 4 |
{props.error.toString()}
5 |
6 | ) 7 | 8 | export const Pending = () =>
Loading...
9 | 10 | export default function Home() { 11 | return

Home - Basic

12 | } 13 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/posts/[id]/-[pid].tsx: -------------------------------------------------------------------------------- 1 | export default function IdPid() { 2 | return

IdPid

3 | } 4 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/posts/[id]/deep.tsx: -------------------------------------------------------------------------------- 1 | export default function IdDeep() { 2 | return

IdDeep

3 | } 4 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/posts/[id]/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMatch } from '@solidjs/router' 2 | 3 | import { useParams } from '../../../router' 4 | 5 | export default function Id() { 6 | // const { params } = useMatch('/posts/$id') 7 | const { id } = useParams('/posts/:id') 8 | console.log({ id }) 9 | const match = useMatch(() => '/posts/:id') 10 | console.log({ match: match()?.params.d }) 11 | 12 | return

Id

13 | } 14 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/posts/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { ParentProps } from 'solid-js' 2 | 3 | export const Catch = () => 'Error' 4 | 5 | export default function Posts(props: ParentProps) { 6 | return ( 7 |
8 |

Posts Layout

9 | {props.children} 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /examples/solid-router/src/pages/posts/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return

Index

3 | } 4 | -------------------------------------------------------------------------------- /examples/solid-router/src/router.ts: -------------------------------------------------------------------------------- 1 | // Generouted, changes to this file will be overridden 2 | /* eslint-disable */ 3 | 4 | import { components, hooks } from '@generouted/solid-router/client' 5 | 6 | export type Path = 7 | | `/` 8 | | `/:lang?/lang` 9 | | `/about` 10 | | `/login` 11 | | `/posts` 12 | | `/posts/:id` 13 | | `/posts/:id/:pid?` 14 | | `/posts/:id/deep` 15 | | `/register` 16 | 17 | export type Params = { 18 | '/:lang?/lang': { lang?: string } 19 | '/posts/:id': { id: string } 20 | '/posts/:id/:pid?': { id: string; pid?: string } 21 | '/posts/:id/deep': { id: string } 22 | } 23 | 24 | export type ModalPath = `/modal` 25 | 26 | export const { A, Navigate } = components() 27 | export const { useMatch, useModals, useNavigate, useParams } = hooks() 28 | -------------------------------------------------------------------------------- /examples/solid-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "jsxImportSource": "solid-js", 18 | "paths": { "@/*": ["src/*"] }, 19 | "types": ["vite/client"] 20 | }, 21 | "include": ["./src"] 22 | } 23 | -------------------------------------------------------------------------------- /examples/solid-router/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import solid from 'vite-plugin-solid' 3 | import generouted from '@generouted/solid-router/plugin' 4 | 5 | export default defineConfig({ 6 | plugins: [solid(), generouted()], 7 | resolve: { alias: { '@': '/src' } }, 8 | }) 9 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Generouted - Basic example 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/tanstack-react-router", 3 | "private": true, 4 | "description": "Generated file-based routes for TanStack React Router and Vite example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "build": "vite build", 10 | "type-check": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@generouted/tanstack-react-router": "^1.20.0", 14 | "@tanstack/react-router": "^1.99.9", 15 | "react": "^19.0.0", 16 | "react-dom": "^19.0.0" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^19.0.8", 20 | "@types/react-dom": "^19.0.3", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "fast-glob": "^3.3.3", 23 | "prettier": "^3.4.2", 24 | "typescript": "^5.7.3", 25 | "vite": "^6.1.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client' 2 | 3 | import { Routes } from './routes.gen' 4 | 5 | const container = document.getElementById('app')! 6 | createRoot(container).render() 7 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from '@tanstack/react-router' 2 | 3 | export default function Layout() { 4 | return ( 5 |
6 |

Auth Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/(auth)/login.tsx: -------------------------------------------------------------------------------- 1 | export default function Login() { 2 | return

Login

3 | } 4 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/(auth)/register.tsx: -------------------------------------------------------------------------------- 1 | export default function Register() { 2 | return

Register

3 | } 4 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return

404

3 | } 4 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Outlet } from '@tanstack/react-router' 2 | 3 | export default function App() { 4 | return ( 5 |
6 |
7 | Home 8 | About 9 | 10 | Posts 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | export default function About() { 2 | return

About - Basic

3 | } 4 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export const Loader = () => console.log('Route loader') 2 | export const Action = () => console.log('Route action') 3 | 4 | export const Pending = () =>
Route pending
5 | export const Catch = () =>
Route error
6 | 7 | export default function Home() { 8 | return

Home - Basic

9 | } 10 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/posts/[id].tsx: -------------------------------------------------------------------------------- 1 | import { LoaderFnContext, useMatch } from '@tanstack/react-router' 2 | 3 | export const Loader = (context: LoaderFnContext) => { 4 | console.log({ params: context.params }) 5 | return context.params 6 | } 7 | 8 | export default function Id() { 9 | const { params } = useMatch({ from: '/posts/$id' }) 10 | 11 | return

{params.id}

12 | } 13 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/posts/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from '@tanstack/react-router' 2 | 3 | export default function About() { 4 | return ( 5 |
6 |

Posts Layout

7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/pages/posts/index.tsx: -------------------------------------------------------------------------------- 1 | export default function Index() { 2 | return

Index

3 | } 4 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/src/routes.gen.tsx: -------------------------------------------------------------------------------- 1 | // Generouted, changes to this file will be overridden 2 | import { Fragment } from 'react' 3 | import { 4 | Outlet, 5 | RouterProvider, 6 | createLazyRoute, 7 | createRootRoute, 8 | createRoute, 9 | createRouter, 10 | } from '@tanstack/react-router' 11 | 12 | import App from './pages/_app' 13 | import NoMatch from './pages/404' 14 | 15 | const root = createRootRoute({ component: App || Outlet }) 16 | const _404 = createRoute({ getParentRoute: () => root, path: '*', component: NoMatch || Fragment }) 17 | const posts = createRoute({ getParentRoute: () => root, path: 'posts' }).lazy(() => 18 | import('./pages/posts/_layout').then((m) => createLazyRoute('/posts')({ component: m.default })), 19 | ) 20 | const postsindex = createRoute({ getParentRoute: () => posts, path: '/' }).lazy(() => 21 | import('./pages/posts/index').then((m) => createLazyRoute('/posts')({ component: m.default })), 22 | ) 23 | const postsid = createRoute({ 24 | getParentRoute: () => posts, 25 | path: '$id', 26 | // @ts-ignore 27 | loader: (...args) => import('./pages/posts/[id]').then((m) => m.Loader(...args)), 28 | }).lazy(() => import('./pages/posts/[id]').then((m) => createLazyRoute('/posts/$id')({ component: m.default }))) 29 | const auth = createRoute({ getParentRoute: () => root, id: 'auth' }).lazy(() => 30 | import('./pages/(auth)/_layout').then((m) => createLazyRoute('/auth')({ component: m.default })), 31 | ) 32 | const authregister = createRoute({ getParentRoute: () => auth, path: 'register' }).lazy(() => 33 | import('./pages/(auth)/register').then((m) => createLazyRoute('/auth/register')({ component: m.default })), 34 | ) 35 | const authlogin = createRoute({ getParentRoute: () => auth, path: 'login' }).lazy(() => 36 | import('./pages/(auth)/login').then((m) => createLazyRoute('/auth/login')({ component: m.default })), 37 | ) 38 | const about = createRoute({ getParentRoute: () => root, path: 'about' }).lazy(() => 39 | import('./pages/about').then((m) => createLazyRoute('/about')({ component: m.default })), 40 | ) 41 | const index = createRoute({ 42 | getParentRoute: () => root, 43 | path: '/', 44 | // @ts-ignore 45 | loader: (...args) => import('./pages/index').then((m) => m.Loader(...args)), 46 | }).lazy(() => 47 | import('./pages/index').then((m) => 48 | createLazyRoute('/')({ component: m.default, pendingComponent: m.Pending, errorComponent: m.Catch }), 49 | ), 50 | ) 51 | 52 | const config = root.addChildren([ 53 | posts.addChildren([postsindex, postsid]), 54 | auth.addChildren([authregister, authlogin]), 55 | about, 56 | index, 57 | _404, 58 | ]) 59 | 60 | const router = createRouter({ routeTree: config }) 61 | export const routes = config 62 | export const Routes = () => 63 | 64 | declare module '@tanstack/react-router' { 65 | interface Register { 66 | router: typeof router 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "types": ["vite/client"] 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /examples/tanstack-react-router/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import generouted from '@generouted/tanstack-react-router' 4 | 5 | export default defineConfig({ plugins: [react(), generouted()] }) 6 | -------------------------------------------------------------------------------- /explorer/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /explorer/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | printWidth: 120, 4 | semi: false, 5 | singleQuote: true, 6 | tabWidth: 2, 7 | trailingComma: 'es5', 8 | useTabs: false, 9 | plugins: ['prettier-plugin-tailwindcss'], 10 | } 11 | -------------------------------------------------------------------------------- /explorer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Generouted Explorer - Interactive Playground for File-based Routing 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /explorer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@generouted/explorer", 3 | "private": true, 4 | "description": "Generated file-based routes for React Router and Vite example", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vite dev --port 3000", 9 | "preview": "vite preview --port 5000", 10 | "type-check": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@generouted/react-router": "^1.20.0", 14 | "react": "^19.0.0", 15 | "react-dom": "^19.0.0", 16 | "react-router": "^7.1.5" 17 | }, 18 | "devDependencies": { 19 | "@types/react": "^19.0.8", 20 | "@types/react-dom": "^19.0.3", 21 | "@vitejs/plugin-react": "^4.3.4", 22 | "autoprefixer": "^10.4.20", 23 | "postcss": "^8.5.1", 24 | "prettier": "^3.4.2", 25 | "prettier-plugin-tailwindcss": "^0.6.11", 26 | "tailwindcss": "^3.4.17", 27 | "typescript": "^5.7.3", 28 | "vite": "^6.1.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /explorer/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { tailwindcss: {}, autoprefixer: {} }, 3 | } 4 | -------------------------------------------------------------------------------- /explorer/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /explorer/readme.md: -------------------------------------------------------------------------------- 1 | # Generouted Explorer - File-based Routing Interactive Playground 2 | 3 | ## Preview 4 | 5 | Run this explorer online via [StackBlitz](https://stackblitz.com/github.com/oedotme/generouted/tree/main/explorer): 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github.com/oedotme/generouted/tree/main/explorer) 8 | -------------------------------------------------------------------------------- /explorer/src/components/container.tsx: -------------------------------------------------------------------------------- 1 | import { patterns } from '@generouted/react-router/core' 2 | 3 | import { Arrow } from '@/icons' 4 | import { classNames } from '@/utils' 5 | 6 | type Props = { children?: React.ReactNode; source: string } 7 | 8 | export const Container = ({ children, source = '' }: Props) => { 9 | const file = source.match(/(src\/pages\/.+\.tsx)/)?.[1] 10 | 11 | return ( 12 |
18 | 24 | {file} 25 | 26 | 27 | {children || ( 28 |
29 |
    30 |
    31 |
  • src/pages
  • 32 |
  • src/pages
  • 33 |
    34 | 35 |
    36 | {`/${file}` 37 | ?.replace(...patterns.route) 38 | .split('/') 39 | .map((segment, index) => { 40 | const result = segment 41 | .replace(/^\([\w-]+\)$/g, '') 42 | .replace(...patterns.splat) 43 | .replace(...patterns.param) 44 | .replace(...patterns.slash) 45 | .replace(...patterns.optional) 46 | 47 | return ( 48 |
  • 49 |
    {segment}
    50 | 51 | 52 | 53 |
    59 | {result || segment} 60 |
    61 |
  • 62 | ) 63 | })} 64 |
    65 | 66 |
    67 |
  • 68 | .tsx 69 |
  • 70 |
  • 71 | .tsx 72 |
  • 73 |
    74 |
75 | 76 | 77 | 78 | 79 | 80 |

81 | {`/${file}` 82 | ?.replace(...patterns.route) 83 | .split('/') 84 | .map( 85 | (segment) => 86 | (segment === 'index' || /^\([\w-]+\)$/.test(segment) ? '' : '/') + 87 | segment 88 | .replace(/^\([\w-]+\)$/g, '') 89 | .replace(...patterns.splat) 90 | .replace(...patterns.param) 91 | .replace(...patterns.slash) 92 | .replace(...patterns.optional) 93 | ) 94 | .join('')} 95 |

96 |
97 | )} 98 |
99 | ) 100 | } 101 | -------------------------------------------------------------------------------- /explorer/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './container' 2 | export * from './routes' 3 | -------------------------------------------------------------------------------- /explorer/src/icons/arrow.tsx: -------------------------------------------------------------------------------- 1 | export const Arrow = ({ ...props }) => ( 2 | 11 | 12 | 13 | ) 14 | -------------------------------------------------------------------------------- /explorer/src/icons/directory.tsx: -------------------------------------------------------------------------------- 1 | export const Directory = ({ ...props }) => ( 2 | 3 | 8 | 9 | ) 10 | -------------------------------------------------------------------------------- /explorer/src/icons/file.tsx: -------------------------------------------------------------------------------- 1 | export const File = ({ ...props }) => ( 2 | 3 | 4 | 5 | ) 6 | -------------------------------------------------------------------------------- /explorer/src/icons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './arrow' 2 | export * from './directory' 3 | export * from './file' 4 | -------------------------------------------------------------------------------- /explorer/src/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /explorer/src/main.tsx: -------------------------------------------------------------------------------- 1 | import '@/main.css' 2 | 3 | import { createRoot } from 'react-dom/client' 4 | import { Routes } from '@generouted/react-router' 5 | 6 | const container = document.getElementById('app')! 7 | createRoot(container).render() 8 | -------------------------------------------------------------------------------- /explorer/src/pages/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | import { Container } from '@/components' 4 | 5 | const source = import.meta.url 6 | 7 | export default function Layout() { 8 | return ( 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /explorer/src/pages/(auth)/login.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function Login() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/(auth)/register.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function Register() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/+info.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router' 2 | 3 | import { Arrow } from '@/icons' 4 | import { useModals } from '@/router' 5 | 6 | export default function Info() { 7 | const location = useLocation() 8 | const modals = useModals() 9 | 10 | const handleClose = () => modals.close() 11 | 12 | return ( 13 |
14 |
15 |
16 |

Generouted Explorer

17 | 18 |

Interactive playground for file-based routing

19 |

20 | This playground will help you understand how file-based routing and its conventions work. Any updates you make 21 | to `src/page` will be reflected on the left sidebar! 22 |

23 | 24 | 29 | Try it online via StackBlitz 30 | 31 | 32 | 37 | Check out the repo 38 | 39 | 40 | 41 |
42 | 45 |
46 |
47 |
48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /explorer/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function NotFound() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | import { Routes, Container } from '@/components' 4 | 5 | const source = import.meta.url 6 | 7 | export default function App() { 8 | return ( 9 |
10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /explorer/src/pages/about.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function About() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/blog.w.o.layout.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function About() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/blog/[slug].tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function BlogSlug() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/blog/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router' 2 | 3 | import { Container } from '@/components' 4 | 5 | const source = import.meta.url 6 | 7 | export default function BlogLayout() { 8 | return ( 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /explorer/src/pages/blog/index.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function BlogIndex() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/blog/tags.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function BlogTags() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/docs/-[lang]/index.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function DocsLangIndex() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/docs/-[lang]/resources.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function DocsLangResources() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@/components' 2 | 3 | const source = import.meta.url 4 | 5 | export default function Home() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /explorer/src/router.ts: -------------------------------------------------------------------------------- 1 | // Generouted, changes to this file will be overridden 2 | /* eslint-disable */ 3 | 4 | import { components, hooks, utils } from '@generouted/react-router/client' 5 | 6 | export type Path = 7 | | `/` 8 | | `/about` 9 | | `/blog` 10 | | `/blog/:slug` 11 | | `/blog/tags` 12 | | `/blog/w/o/layout` 13 | | `/docs/:lang?` 14 | | `/docs/:lang?/resources` 15 | | `/login` 16 | | `/register` 17 | 18 | export type Params = { 19 | '/blog/:slug': { slug: string } 20 | '/docs/:lang?': { lang?: string } 21 | '/docs/:lang?/resources': { lang?: string } 22 | } 23 | 24 | export type ModalPath = `/info` 25 | 26 | export const { Link, Navigate } = components() 27 | export const { useModals, useNavigate, useParams } = hooks() 28 | export const { redirect } = utils() 29 | -------------------------------------------------------------------------------- /explorer/src/utils/class-names.ts: -------------------------------------------------------------------------------- 1 | export const classNames = (...list: (false | null | undefined | string)[]): string => { 2 | return list.filter(Boolean).join(' ') 3 | } 4 | -------------------------------------------------------------------------------- /explorer/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './class-names' 2 | -------------------------------------------------------------------------------- /explorer/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 6 | theme: { 7 | extend: { 8 | colors: { 9 | primary: '#436cd7', 10 | }, 11 | fontFamily: { 12 | mono: ['JetBrains Mono', 'Fira Code', ...defaultTheme.fontFamily.mono], 13 | }, 14 | }, 15 | }, 16 | plugins: [], 17 | } 18 | -------------------------------------------------------------------------------- /explorer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "paths": { "@/*": ["src/*"] }, 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /explorer/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "cleanUrls": true, 3 | "github": { "autoJobCancelation": true, "silent": true }, 4 | "rewrites": [{ "source": "/([^.]+)", "destination": "/index" }] 5 | } 6 | -------------------------------------------------------------------------------- /explorer/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import generouted from '@generouted/react-router/plugin' 4 | 5 | export default defineConfig({ 6 | plugins: [react(), generouted()], 7 | resolve: { alias: { '@': '/src' } }, 8 | }) 9 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@generouted/repo", 3 | "packageManager": "pnpm@9.0.0", 4 | "private": true, 5 | "description": "Generated client-side file-based routes for Vite", 6 | "author": "Omar Elhawary (https://omarelhawary.me)", 7 | "license": "MIT", 8 | "repository": "https://github.com/oedotme/generouted", 9 | "bugs": "https://github.com/oedotme/generouted/issues", 10 | "homepage": "https://github.com/oedotme/generouted#readme", 11 | "keywords": [ 12 | "actions", 13 | "code-splitting", 14 | "data-loaders", 15 | "file-based-routing", 16 | "generate", 17 | "nested-layouts", 18 | "nextjs", 19 | "pages", 20 | "pre-loading ", 21 | "react", 22 | "react-location", 23 | "react-router", 24 | "react-router-dom", 25 | "remix", 26 | "router", 27 | "routes", 28 | "solid", 29 | "solid-router", 30 | "typescript", 31 | "vite" 32 | ], 33 | "scripts": { 34 | "build": "turbo build", 35 | "test": "turbo test", 36 | "type-check": "turbo type-check", 37 | "release": "release-it", 38 | "prepare": "husky" 39 | }, 40 | "devDependencies": { 41 | "@commitlint/cli": "^19.7.1", 42 | "@commitlint/config-conventional": "^19.7.1", 43 | "@release-it-plugins/workspaces": "^4.2.0", 44 | "husky": "^9.1.7", 45 | "lint-staged": "^15.4.3", 46 | "prettier": "^3.4.2", 47 | "release-it": "^18.1.2", 48 | "turbo": "^2.4.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/generouted/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generouted", 3 | "version": "1.20.0", 4 | "description": "Generated client-side file-based routes for Vite", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "repository": "https://github.com/oedotme/generouted", 8 | "bugs": "https://github.com/oedotme/generouted/issues", 9 | "homepage": "https://github.com/oedotme/generouted#readme", 10 | "keywords": [ 11 | "actions", 12 | "code-splitting", 13 | "data-loaders", 14 | "file-based-routing", 15 | "generate", 16 | "nested-layouts", 17 | "nextjs", 18 | "pages", 19 | "pre-loading ", 20 | "react", 21 | "react-location", 22 | "react-router", 23 | "react-router-dom", 24 | "remix", 25 | "router", 26 | "routes", 27 | "solid", 28 | "solid-router", 29 | "typescript", 30 | "vite" 31 | ], 32 | "type": "module", 33 | "exports": { 34 | "./core": { 35 | "types": "./src/core.ts", 36 | "import": "./src/core.ts" 37 | }, 38 | "./react-location": { 39 | "types": "./src/react-location.tsx", 40 | "import": "./src/react-location.tsx" 41 | }, 42 | "./react-router-lazy": { 43 | "types": "./src/react-router-lazy.tsx", 44 | "import": "./src/react-router-lazy.tsx" 45 | }, 46 | "./react-router": { 47 | "types": "./src/react-router.tsx", 48 | "import": "./src/react-router.tsx" 49 | }, 50 | "./solid-router-lazy": { 51 | "types": "./src/solid-router-lazy.tsx", 52 | "import": "./src/solid-router-lazy.tsx" 53 | }, 54 | "./solid-router": { 55 | "types": "./src/solid-router.tsx", 56 | "import": "./src/solid-router.tsx" 57 | } 58 | }, 59 | "typesVersions": { 60 | "*": { 61 | "core": [ 62 | "./src/core.ts" 63 | ], 64 | "react-location": [ 65 | "./src/react-location.tsx" 66 | ], 67 | "react-router-lazy": [ 68 | "./src/react-router-lazy.tsx" 69 | ], 70 | "react-router": [ 71 | "./src/react-router.tsx" 72 | ], 73 | "solid-router-lazy": [ 74 | "./src/solid-router-lazy.tsx" 75 | ], 76 | "solid-router": [ 77 | "./src/solid-router.tsx" 78 | ] 79 | } 80 | }, 81 | "files": [ 82 | "src" 83 | ], 84 | "scripts": { 85 | "dev": "vitest watch", 86 | "build": "tsc --emitDeclarationOnly --noEmit false --outDir dist", 87 | "test": "vitest run", 88 | "type-check": "tsc --noEmit" 89 | }, 90 | "devDependencies": { 91 | "@solidjs/router": "^0.15.3", 92 | "@tanstack/react-location": "^3.7.4", 93 | "@types/react": "^19.0.8", 94 | "@types/react-dom": "^19.0.3", 95 | "react": "^19.0.0", 96 | "react-dom": "^19.0.0", 97 | "react-router": "^7.1.5", 98 | "solid-js": "^1.9.4", 99 | "typescript": "^5.7.3", 100 | "vite": "^6.1.0", 101 | "vitest": "^3.0.5" 102 | }, 103 | "peerDependencies": { 104 | "vite": ">=5" 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /packages/generouted/src/core.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | import { generateModalRoutes, generatePreservedRoutes, generateRegularRoutes } from './core' 4 | 5 | test('modal routes generation', () => { 6 | const modules = { 7 | '/src/pages/(auth)/+login.tsx': {}, 8 | '/src/pages/+info.tsx': {}, 9 | '/src/pages/+index.tsx': {}, 10 | '/src/pages/+checkout/+index.tsx': {}, 11 | '/src/pages/+checkout/+review.tsx': {}, 12 | } 13 | 14 | expect(generateModalRoutes(modules)).toStrictEqual({ 15 | '/login': undefined, 16 | '/info': undefined, 17 | '/': undefined, 18 | '/checkout': undefined, 19 | '/checkout/review': undefined, 20 | }) 21 | }) 22 | 23 | test('preserved routes generation', () => { 24 | const modules = { 25 | '/src/pages/404.tsx': {}, 26 | '/src/pages/_app.tsx': {}, 27 | } 28 | 29 | expect(generatePreservedRoutes(modules)).toStrictEqual({ 30 | '404': {}, 31 | _app: {}, 32 | }) 33 | }) 34 | 35 | test('regular routes generation', () => { 36 | const modules = { 37 | '/src/pages/(auth)/_layout.tsx': {}, 38 | '/src/pages/(auth)/login/_layout.tsx': {}, 39 | '/src/pages/(auth)/login/index.tsx': {}, 40 | '/src/pages/(auth)/in/_layout.tsx': {}, 41 | '/src/pages/(auth)/in/index.tsx': {}, 42 | '/src/pages/(auth)/register.tsx': {}, 43 | '/src/pages/(external-auth)/sso.tsx': {}, 44 | '/src/pages/_ignored-directory/components.tsx': {}, 45 | '/src/pages/_ignored-path.tsx': {}, 46 | '/src/pages/about.tsx': {}, 47 | '/src/pages/blog.w.o.layout.tsx': {}, 48 | '/src/pages/blog/-[...all].tsx': {}, 49 | '/src/pages/blog/[slug].tsx': {}, 50 | '/src/pages/blog/_layout.tsx': {}, 51 | '/src/pages/blog/index.tsx': {}, 52 | '/src/pages/blog/tags.tsx': {}, 53 | '/src/pages/content.mdx': {}, 54 | '/src/pages/docs/-[lang]/index.tsx': {}, 55 | '/src/pages/docs/-[lang]/resources.tsx': {}, 56 | '/src/pages/docs/-en/support.tsx': {}, 57 | '/src/pages/index.tsx': {}, 58 | } 59 | 60 | expect(generateRegularRoutes(modules, () => ({}))).toStrictEqual([ 61 | { 62 | path: 'docs', 63 | children: [ 64 | { 65 | path: 'en?', 66 | children: [{ path: 'support', id: 'docs/-en/support' }], 67 | }, 68 | { 69 | path: ':lang?', 70 | children: [ 71 | { path: 'resources', id: 'docs/-[lang]/resources' }, 72 | { path: '/', id: 'docs/-[lang]/index' }, 73 | ], 74 | }, 75 | ], 76 | }, 77 | { 78 | path: 'blog', 79 | id: 'blog/_layout', 80 | children: [ 81 | { path: 'tags', id: 'blog/tags' }, 82 | { path: '/', id: 'blog/index' }, 83 | { path: '*?', id: 'blog/-[...all]' }, 84 | { path: ':slug', id: 'blog/[slug]' }, 85 | ], 86 | }, 87 | { 88 | id: '(auth)/_layout', 89 | children: [ 90 | { path: 'register', id: '(auth)/register' }, 91 | { path: 'in', id: '(auth)/in/_layout', children: [{ path: '/', id: '(auth)/in/index' }] }, 92 | { path: 'login', id: '(auth)/login/_layout', children: [{ path: '/', id: '(auth)/login/index' }] }, 93 | ], 94 | }, 95 | { 96 | id: '(external-auth)', 97 | children: [{ path: 'sso', id: '(external-auth)/sso' }], 98 | }, 99 | { path: 'about', id: 'about' }, 100 | { path: 'blog/w/o/layout', id: 'blog.w.o.layout' }, 101 | { path: 'content', id: 'content' }, 102 | { path: '/', id: 'index' }, 103 | ]) 104 | }) 105 | -------------------------------------------------------------------------------- /packages/generouted/src/core.ts: -------------------------------------------------------------------------------- 1 | export const patterns = { 2 | route: [/^.*\/src\/pages\/|\.(jsx|tsx|mdx)$/g, ''], 3 | splat: [/\[\.{3}\w+\]/g, '*'], 4 | param: [/\[([^\]]+)\]/g, ':$1'], 5 | slash: [/^index$|\./g, '/'], 6 | optional: [/^-(:?[\w-]+|\*)/, '$1?'], 7 | } as const 8 | 9 | type PreservedKey = '_app' | '404' 10 | type BaseRoute = { id?: string; path?: string; children?: BaseRoute[] } & Record 11 | 12 | export const generatePreservedRoutes = (files: Record): Partial> => { 13 | return Object.keys(files).reduce((routes, key) => { 14 | const path = key.replace(...patterns.route) 15 | return { ...routes, [path]: files[key] } 16 | }, {}) 17 | } 18 | 19 | export const generateRegularRoutes = ( 20 | files: Record, 21 | buildRoute: (module: M, key: string) => T, 22 | ) => { 23 | const filteredRoutes = Object.keys(files).filter((key) => !key.includes('/_') || /_layout\.(jsx|tsx)$/.test(key)) 24 | return filteredRoutes.reduce((routes, key) => { 25 | const module = files[key] 26 | const route = { id: key.replace(...patterns.route), ...buildRoute(module, key) } 27 | 28 | const segments = key 29 | .replace(...patterns.route) 30 | .replace(...patterns.splat) 31 | .replace(...patterns.param) 32 | .split('/') 33 | .filter(Boolean) 34 | 35 | segments.reduce((parent, segment, index) => { 36 | const path = segment.replace(...patterns.slash).replace(...patterns.optional) 37 | const root = index === 0 38 | const leaf = index === segments.length - 1 && segments.length > 1 39 | const node = !root && !leaf 40 | const layout = segment === '_layout' 41 | const group = /\([\w-]+\)/.test(path) 42 | const insert = /^\w|\//.test(path) ? 'unshift' : 'push' 43 | 44 | if (root) { 45 | const last = segments.length === 1 46 | if (last) { 47 | routes.push({ path, ...route }) 48 | return parent 49 | } 50 | } 51 | 52 | if (root || node) { 53 | const current = root ? routes : parent.children 54 | const found = current?.find((r) => r.path === path || r.id?.replace('/_layout', '').split('/').pop() === path) 55 | const props = group ? (route?.component ? { id: path, path: '/' } : { id: path }) : { path } 56 | if (found) found.children ??= [] 57 | else current?.[insert]({ ...props, children: [] }) 58 | return found || (current?.[insert === 'unshift' ? 0 : current.length - 1] as BaseRoute) 59 | } 60 | 61 | if (layout) { 62 | return Object.assign(parent, route) 63 | } 64 | 65 | if (leaf) { 66 | parent?.children?.[insert](route?.index ? route : { path, ...route }) 67 | } 68 | 69 | return parent 70 | }, {} as BaseRoute) 71 | 72 | return routes 73 | }, []) 74 | } 75 | 76 | export const generateModalRoutes = (files: Record): Record => { 77 | return Object.keys(files).reduce((modals, key) => { 78 | const path = key 79 | .replace(...patterns.route) 80 | .replace(/\+|\([\w-]+\)\//g, '') 81 | .replace(/(\/)?index/g, '') 82 | .replace(/\./g, '/') 83 | 84 | return { ...modals, [`/${path}`]: files[key]?.default } 85 | }, {}) 86 | } 87 | -------------------------------------------------------------------------------- /packages/generouted/src/react-location.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, JSX } from 'react' 2 | import { LoaderFn, Outlet, ReactLocation, Route, Router, RouterProps } from '@tanstack/react-location' 3 | 4 | import { generatePreservedRoutes, generateRegularRoutes } from './core' 5 | 6 | type Element = () => JSX.Element 7 | type Module = { default: Element; Loader: LoaderFn; Pending: Element; Catch: Element } 8 | 9 | const PRESERVED = import.meta.glob('/src/pages/(_app|404).{jsx,tsx}', { eager: true }) 10 | const ROUTES = import.meta.glob([ 11 | '/src/pages/**/[\\w[-]*.{jsx,tsx}', 12 | '!/src/pages/**/(_!(layout)*(/*)?|_app|404)*', 13 | ]) 14 | 15 | const preservedRoutes = generatePreservedRoutes(PRESERVED) 16 | 17 | const regularRoutes = generateRegularRoutes Promise>(ROUTES, (module) => ({ 18 | element: () => module().then((mod) => (mod?.default ? : null)), 19 | loader: (...args) => module().then((mod) => mod?.Loader?.(...args) || null), 20 | pendingElement: () => module().then((mod) => (mod?.Pending ? : null)), 21 | errorElement: () => module().then((mod) => (mod?.Catch ? : null)), 22 | })) 23 | 24 | const App = preservedRoutes?.['_app']?.default || Fragment 25 | const NotFound = preservedRoutes?.['404']?.default || Fragment 26 | 27 | const location = new ReactLocation() 28 | export const routes = [...regularRoutes, { path: '*', element: }] 29 | 30 | export const Routes = (props: Omit = {}) => { 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /packages/generouted/src/react-router-lazy.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, JSX, Suspense } from 'react' 2 | import { createBrowserRouter, Outlet, RouterProvider, useLocation } from 'react-router' 3 | import type { ActionFunction, RouteObject, LoaderFunction } from 'react-router' 4 | 5 | import { generateModalRoutes, generatePreservedRoutes, generateRegularRoutes } from './core' 6 | 7 | type Element = () => JSX.Element 8 | type Module = { default: Element; Loader?: LoaderFunction; Action?: ActionFunction; Catch?: Element; Pending?: Element } 9 | 10 | const PRESERVED = import.meta.glob('/src/pages/(_app|404).{jsx,tsx}', { eager: true }) 11 | const MODALS = import.meta.glob>('/src/pages/**/[+]*.{jsx,tsx}', { eager: true }) 12 | const ROUTES = import.meta.glob([ 13 | '/src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', 14 | '!/src/pages/**/(_!(layout)*(/*)?|_app|404)*', 15 | ]) 16 | 17 | const preservedRoutes = generatePreservedRoutes>(PRESERVED) 18 | const modalRoutes = generateModalRoutes(MODALS) 19 | 20 | const regularRoutes = generateRegularRoutes Promise>>(ROUTES, (module, key) => { 21 | const index = /index\.(jsx|tsx|mdx)$/.test(key) && !key.includes('pages/index') ? { index: true } : {} 22 | 23 | return { 24 | ...index, 25 | lazy: async () => { 26 | const Default = (await module())?.default || Fragment 27 | const Pending = (await module())?.Pending 28 | const Page = () => (Pending ? } children={} /> : ) 29 | 30 | return { 31 | Component: Page, 32 | ErrorBoundary: (await module())?.Catch, 33 | loader: (await module())?.Loader, 34 | action: (await module())?.Action, 35 | } 36 | }, 37 | } 38 | }) 39 | 40 | const _app = preservedRoutes?.['_app'] 41 | const _404 = preservedRoutes?.['404'] 42 | 43 | const Default = _app?.default || Outlet 44 | 45 | const Modals_ = () => { 46 | const Modal = modalRoutes[useLocation().state?.modal] || Fragment 47 | return 48 | } 49 | 50 | const Layout = () => ( 51 | <> 52 | 53 | 54 | ) 55 | 56 | const App = () => (_app?.Pending ? } children={} /> : ) 57 | 58 | const app = { Component: _app?.default ? App : Layout, ErrorBoundary: _app?.Catch, loader: _app?.Loader } 59 | const fallback = { path: '*', Component: _404?.default || Fragment } 60 | 61 | export const routes: RouteObject[] = [{ ...app, children: [...regularRoutes, fallback] }] 62 | let router: ReturnType 63 | const createRouter = () => ((router ??= createBrowserRouter(routes)), router) 64 | export const Routes = () => 65 | 66 | /** @deprecated `` is no longer needed, it will be removed in future releases */ 67 | export const Modals = () => (console.warn('[generouted] `` will be removed in future releases'), null) 68 | -------------------------------------------------------------------------------- /packages/generouted/src/react-router.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, JSX, Suspense } from 'react' 2 | import { createBrowserRouter, Outlet, RouterProvider, useLocation } from 'react-router' 3 | import type { ActionFunction, RouteObject, LoaderFunction } from 'react-router' 4 | 5 | import { generateModalRoutes, generatePreservedRoutes, generateRegularRoutes } from './core' 6 | 7 | type Element = () => JSX.Element 8 | type Module = { default: Element; Loader?: LoaderFunction; Action?: ActionFunction; Catch?: Element; Pending?: Element } 9 | 10 | const PRESERVED = import.meta.glob('/src/pages/(_app|404).{jsx,tsx}', { eager: true }) 11 | const MODALS = import.meta.glob>('/src/pages/**/[+]*.{jsx,tsx}', { eager: true }) 12 | const ROUTES = import.meta.glob( 13 | ['/src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', '!/src/pages/**/(_!(layout)*(/*)?|_app|404)*'], 14 | { eager: true }, 15 | ) 16 | 17 | const preservedRoutes = generatePreservedRoutes>(PRESERVED) 18 | const modalRoutes = generateModalRoutes(MODALS) 19 | 20 | const regularRoutes = generateRegularRoutes>(ROUTES, (module, key) => { 21 | const index = /index\.(jsx|tsx|mdx)$/.test(key) && !key.includes('pages/index') ? { index: true } : {} 22 | const Default = module?.default || Fragment 23 | const Page = () => (module?.Pending ? } children={} /> : ) 24 | return { ...index, Component: Page, ErrorBoundary: module?.Catch, loader: module?.Loader, action: module?.Action } 25 | }) 26 | 27 | const _app = preservedRoutes?.['_app'] 28 | const _404 = preservedRoutes?.['404'] 29 | 30 | const Default = _app?.default || Outlet 31 | 32 | const Modals_ = () => { 33 | const Modal = modalRoutes[useLocation().state?.modal] || Fragment 34 | return 35 | } 36 | 37 | const Layout = () => ( 38 | <> 39 | 40 | 41 | ) 42 | 43 | const App = () => (_app?.Pending ? } children={} /> : ) 44 | 45 | const app = { Component: _app?.default ? App : Layout, ErrorBoundary: _app?.Catch, loader: _app?.Loader } 46 | const fallback = { path: '*', Component: _404?.default || Fragment } 47 | 48 | export const routes: RouteObject[] = [{ ...app, children: [...regularRoutes, fallback] }] 49 | let router: ReturnType 50 | const createRouter = () => ((router ??= createBrowserRouter(routes)), router) 51 | export const Routes = () => 52 | 53 | /** @deprecated `` is no longer needed, it will be removed in future releases */ 54 | export const Modals = () => (console.warn('[generouted] `` will be removed in future releases'), null) 55 | -------------------------------------------------------------------------------- /packages/generouted/src/solid-router-lazy.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | import { Component, createMemo, ErrorBoundary, lazy, ParentProps, Show, Suspense } from 'solid-js' 3 | import { RouteDefinition, RouteLoadFunc, RouteLoadFuncArgs, Router, useLocation } from '@solidjs/router' 4 | 5 | import { generateModalRoutes, generatePreservedRoutes, generateRegularRoutes } from './core' 6 | 7 | type CatchProps = { error: any; reset: () => void } 8 | type Module = { default: Component; Loader?: RouteLoadFunc; Catch?: Component; Pending?: Component } 9 | type Route = { path?: string; component?: Component; children?: Route[] } 10 | 11 | const PRESERVED = import.meta.glob('/src/pages/(_app|404).{jsx,tsx}', { eager: true }) 12 | const MODALS = import.meta.glob>('/src/pages/**/[+]*.{jsx,tsx}', { eager: true }) 13 | const ROUTES = import.meta.glob([ 14 | '/src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', 15 | '!/src/pages/**/(_!(layout)*(/*)?|_app|404)*', 16 | ]) 17 | 18 | const preservedRoutes = generatePreservedRoutes(PRESERVED) 19 | const modalRoutes = generateModalRoutes(MODALS) 20 | 21 | const regularRoutes = generateRegularRoutes Promise>(ROUTES, (module) => { 22 | const Default = lazy(module) 23 | const Pending = lazy(() => module().then((module) => ({ default: module?.Pending || Fragment }))) 24 | const Catch = lazy(() => module().then((module) => ({ default: module?.Catch || Fragment }))) 25 | const Page = (props: any) => } children={} /> 26 | const Component = (props: any) => ( 27 | Catch({ error, reset })} children={} /> 28 | ) 29 | 30 | return { 31 | component: Component, 32 | load: (args: RouteLoadFuncArgs) => module().then((mod) => mod?.Loader?.(args) || undefined), 33 | } 34 | }) 35 | const _app = preservedRoutes?.['_app'] 36 | const _404 = preservedRoutes?.['404'] 37 | 38 | const Fragment = (props: ParentProps) => props?.children 39 | const Default = _app?.default || Fragment 40 | const Pending = _app?.Pending || Fragment 41 | const Catch = preservedRoutes?.['_app']?.Catch 42 | const Modals_ = () => createMemo(() => modalRoutes[useLocation().state?.modal || ''] || Fragment) as any 43 | 44 | const Layout = (props: ParentProps) => ( 45 | <> 46 | 47 | 48 | ) 49 | 50 | const App = (props: ParentProps) => ( 51 | Catch?.({ error, reset })}> 52 | }> 53 | } children={} /> 54 | 55 | 56 | ) 57 | 58 | const app: RouteDefinition = { path: '', component: _app?.default ? App : Layout, load: _app?.Loader || undefined } 59 | const fallback: RouteDefinition = { path: '*', component: _404?.default || Fragment } 60 | 61 | export const routes: RouteDefinition[] = [{ ...app, children: [...regularRoutes, fallback] }] 62 | export const Routes = () => {routes} 63 | 64 | /** @deprecated `` is no longer needed, it will be removed in future releases */ 65 | export const Modals = () => (console.warn('[generouted] `` will be removed in future releases'), null) 66 | -------------------------------------------------------------------------------- /packages/generouted/src/solid-router.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | import { Component, createMemo, ErrorBoundary, ParentProps, Show, Suspense } from 'solid-js' 3 | import { RouteDefinition, RouteLoadFunc, Router, useLocation } from '@solidjs/router' 4 | 5 | import { generateModalRoutes, generatePreservedRoutes, generateRegularRoutes } from './core' 6 | 7 | type CatchProps = { error: any; reset: () => void } 8 | type Module = { default: Component; Loader?: RouteLoadFunc; Catch?: Component; Pending?: Component } 9 | type RouteDef = { path?: string; component?: Component; children?: RouteDef[] } 10 | 11 | const PRESERVED = import.meta.glob('/src/pages/(_app|404).{jsx,tsx}', { eager: true }) 12 | const MODALS = import.meta.glob>('/src/pages/**/[+]*.{jsx,tsx}', { eager: true }) 13 | const ROUTES = import.meta.glob( 14 | ['/src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', '!/src/pages/**/(_!(layout)*(/*)?|_app|404)*'], 15 | { eager: true }, 16 | ) 17 | 18 | const preservedRoutes = generatePreservedRoutes(PRESERVED) 19 | const modalRoutes = generateModalRoutes(MODALS) 20 | 21 | const regularRoutes = generateRegularRoutes(ROUTES, (mod) => { 22 | const Default = mod?.default || Fragment 23 | const Page = (props: any) => 24 | mod?.Pending ? } children={} /> : 25 | const Component = (props: any) => 26 | mod?.Catch ? ( 27 | mod.Catch?.({ error, reset })} children={} /> 28 | ) : ( 29 | 30 | ) 31 | return { component: Component, load: mod?.Loader || undefined } 32 | }) 33 | 34 | const _app = preservedRoutes?.['_app'] 35 | const _404 = preservedRoutes?.['404'] 36 | 37 | const Fragment = (props: ParentProps) => props?.children 38 | const Default = _app?.default || Fragment 39 | const Pending = _app?.Pending || Fragment 40 | const Catch = preservedRoutes?.['_app']?.Catch 41 | const Modals_ = () => createMemo(() => modalRoutes[useLocation().state?.modal || ''] || Fragment) as any 42 | 43 | const Layout = (props: ParentProps) => ( 44 | <> 45 | 46 | 47 | ) 48 | 49 | const App = (props: ParentProps) => ( 50 | Catch?.({ error, reset })}> 51 | }> 52 | } children={} /> 53 | 54 | 55 | ) 56 | 57 | const app: RouteDefinition = { path: '', component: _app?.default ? App : Layout, load: _app?.Loader || undefined } 58 | const fallback: RouteDefinition = { path: '*', component: _404?.default || Fragment } 59 | 60 | export const routes: RouteDefinition[] = [{ ...app, children: [...regularRoutes, fallback] }] 61 | export const Routes = () => {routes} 62 | 63 | /** @deprecated `` is no longer needed, it will be removed in future releases */ 64 | export const Modals = () => (console.warn('[generouted] `` will be removed in future releases'), null) 65 | -------------------------------------------------------------------------------- /packages/generouted/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "declaration": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/generouted/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({}) 4 | -------------------------------------------------------------------------------- /packages/react-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@generouted/react-router", 3 | "version": "1.20.0", 4 | "description": "Generated file-based routes for React Router and Vite", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "repository": "https://github.com/oedotme/generouted", 8 | "bugs": "https://github.com/oedotme/generouted/issues", 9 | "homepage": "https://github.com/oedotme/generouted#readme", 10 | "keywords": [ 11 | "actions", 12 | "code-splitting", 13 | "data-loaders", 14 | "file-based-routing", 15 | "generate", 16 | "nested-layouts", 17 | "nextjs", 18 | "pages", 19 | "pre-loading ", 20 | "react", 21 | "react-location", 22 | "react-router", 23 | "react-router-dom", 24 | "remix", 25 | "router", 26 | "routes", 27 | "solid", 28 | "solid-router", 29 | "typescript", 30 | "vite" 31 | ], 32 | "type": "module", 33 | "exports": { 34 | ".": { 35 | "types": "./dist/index.d.ts", 36 | "import": "./dist/index.js" 37 | }, 38 | "./core": { 39 | "types": "./dist/core.d.ts", 40 | "import": "./dist/core.js" 41 | }, 42 | "./lazy": { 43 | "types": "./dist/index-lazy.d.ts", 44 | "import": "./dist/index-lazy.js" 45 | }, 46 | "./client": { 47 | "types": "./dist/client/index.d.ts", 48 | "import": "./dist/client/index.js" 49 | }, 50 | "./plugin": { 51 | "types": "./dist/plugin/index.d.ts", 52 | "require": "./dist/plugin/index.cjs", 53 | "import": "./dist/plugin/index.js" 54 | } 55 | }, 56 | "typesVersions": { 57 | "*": { 58 | "*": [ 59 | "./dist/index.d.ts" 60 | ], 61 | "core": [ 62 | "./dist/core.d.ts" 63 | ], 64 | "lazy": [ 65 | "./dist/index-lazy.d.ts" 66 | ], 67 | "client": [ 68 | "./dist/client/index.d.ts" 69 | ], 70 | "plugin": [ 71 | "./dist/plugin/index.d.ts" 72 | ] 73 | } 74 | }, 75 | "files": [ 76 | "dist" 77 | ], 78 | "scripts": { 79 | "dev": "tsup --watch", 80 | "build": "tsup --clean", 81 | "type-check": "tsc --noEmit" 82 | }, 83 | "dependencies": { 84 | "fast-glob": "^3.3.3", 85 | "generouted": "^1.20.0" 86 | }, 87 | "devDependencies": { 88 | "@generouted/core": "workspace:*", 89 | "@types/react": "^19.0.8", 90 | "react-router": "^7.1.5", 91 | "tsup": "^8.3.6", 92 | "typescript": "^5.7.3", 93 | "vite": "^6.1.0" 94 | }, 95 | "peerDependencies": { 96 | "react": ">=18", 97 | "react-router": ">=7", 98 | "vite": ">=5" 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /packages/react-router/src/client/components.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react' 2 | import { generatePath, Link, Navigate } from 'react-router' 3 | import { LinkProps, To } from './types' 4 | 5 | type LinkRef = React.ForwardedRef 6 | 7 | export const components = >() => { 8 | return { 9 | // @ts-expect-error 10 | Link: forwardRef(

>({ to, params, ...props }: LinkProps, ref: LinkRef) => { 11 | const path = generatePath(typeof to === 'string' ? to : to.pathname, params || ({} as any)) 12 | return ( 13 | 18 | ) 19 | }), 20 | Navigate:

>({ to, params, ...props }: LinkProps) => { 21 | const path = generatePath(typeof to === 'string' ? to : to.pathname, params || ({} as any)) 22 | return ( 23 | 27 | ) 28 | }, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/react-router/src/client/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo } from 'react' 2 | import { generatePath, NavigateOptions as NavOptions, useLocation, useNavigate, useParams } from 'react-router' 3 | import { NavigateOptions, To } from './types' 4 | 5 | export const hooks = , ModalPath extends string>() => { 6 | return { 7 | useParams:

(path: P) => useParams() as Params[P], 8 | useNavigate: () => { 9 | const navigate = useNavigate() 10 | 11 | return useCallback( 12 |

| number>(to: P, ...[options]: NavigateOptions) => { 13 | if (typeof to === 'number') return navigate(to) 14 | const path = generatePath(typeof to === 'string' ? to : to.pathname, options?.params || ({} as any)) 15 | return navigate(typeof to === 'string' ? path : { pathname: path, search: to.search, hash: to.hash }, options) 16 | }, 17 | [navigate], 18 | ) 19 | }, 20 | useModals: () => { 21 | const location = useLocation() 22 | const navigate = useNavigate() 23 | 24 | type Options

= NavOptions & 25 | (P extends keyof Params ? { at?: P; params: Params[P] } : { at?: P; params?: never }) 26 | 27 | return useMemo(() => { 28 | return { 29 | current: location.state?.modal || '', 30 | open:

(path: ModalPath, options?: Options

) => { 31 | const { at, state, ...opts } = options || {} 32 | const pathname = options?.params ? generatePath(at || '', options.params || {}) : at 33 | navigate(pathname || location.pathname, { ...opts, state: { ...location.state, ...state, modal: path } }) 34 | }, 35 | close:

(options?: Options

) => { 36 | const { at, state, ...opts } = options || {} 37 | const pathname = options?.params ? generatePath(at || '', options.params || {}) : at 38 | navigate(pathname || location.pathname, { ...opts, state: { ...location.state, ...state, modal: '' } }) 39 | }, 40 | } 41 | }, [location, navigate]) 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/react-router/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components' 2 | export * from './hooks' 3 | export * from './types' 4 | export * from './utils' 5 | -------------------------------------------------------------------------------- /packages/react-router/src/client/types.ts: -------------------------------------------------------------------------------- 1 | import { LinkProps as _LinkProps, NavigateOptions as NavOptions, NavigateProps as NavProps } from 'react-router' 2 | 3 | export type To = { pathname: Pathname; search?: string; hash?: string } 4 | 5 | type ComponentProps> = Path extends keyof Params 6 | ? { to: Path; params: Params[Path] } 7 | : Path extends { pathname: infer Pathname } 8 | ? Pathname extends keyof Params 9 | ? { to: To; params: Params[Pathname] } 10 | : { to: To; params?: never } 11 | : { to: Path; params?: never } 12 | 13 | export type LinkProps> = Omit<_LinkProps, 'to'> & 14 | ComponentProps 15 | 16 | export type NavigateProps> = Omit & 17 | ComponentProps 18 | 19 | export type NavigateOptions> = Path extends number 20 | ? [] 21 | : Path extends keyof Params 22 | ? [NavOptions & { params: Params[Path] }] 23 | : Path extends { pathname: infer Pathname } 24 | ? Pathname extends keyof Params 25 | ? [NavOptions & { params: Params[Pathname] }] 26 | : [NavOptions & { params?: never }] | [] 27 | : [NavOptions & { params?: never }] | [] 28 | -------------------------------------------------------------------------------- /packages/react-router/src/client/utils.ts: -------------------------------------------------------------------------------- 1 | import { generatePath, redirect } from 'react-router' 2 | 3 | export const utils = >() => { 4 | type Init = number | ResponseInit 5 | type RedirectOptions

= P extends keyof Params ? [Init & { params: Params[P] }] : [Init & { params?: never }] | [] 6 | 7 | return { 8 | redirect:

(url: P, ...[options]: RedirectOptions

) => { 9 | return redirect(options?.params ? generatePath(url, options.params) : url, options) 10 | }, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-router/src/core.ts: -------------------------------------------------------------------------------- 1 | export * from 'generouted/core' 2 | -------------------------------------------------------------------------------- /packages/react-router/src/index-lazy.ts: -------------------------------------------------------------------------------- 1 | export * from 'generouted/react-router-lazy' 2 | -------------------------------------------------------------------------------- /packages/react-router/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from 'generouted/react-router' 2 | -------------------------------------------------------------------------------- /packages/react-router/src/plugin/generate.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import fs from 'fs' 3 | import path from 'path' 4 | 5 | import { createLogger } from 'vite' 6 | import { patterns } from '@generouted/core' 7 | import fg from 'fast-glob' 8 | 9 | import { Options } from './options' 10 | import { template } from './template' 11 | 12 | const generateRouteTypes = async (options: Options) => { 13 | const files = await fg(options.source.routes || './src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', { onlyFiles: true }) 14 | const modal = await fg(options.source.modals || './src/pages/**/[+]*.{jsx,tsx,mdx}', { onlyFiles: true }) 15 | 16 | const filtered = files.filter((key) => !key.includes('/_') && !key.includes('/404')) 17 | const params: string[] = [] 18 | 19 | const paths = filtered.map((key) => { 20 | const path = key 21 | .replace(...patterns.route) 22 | .replace(...patterns.splat) 23 | .replace(...patterns.param) 24 | .replace(/\([\w-]+\)\/|\/?_layout/g, '') 25 | .replace(/\/?index|\./g, '/') 26 | .replace(/(\w)\/$/g, '$1') 27 | .split('/') 28 | .map((segment) => segment.replace(...patterns.optional)) 29 | .join('/') 30 | 31 | if (path) { 32 | const param = path.split('/').filter((segment) => segment.startsWith(':')) 33 | 34 | if (param.length || path.includes('*')) { 35 | const dynamic = param.length ? param.map((p) => p.replace(/:(.+)(\?)?/, '$1$2:') + ' string') : [] 36 | const splat = path.includes('*') ? ["'*': string"] : [] 37 | params.push(`'/${path}': { ${[...dynamic, ...splat].join('; ')} }`) 38 | } 39 | 40 | return path.length > 1 ? `/${path}` : path 41 | } 42 | }) 43 | 44 | const modals = modal.map( 45 | (path) => 46 | `/${path 47 | .replace(...patterns.route) 48 | .replace(/\+|\([\w-]+\)\//g, '') 49 | .replace(/(\/)?index/g, '') 50 | .replace(/\./g, '/')}`, 51 | ) 52 | 53 | const types = 54 | `export type Path =\n | "${[...new Set(paths.filter(Boolean))].sort().join('"\n | "')}"`.replace(/"/g, '`') + 55 | '\n\n' + 56 | `export type Params = {\n ${params.sort().join('\n ')}\n}` + 57 | '\n\n' + 58 | `export type ModalPath = "${modals.sort().join('" | "') || 'never'}"`.replace(/"/g, modals.length ? '`' : '') 59 | 60 | const content = template.replace('// types', types) 61 | const count = paths.length + modals.length 62 | 63 | return { content, count } 64 | } 65 | 66 | const logger = createLogger('info', { prefix: '[generouted]' }) 67 | let latestContent = '' 68 | 69 | export const generate = async (options: Options) => { 70 | const start = Date.now() 71 | const { content, count } = await generateRouteTypes(options) 72 | logger.info(`scanned ${count} routes in ${Date.now() - start} ms`, { timestamp: true }) 73 | 74 | if (latestContent === content) return 75 | latestContent = content 76 | 77 | await fs.promises.writeFile(options.output, content) 78 | 79 | if (!options.format) return 80 | const prettier = path.resolve('./node_modules/.bin/prettier') 81 | if (fs.existsSync(prettier)) execSync(`${prettier} --write --cache ${options.output}`) 82 | } 83 | -------------------------------------------------------------------------------- /packages/react-router/src/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { Plugin } from 'vite' 3 | 4 | import { generate } from './generate' 5 | import { defaultOptions, Options } from './options' 6 | 7 | export default function Generouted(options?: Partial): Plugin { 8 | const resolvedOptions = { ...defaultOptions, ...options } 9 | 10 | return { 11 | name: 'generouted/react-router', 12 | enforce: 'pre', 13 | configureServer(server) { 14 | const listener = (file = '') => (file.includes(path.normalize('/src/pages/')) ? generate(resolvedOptions) : null) 15 | server.watcher.on('add', listener) 16 | server.watcher.on('change', listener) 17 | server.watcher.on('unlink', listener) 18 | }, 19 | buildStart(): Promise { 20 | return generate(resolvedOptions) 21 | }, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/react-router/src/plugin/options.ts: -------------------------------------------------------------------------------- 1 | export type Options = { 2 | source: { routes: string | string[]; modals: string | string[] } 3 | output: string 4 | format: boolean 5 | } 6 | 7 | export const defaultOptions: Options = { 8 | source: { routes: './src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', modals: './src/pages/**/[+]*.{jsx,tsx,mdx}' }, 9 | output: './src/router.ts', 10 | format: true, 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-router/src/plugin/template.ts: -------------------------------------------------------------------------------- 1 | export const template = `// Generouted, changes to this file will be overridden 2 | /* eslint-disable */ 3 | 4 | import { components, hooks, utils } from '@generouted/react-router/client' 5 | 6 | // types 7 | 8 | export const { Link, Navigate } = components() 9 | export const { useModals, useNavigate, useParams } = hooks() 10 | export const { redirect } = utils() 11 | ` 12 | -------------------------------------------------------------------------------- /packages/react-router/src/react.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | export { React } 3 | -------------------------------------------------------------------------------- /packages/react-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "esnext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "declaration": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "react-jsx", 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["./src"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/react-router/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig([ 4 | { 5 | entry: ['src/core.ts', 'src/index*', 'src/client'], 6 | format: ['esm'], 7 | dts: { 8 | entry: { 9 | core: './node_modules/generouted/dist/core.d.ts', 10 | index: './node_modules/generouted/dist/react-router.d.ts', 11 | 'index-lazy': './node_modules/generouted/dist/react-router-lazy.d.ts', 12 | 'client/index': 'src/client/index.ts', 13 | }, 14 | }, 15 | external: ['react', 'react-router'], 16 | noExternal: ['generouted'], 17 | inject: ['./src/react.js'], 18 | }, 19 | { 20 | entry: ['src/plugin'], 21 | outDir: 'dist/plugin', 22 | format: ['cjs', 'esm'], 23 | dts: { entry: 'src/plugin/index.ts' }, 24 | }, 25 | ]) 26 | -------------------------------------------------------------------------------- /packages/solid-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@generouted/solid-router", 3 | "version": "1.20.0", 4 | "description": "Generated file-based routes for Solid Router and Vite", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "repository": "https://github.com/oedotme/generouted", 8 | "bugs": "https://github.com/oedotme/generouted/issues", 9 | "homepage": "https://github.com/oedotme/generouted#readme", 10 | "keywords": [ 11 | "actions", 12 | "code-splitting", 13 | "data-loaders", 14 | "file-based-routing", 15 | "generate", 16 | "nested-layouts", 17 | "nextjs", 18 | "pages", 19 | "pre-loading ", 20 | "react", 21 | "react-location", 22 | "react-router", 23 | "react-router-dom", 24 | "remix", 25 | "router", 26 | "routes", 27 | "solid", 28 | "solid-router", 29 | "typescript", 30 | "vite" 31 | ], 32 | "type": "module", 33 | "exports": { 34 | ".": { 35 | "types": "./dist/index.d.ts", 36 | "import": "./dist/index.js" 37 | }, 38 | "./core": { 39 | "types": "./dist/core.d.ts", 40 | "import": "./dist/core.js" 41 | }, 42 | "./lazy": { 43 | "types": "./dist/index-lazy.d.ts", 44 | "import": "./dist/index-lazy.js" 45 | }, 46 | "./client": { 47 | "types": "./dist/client/index.d.ts", 48 | "import": "./dist/client/index.js" 49 | }, 50 | "./plugin": { 51 | "types": "./dist/plugin/index.d.ts", 52 | "require": "./dist/plugin/index.cjs", 53 | "import": "./dist/plugin/index.js" 54 | } 55 | }, 56 | "typesVersions": { 57 | "*": { 58 | "*": [ 59 | "./dist/index.d.ts" 60 | ], 61 | "core": [ 62 | "./dist/core.d.ts" 63 | ], 64 | "lazy": [ 65 | "./dist/index-lazy.d.ts" 66 | ], 67 | "client": [ 68 | "./dist/client/index.d.ts" 69 | ], 70 | "plugin": [ 71 | "./dist/plugin/index.d.ts" 72 | ] 73 | } 74 | }, 75 | "files": [ 76 | "dist" 77 | ], 78 | "scripts": { 79 | "dev": "tsup --watch", 80 | "build": "tsup --clean", 81 | "type-check": "tsc --noEmit" 82 | }, 83 | "dependencies": { 84 | "fast-glob": "^3.3.3", 85 | "generouted": "^1.20.0" 86 | }, 87 | "devDependencies": { 88 | "@generouted/core": "workspace:*", 89 | "@solidjs/router": "^0.15.3", 90 | "esbuild-plugin-solid": "^0.6.0", 91 | "solid-js": "^1.9.4", 92 | "tsup": "^8.3.6", 93 | "typescript": "^5.7.3", 94 | "vite": "^6.1.0" 95 | }, 96 | "peerDependencies": { 97 | "@solidjs/router": ">=0.14", 98 | "solid-js": ">=1", 99 | "vite": ">=5" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/solid-router/src/client/components.tsx: -------------------------------------------------------------------------------- 1 | import { A, Navigate } from '@solidjs/router' 2 | 3 | import { AnchorProps, NavigateProps } from './types' 4 | import { generatePath } from './utils' 5 | 6 | export const components = >() => { 7 | return { 8 | A:

(props: AnchorProps) => { 9 | return 10 | }, 11 | Navigate:

(props: NavigateProps) => { 12 | return 13 | }, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/solid-router/src/client/hooks.ts: -------------------------------------------------------------------------------- 1 | import { Accessor } from 'solid-js' 2 | import { NavigateOptions as NavOptions, useLocation, useMatch, useNavigate, useParams } from '@solidjs/router' 3 | import { MatchFilters } from '@solidjs/router/dist/types' 4 | 5 | import { generatePath } from './utils' 6 | import { NavigateOptions } from './types' 7 | 8 | export const hooks = , ModalPath extends string>() => { 9 | return { 10 | useParams:

(path: P) => useParams() as Params[P], 11 | useNavigate: () => { 12 | const navigate = useNavigate() 13 | return

(href: P, ...[options]: NavigateOptions) => { 14 | navigate(options?.params ? generatePath(href, options.params) : href, options) 15 | } 16 | }, 17 | useMatch:

(path: () => P, matchFilters?: MatchFilters

) => { 18 | return useMatch(path, matchFilters) as Accessor<{ path: P; params: Params[P] } | undefined> 19 | }, 20 | useModals: () => { 21 | const location = useLocation() 22 | const navigate = useNavigate() 23 | 24 | type Options

= Partial> & 25 | (P extends keyof Params ? { at?: P; params: Params[P] } : { at?: P; params?: never }) 26 | 27 | return { 28 | current: location.state?.modal || '', 29 | open:

(path: ModalPath, options?: Options

) => { 30 | const { at, state, ...opts } = options || {} 31 | const pathname = options?.params ? generatePath(at || '', options.params || {}) : at 32 | navigate(pathname || location.pathname, { ...opts, state: { ...location.state, ...state, modal: path } }) 33 | }, 34 | close:

(options?: Options

) => { 35 | const { at, state, ...opts } = options || {} 36 | const pathname = options?.params ? generatePath(at || '', options.params || {}) : at 37 | navigate(pathname || location.pathname, { ...opts, state: { ...location.state, ...state, modal: '' } }) 38 | }, 39 | } 40 | }, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/solid-router/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components' 2 | export * from './hooks' 3 | export * from './types' 4 | export * from './utils' 5 | -------------------------------------------------------------------------------- /packages/solid-router/src/client/types.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProps as AProps, NavigateOptions as NavOptions, NavigateProps as NavProps } from '@solidjs/router' 2 | 3 | type ComponentProps> = Path extends keyof Params 4 | ? { href: Path; params: Params[Path] } 5 | : { href: Path; params?: never } 6 | 7 | export type AnchorProps> = Omit & 8 | ComponentProps 9 | 10 | export type NavigateProps> = Omit & 11 | ComponentProps 12 | 13 | export type NavigateOptions> = Path extends keyof Params 14 | ? [Partial & { params: Params[Path] }] 15 | : [Partial & { params?: never }] | [] 16 | -------------------------------------------------------------------------------- /packages/solid-router/src/client/utils.ts: -------------------------------------------------------------------------------- 1 | export const generatePath = (path: string, params: Record) => { 2 | return path.replace(/\/:(\w+)(\??)/g, (_, segment) => (params[segment] ? `/${params[segment]}` : '')) 3 | } 4 | -------------------------------------------------------------------------------- /packages/solid-router/src/core.ts: -------------------------------------------------------------------------------- 1 | export * from 'generouted/core' 2 | -------------------------------------------------------------------------------- /packages/solid-router/src/index-lazy.ts: -------------------------------------------------------------------------------- 1 | export * from 'generouted/solid-router-lazy' 2 | -------------------------------------------------------------------------------- /packages/solid-router/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from 'generouted/solid-router' 2 | -------------------------------------------------------------------------------- /packages/solid-router/src/plugin/generate.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import fs from 'fs' 3 | import path from 'path' 4 | 5 | import { createLogger } from 'vite' 6 | import { patterns } from '@generouted/core' 7 | import fg from 'fast-glob' 8 | 9 | import { Options } from './options' 10 | import { template } from './template' 11 | 12 | const generateRouteTypes = async (options: Options) => { 13 | const files = await fg(options.source.routes || './src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', { onlyFiles: true }) 14 | const modal = await fg(options.source.modals || './src/pages/**/[+]*.{jsx,tsx,mdx}', { onlyFiles: true }) 15 | 16 | const filtered = files.filter((key) => !key.includes('/_') && !key.includes('/404')) 17 | const params: string[] = [] 18 | 19 | const paths = filtered.map((key) => { 20 | const path = key 21 | .replace(...patterns.route) 22 | .replace(...patterns.splat) 23 | .replace(...patterns.param) 24 | .replace(/\([\w-]+\)\/|\/?_layout/g, '') 25 | .replace(/\/?index|\./g, '/') 26 | .replace(/(\w)\/$/g, '$1') 27 | .split('/') 28 | .map((segment) => segment.replace(...patterns.optional)) 29 | .join('/') 30 | 31 | if (path) { 32 | const param = path.split('/').filter((segment) => segment.startsWith(':')) 33 | 34 | if (param.length || path.includes('*')) { 35 | const dynamic = param.length ? param.map((p) => p.replace(/:(.+)(\?)?/, '$1$2:') + ' string') : [] 36 | const splat = path.includes('*') ? ["'*': string"] : [] 37 | params.push(`'/${path}': { ${[...dynamic, ...splat].join('; ')} }`) 38 | } 39 | 40 | return path.length > 1 ? `/${path}` : path 41 | } 42 | }) 43 | 44 | const modals = modal.map( 45 | (path) => 46 | `/${path 47 | .replace(...patterns.route) 48 | .replace(/\+|\([\w-]+\)\//g, '') 49 | .replace(/(\/)?index/g, '') 50 | .replace(/\./g, '/')}`, 51 | ) 52 | 53 | const types = 54 | `export type Path =\n | "${[...new Set(paths.filter(Boolean))].sort().join('"\n | "')}"`.replace(/"/g, '`') + 55 | '\n\n' + 56 | `export type Params = {\n ${params.sort().join('\n ')}\n}` + 57 | '\n\n' + 58 | `export type ModalPath = "${modals.sort().join('" | "') || 'never'}"`.replace(/"/g, modals.length ? '`' : '') 59 | 60 | const content = template.replace('// types', types) 61 | const count = paths.length + modals.length 62 | 63 | return { content, count } 64 | } 65 | 66 | const logger = createLogger('info', { prefix: '[generouted]' }) 67 | let latestContent = '' 68 | 69 | export const generate = async (options: Options) => { 70 | const start = Date.now() 71 | const { content, count } = await generateRouteTypes(options) 72 | logger.info(`scanned ${count} routes in ${Date.now() - start} ms`, { timestamp: true }) 73 | 74 | if (latestContent === content) return 75 | latestContent = content 76 | 77 | await fs.promises.writeFile(options.output, content) 78 | 79 | if (!options.format) return 80 | const prettier = path.resolve('./node_modules/.bin/prettier') 81 | if (fs.existsSync(prettier)) execSync(`${prettier} --write --cache ${options.output}`) 82 | } 83 | -------------------------------------------------------------------------------- /packages/solid-router/src/plugin/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { Plugin } from 'vite' 3 | 4 | import { generate } from './generate' 5 | import { defaultOptions, Options } from './options' 6 | 7 | export default function Generouted(options?: Partial): Plugin { 8 | const resolvedOptions = { ...defaultOptions, ...options } 9 | 10 | return { 11 | name: 'generouted/solid-router', 12 | enforce: 'pre', 13 | configureServer(server) { 14 | const listener = (file = '') => (file.includes(path.normalize('/src/pages/')) ? generate(resolvedOptions) : null) 15 | server.watcher.on('add', listener) 16 | server.watcher.on('change', listener) 17 | server.watcher.on('unlink', listener) 18 | }, 19 | buildStart(): Promise { 20 | return generate(resolvedOptions) 21 | }, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/solid-router/src/plugin/options.ts: -------------------------------------------------------------------------------- 1 | export type Options = { 2 | source: { routes: string | string[]; modals: string | string[] } 3 | output: string 4 | format: boolean 5 | } 6 | 7 | export const defaultOptions: Options = { 8 | source: { routes: './src/pages/**/[\\w[-]*.{jsx,tsx,mdx}', modals: './src/pages/**/[+]*.{jsx,tsx,mdx}' }, 9 | output: './src/router.ts', 10 | format: true, 11 | } 12 | -------------------------------------------------------------------------------- /packages/solid-router/src/plugin/template.ts: -------------------------------------------------------------------------------- 1 | export const template = `// Generouted, changes to this file will be overridden 2 | /* eslint-disable */ 3 | 4 | import { components, hooks } from '@generouted/solid-router/client' 5 | 6 | // types 7 | 8 | export const { A, Navigate } = components() 9 | export const { useMatch, useModals, useNavigate, useParams } = hooks() 10 | ` 11 | -------------------------------------------------------------------------------- /packages/solid-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "esnext", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "declaration": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "jsxImportSource": "solid-js", 19 | "types": ["vite/client"] 20 | }, 21 | "include": ["./src"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/solid-router/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | import { solidPlugin as solid } from 'esbuild-plugin-solid' 3 | 4 | export default defineConfig([ 5 | { 6 | entry: ['src/core.ts', 'src/index*', 'src/client'], 7 | format: ['esm'], 8 | dts: { 9 | entry: { 10 | core: './node_modules/generouted/dist/core.d.ts', 11 | index: './node_modules/generouted/dist/solid-router.d.ts', 12 | 'index-lazy': './node_modules/generouted/dist/solid-router-lazy.d.ts', 13 | 'client/index': 'src/client/index.ts', 14 | }, 15 | }, 16 | external: ['solid-js', 'solid-js/web', '@solidjs/router'], 17 | noExternal: ['generouted'], 18 | esbuildPlugins: [solid()], 19 | }, 20 | { 21 | entry: ['src/plugin'], 22 | outDir: 'dist/plugin', 23 | format: ['cjs', 'esm'], 24 | dts: { entry: 'src/plugin/index.ts' }, 25 | }, 26 | ]) 27 | -------------------------------------------------------------------------------- /packages/tanstack-react-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@generouted/tanstack-react-router", 3 | "version": "1.20.0", 4 | "description": "Generated file-based routes for TanStack React Router and Vite", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "repository": "https://github.com/oedotme/generouted", 8 | "bugs": "https://github.com/oedotme/generouted/issues", 9 | "homepage": "https://github.com/oedotme/generouted#readme", 10 | "keywords": [ 11 | "actions", 12 | "code-splitting", 13 | "data-loaders", 14 | "file-based-routing", 15 | "generate", 16 | "nested-layouts", 17 | "nextjs", 18 | "pages", 19 | "pre-loading ", 20 | "react", 21 | "react-location", 22 | "react-router", 23 | "react-router-dom", 24 | "remix", 25 | "router", 26 | "routes", 27 | "solid", 28 | "solid-router", 29 | "typescript", 30 | "vite" 31 | ], 32 | "type": "module", 33 | "exports": { 34 | ".": { 35 | "types": "./dist/index.d.ts", 36 | "require": "./dist/index.cjs", 37 | "import": "./dist/index.js" 38 | } 39 | }, 40 | "typesVersions": { 41 | "*": { 42 | "*": [ 43 | "./dist/index.d.ts" 44 | ] 45 | } 46 | }, 47 | "files": [ 48 | "dist" 49 | ], 50 | "scripts": { 51 | "dev": "tsup src/index.ts --dts --format cjs,esm --watch", 52 | "build": "tsup src/index.ts --dts --format cjs,esm --minify --clean", 53 | "type-check": "tsc --noEmit" 54 | }, 55 | "dependencies": { 56 | "fast-glob": "^3.3.3" 57 | }, 58 | "devDependencies": { 59 | "@generouted/core": "workspace:*", 60 | "tsup": "^8.3.6", 61 | "typescript": "^5.7.3", 62 | "vite": "^6.1.0" 63 | }, 64 | "peerDependencies": { 65 | "vite": ">=5" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/tanstack-react-router/readme.md: -------------------------------------------------------------------------------- 1 | # Generouted + TanStack React Router 2 | 3 | ## How 4 | 5 | This integration is based on a Vite plugin to generate routes config for TanStack React Router with `generouted` conventions. The output is saved at `src/routes.gen.tsx` and gets updated by the add/change/delete at `src/pages/*`. 6 | 7 | ## Getting started 8 | 9 | In case you don't have a Vite project with React and TypeScript, check [Vite documentation to start a new project](https://vitejs.dev/guide/#scaffolding-your-first-vite-project). 10 | 11 | ### Installation 12 | 13 | ```shell 14 | pnpm add @generouted/tanstack-react-router @tanstack/router@beta 15 | ``` 16 | 17 | Optional additional packages for actions and/or loaders: 18 | 19 | ```shell 20 | pnpm add @tanstack/react-actions@beta @tanstack/react-loaders@beta 21 | ``` 22 | 23 | Optionally install `prettier` as a dev dependency so `generouted` formats the generated `src/routes.gen.tsx` file automatically: 24 | 25 | ```shell 26 | pnpm add --save-dev prettier 27 | ``` 28 | 29 | ### Setup 30 | 31 | ```ts 32 | // vite.config.ts 33 | 34 | import { defineConfig } from 'vite' 35 | import react from '@vitejs/plugin-react' 36 | import generouted from '@generouted/tanstack-react-router' 37 | 38 | export default defineConfig({ plugins: [react(), generouted()] }) 39 | ``` 40 | 41 | ### Usage 42 | 43 | ```tsx 44 | // src/main.tsx 45 | 46 | import { createRoot } from 'react-dom/client' 47 | import { Routes } from './routes.gen' 48 | 49 | createRoot(document.getElementById('root')!).render() 50 | ``` 51 | 52 | ### Adding pages 53 | 54 | Add the home page by creating a new file `src/pages/index.tsx` **→** `/`, then export a default component: 55 | 56 | ```tsx 57 | // src/pages/index.tsx 58 | 59 | export default function Home() { 60 | return

Home

61 | } 62 | ``` 63 | 64 | ### Optional root layout at `pages/_app.tsx` 65 | 66 | ```tsx 67 | // src/pages/_app.tsx 68 | 69 | import { Outlet } from '@tanstack/router' 70 | 71 | export default function App() { 72 | return ( 73 |
74 |
75 | 76 |
77 | 78 |
79 | 80 |
81 |
82 | ) 83 | } 84 | ``` 85 | 86 | ## Examples 87 | 88 | ### TanStack React Router 89 | 90 | - [Basic](../../examples/tanstack-react-router/) 91 | 92 | ## License 93 | 94 | MIT 95 | -------------------------------------------------------------------------------- /packages/tanstack-react-router/src/format.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | import { existsSync } from 'fs' 3 | import path from 'path' 4 | 5 | const prettier = path.resolve('./node_modules/.bin/prettier') 6 | 7 | export const format = (file: string) => { 8 | if (!existsSync(prettier) || !existsSync(file)) return 9 | execSync(`${prettier} --write --cache ${file}`) 10 | } 11 | -------------------------------------------------------------------------------- /packages/tanstack-react-router/src/generate.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs' 2 | import fg from 'fast-glob' 3 | 4 | import { patterns as corePatterns, getRoutes } from '@generouted/core' 5 | 6 | import { format } from './format' 7 | import { Options } from './options' 8 | import { template } from './template' 9 | 10 | const patterns = Object.assign(corePatterns, { 11 | param: [/\[([^\]]+)\]/g, '$$$1'], 12 | optional: [/^-(\$?[\w-]+)/, '$1?'], 13 | }) as Record 14 | 15 | const generateRoutes = async () => { 16 | const source = ['./src/pages/**/[\\w[-]*.{jsx,tsx}'] 17 | const files = await fg(source, { onlyFiles: true }) 18 | 19 | const imports: string[] = [] 20 | const modules: string[] = [] 21 | 22 | const { routes, preserved, exports, count } = getRoutes( 23 | files, 24 | (key, exports, _id = '') => { 25 | const { loader, action, pending, catch_ } = exports 26 | const file = key.replace(...patterns.route) 27 | const module = `import('./pages/${file}')` 28 | const path = file 29 | .replace(...patterns.splat) 30 | .replace(...patterns.param) 31 | .replace(/\(|\)|\/?_layout/g, '') 32 | .replace(/\/?index|\./g, '/') 33 | .replace(/(\w)\/$/g, '$1') 34 | .split('/') 35 | .map((segment) => segment.replace(...patterns.optional)) 36 | .join('/') 37 | 38 | const ignore = '\n// @ts-ignore\n' 39 | return { 40 | _path: path.length > 1 ? `/${path}` : path, 41 | _module: module, 42 | _loader: loader ? `${ignore} loader: (...args) => ${module}.then((m) => m.Loader(...args))` : '', 43 | _action: action ? '' : '', 44 | _component: 'component: m.default', 45 | _pendingComponent: pending ? 'pendingComponent: m.Pending' : '', 46 | _errorComponent: catch_ ? 'errorComponent: m.Catch' : '', 47 | } 48 | }, 49 | patterns, 50 | ) 51 | 52 | if (preserved._app && exports['_app'].default) { 53 | imports.push(`import App from './pages/_app'`) 54 | modules.push(`const root = createRootRoute({ component: App || Outlet })`) 55 | } else { 56 | modules.push(`const root = createRootRoute({ component: Outlet })`) 57 | } 58 | 59 | if (preserved._404 && exports['404'].default) { 60 | imports.push(`import NoMatch from './pages/404'`) 61 | modules.push(`const _404 = createRoute({ getParentRoute: () => root, path: '*', component: NoMatch || Fragment })`) 62 | } else { 63 | modules.push(`const _404 = createRoute({ getParentRoute: () => root, path: '*', component: Fragment })`) 64 | } 65 | 66 | const config = JSON.stringify(routes, function (key, value) { 67 | if (key === 'id') { 68 | const { id, pid, path, ...props } = this 69 | const meta = props as (typeof routes)[number] 70 | 71 | const components = [meta._component, meta._pendingComponent, meta._errorComponent].filter(Boolean) 72 | const options = [path ? `path: '${path}'` : `id: '${id}'`, meta._loader, meta._action].filter(Boolean) 73 | const module = `const ${id} = createRoute({ getParentRoute: () => ${pid}, ${options.join(', ')} })` 74 | 75 | modules.push( 76 | meta._module 77 | ? `${module}.lazy(() =>\n ${meta._module}.then((m) => createLazyRoute('${meta._path}')({ ${components.join(', ')} }))\n)` 78 | : module, 79 | ) 80 | } 81 | 82 | if (['pid', 'path'].includes(key) || key.startsWith('_')) return undefined 83 | return value 84 | }) 85 | .replace(/"id":"([\w-]+)"/g, '$1') 86 | .replace(/^\[|\]$|{|}/g, '') 87 | .replace(/\[/g, '([') 88 | .replace(/\]/g, '])') 89 | .replace(/,"children":/g, '.addChildren') 90 | .replace(/\),/g, '),\n ') 91 | 92 | const content = template 93 | .replace('// imports', imports.join('\n')) 94 | .replace('// modules', modules.join('\n')) 95 | .replace('// config', config) 96 | 97 | return { content, count } 98 | } 99 | 100 | let latestContent = '' 101 | 102 | export const generate = async (options: Options) => { 103 | const start = Date.now() 104 | const { content, count } = await generateRoutes() 105 | console.log(`${new Date().toLocaleTimeString()} [generouted] ${count} routes in ${Date.now() - start} ms`) 106 | 107 | if (latestContent === content) return 108 | latestContent = content 109 | 110 | writeFileSync(`./src/${options.output}`, content) 111 | if (options.format) format(`./src/${options.output}`) 112 | } 113 | -------------------------------------------------------------------------------- /packages/tanstack-react-router/src/index.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { Plugin } from 'vite' 3 | 4 | import { generate } from './generate' 5 | import { defaultOptions, Options } from './options' 6 | 7 | export default function Generouted(options?: Partial): Plugin { 8 | const resolvedOptions = { ...defaultOptions, ...options } 9 | 10 | return { 11 | name: 'generouted/tanstack-react-router', 12 | enforce: 'pre', 13 | configureServer(server) { 14 | const listener = (file = '') => (file.includes(path.normalize('/src/pages/')) ? generate(resolvedOptions) : null) 15 | server.watcher.on('add', listener) 16 | server.watcher.on('change', listener) 17 | server.watcher.on('unlink', listener) 18 | }, 19 | buildStart(): Promise { 20 | return generate(resolvedOptions) 21 | }, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/tanstack-react-router/src/options.ts: -------------------------------------------------------------------------------- 1 | export const defaultOptions = { 2 | output: 'routes.gen.tsx', 3 | format: true, 4 | } 5 | 6 | export type Options = typeof defaultOptions 7 | -------------------------------------------------------------------------------- /packages/tanstack-react-router/src/template.ts: -------------------------------------------------------------------------------- 1 | export const template = `// Generouted, changes to this file will be overridden 2 | import { Fragment } from 'react' 3 | import { Outlet, RouterProvider, createLazyRoute, createRootRoute, createRoute, createRouter } from '@tanstack/react-router' 4 | 5 | // imports 6 | 7 | // modules 8 | 9 | const config = root.addChildren([ 10 | // config, 11 | _404, 12 | ]) 13 | 14 | const router = createRouter({ routeTree: config }) 15 | export const routes = config 16 | export const Routes = () => 17 | 18 | declare module '@tanstack/react-router' { 19 | interface Register { 20 | router: typeof router 21 | } 22 | } 23 | ` 24 | -------------------------------------------------------------------------------- /packages/tanstack-react-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es2017", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "declaration": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve" 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - examples/* 3 | - examples/react-location/* 4 | - explorer 5 | - packages/* 6 | - shared/* 7 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ./packages/generouted/readme.md -------------------------------------------------------------------------------- /shared/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@generouted/core", 3 | "private": true, 4 | "description": "Generated file-based routes for TanStack React Router and Vite", 5 | "author": "Omar Elhawary (https://omarelhawary.me)", 6 | "license": "MIT", 7 | "repository": "https://github.com/oedotme/generouted", 8 | "bugs": "https://github.com/oedotme/generouted/issues", 9 | "homepage": "https://github.com/oedotme/generouted#readme", 10 | "keywords": [], 11 | "type": "module", 12 | "types": "./src/index.ts", 13 | "exports": "./src/index.ts", 14 | "files": [ 15 | "src" 16 | ], 17 | "devDependencies": { 18 | "@types/node": "^22.13.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /shared/core/src/index.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs' 2 | 3 | export const patterns = { 4 | route: [/^.*\/src\/pages\/|^\/pages\/|\.(jsx|tsx|mdx)$/g, ''], 5 | splat: [/\[\.{3}\w+\]/g, '*'], 6 | param: [/\[([^\]]+)\]/g, ':$1'], 7 | slash: [/^index$|\./g, '/'], 8 | optional: [/^-(:?[\w-]+|\*)/, '$1?'], 9 | } as Record 10 | 11 | const getRouteId = (path: string) => path.replace(...patterns.route).replace(/\W/g, '') 12 | 13 | const getRouteExports = (content: string) => ({ 14 | default: /^export\s+default\s/gm.test(content), 15 | loader: /^export\s+(const|function|async function|let)\s+Loader\W/gm.test(content), 16 | action: /^export\s+(const|function|async function|let)\s+Action\W/gm.test(content), 17 | pending: /^export\s+(const|function|async function|let)\s+Pending\W/gm.test(content), 18 | catch_: /^export\s+(const|function|let)\s+Catch\W/gm.test(content), 19 | }) 20 | 21 | type BaseRoute = { id?: string; path?: string; children?: BaseRoute[] } & Record 22 | type Exports = Record> 23 | type Patterns = typeof patterns 24 | 25 | export const getRoutes = ( 26 | files: string[], 27 | buildRoute: (key: string, exports: Exports[string], id?: string) => T, 28 | patterns: Patterns, 29 | ) => { 30 | const filteredRoutes = files 31 | .filter((key) => !key.includes('/_') || /(_app|_layout)\.(jsx|tsx)$/.test(key)) 32 | .sort((a, b) => a.localeCompare(b)) 33 | 34 | const ids = filteredRoutes.map((route) => getRouteId(route)) 35 | const exports: Record> = {} 36 | 37 | const routes = filteredRoutes.reduce((routes, key) => { 38 | const content = readFileSync(key, { encoding: 'utf-8' }) 39 | const id = getRouteId(key) 40 | 41 | exports[id] = getRouteExports(content) 42 | 43 | if (!exports[id].default) return routes 44 | if (['_app', '404'].includes(id) || ids.includes(id + '_layout')) return routes 45 | 46 | const route = buildRoute(key, exports[id], id) 47 | const segments = key 48 | .replace(...patterns.route) 49 | .replace(...patterns.splat) 50 | .replace(...patterns.param) 51 | .split('/') 52 | .filter(Boolean) 53 | 54 | segments.reduce((parent, segment, index) => { 55 | const path = segment.replace(...patterns.slash).replace(...patterns.optional) 56 | const root = index === 0 57 | const leaf = index === segments.length - 1 && segments.length > 1 58 | const node = !root && !leaf 59 | const layout = segment === '_layout' 60 | const group = /\([\w-]+\)/.test(path) 61 | const insert = /^\w|\//.test(path) ? 'unshift' : 'push' 62 | 63 | if (root) { 64 | const last = segments.length === 1 65 | if (last) { 66 | routes.push({ id, pid: 'root', path, ...route }) 67 | return parent 68 | } 69 | } 70 | 71 | if (root || node) { 72 | const current = root ? routes : parent.children 73 | const _id = getRouteId(segments.slice(0, index + 1).join('')) 74 | const found = current?.find((route) => route.path === path || route.id === _id) 75 | const pid = parent?.id || 'root' 76 | const props = group ? {} : { path } 77 | 78 | if (found) found.children ??= [] 79 | else current?.[insert]({ ...props, id: _id, pid, children: [] }) 80 | return found || (current?.[insert === 'unshift' ? 0 : current.length - 1] as BaseRoute) 81 | } 82 | 83 | if (layout) { 84 | const pid = segments.slice(0, index - 1).join('') || 'root' 85 | return Object.assign(parent, { id: parent.id || parent.path, pid, ...route }) 86 | } 87 | 88 | if (leaf) { 89 | parent?.children?.[insert]({ id, pid: parent?.id || 'root', path, ...route }) 90 | } 91 | 92 | return parent 93 | }, {} as BaseRoute) 94 | 95 | return routes 96 | }, []) 97 | 98 | const preserved = { _app: ids.includes('_app'), _404: ids.includes('404') } 99 | const count = ids.length - Object.values(preserved).filter(Boolean).length 100 | 101 | return { routes, preserved, exports, count } 102 | } 103 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "ui": "stream", 3 | "tasks": { 4 | "dev": { 5 | "cache": false 6 | }, 7 | "build": { 8 | "dependsOn": ["^build", "^test", "^type-check"], 9 | "outputs": ["dist/**"] 10 | }, 11 | "test": {}, 12 | "type-check": {} 13 | } 14 | } 15 | --------------------------------------------------------------------------------