├── .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 |
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 |
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 |
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(Current pathname: {location.pathname}
19 | 20 | 21 | 22 |Current pathname: {location.pathname}
19 | 20 | 21 | 22 |Current pathname: {location.pathname}
19 | 20 | 21 | 22 |Current pathname: {location.pathname}
19 | 20 | 21 | 22 |Current pathname: {location.pathname}
19 | 20 | 21 | 22 |Current pathname: {location.pathname}
25 | 26 | 27 | 28 |{props.error.toString()}5 |
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 |>({ 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 | (path: P) => useParams | 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 = 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 (props: AnchorProps ) => {
9 | return
10 | },
11 | Navigate: (props: NavigateProps ) => {
12 | return (path: P) => useParams (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 = Partial (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 ComponentPropsHome
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 |