feat(packages/tuono-(router|fs-router-vite-plugin)): replace __route with __layout (#283)

This commit is contained in:
Marco Pasqualetti
2025-01-03 17:08:18 +01:00
committed by GitHub
parent cf56b3a53e
commit 0ef1252e1f
24 changed files with 82 additions and 78 deletions
@@ -54,7 +54,7 @@ then it will be accessible at `posts/1`, `posts/2`, etc.
## The Root route (Layout components)
Tuono allows you to have a layout component to wrap all the routes included within the same folder.
To define such component you will have to create a `__root.tsx` file (is allowed only a single `__root` file per folder).
To define such component you will have to create a `__layout.tsx` file (is allowed only a single `__layout` file per folder).
This file won't generate any route.
@@ -63,7 +63,7 @@ This file won't generate any route.
A file created in this location will wrap all project route.
```tsx
// src/routes/__root.tsx
// src/routes/__layout.tsx
export default function RootLayout({ children }) {
return <main>{children}</main>
}
@@ -74,8 +74,8 @@ export default function RootLayout({ children }) {
A file created in this location will wrap only the routes defined within the `src/routes/posts` folder.
```tsx
// src/routes/posts/__root.tsx
export default function PostRootLayout({ children }) {
// src/routes/posts/__layout.tsx
export default function PostLayout({ children }) {
return <article>{children}</article>
}
```
@@ -86,9 +86,9 @@ Referring to the two examples above consider that:
```tsx
<RootLayout>
<PostRootLayout>
<PostLayout>
<ExamplePost />
</PostRootLayout>
</PostLayout>
</RootLayout>
```
+2 -2
View File
@@ -3,7 +3,7 @@ use time::OffsetDateTime;
use tuono_lib::axum::http::{header, HeaderMap, StatusCode};
use tuono_lib::{Request, Response};
const FILE_TO_EXCLUDE: [&str; 2] = ["sitemap.xml", "__root"];
const FILES_TO_EXCLUDE: [&str; 2] = ["sitemap.xml", "__layout"];
const SITEMAP: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@@ -20,7 +20,7 @@ fn load_routes() -> Vec<String> {
if !entry.is_dir() {
let path = clean_path(format!("/{}", entry.to_string_lossy()));
if !FILE_TO_EXCLUDE
if !FILES_TO_EXCLUDE
.iter()
.any(|exclude| path.ends_with(exclude))
{
+3 -3
View File
@@ -14,7 +14,7 @@ use std::process::Stdio;
use crate::route::Route;
const IGNORE_EXTENSIONS: [&str; 3] = ["css", "scss", "sass"];
const IGNORE_FILES: [&str; 1] = ["__root"];
const IGNORE_FILES: [&str; 1] = ["__layout"];
#[cfg(target_os = "windows")]
const ROUTES_FOLDER_PATH: &str = "\\src\\routes";
@@ -333,8 +333,8 @@ mod tests {
app.base_path = "/home/user/Documents/tuono".into();
let routes = [
"/home/user/Documents/tuono/src/routes/__root.tsx",
"/home/user/Documents/tuono/src/routes/posts/__root.tsx",
"/home/user/Documents/tuono/src/routes/__layout.tsx",
"/home/user/Documents/tuono/src/routes/posts/__layout.tsx",
];
routes.into_iter().for_each(|route| {
@@ -1,10 +1,10 @@
import type { ReactNode, JSX } from 'react'
interface RootRouteProps {
interface RootLayoutProps {
children: ReactNode
}
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
export default function RootLayout({ children }: RootLayoutProps): JSX.Element {
return (
<html>
<body>
@@ -1,10 +1,10 @@
import type { ReactNode, JSX } from 'react'
interface RootRouteProps {
interface RootLayoutProps {
children: ReactNode
}
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
export default function RootLayout({ children }: RootLayoutProps): JSX.Element {
return (
<html>
<body>
@@ -1,11 +1,11 @@
import type { ReactNode, JSX } from 'react'
import { MDXProvider } from '@mdx-js/react'
interface RootRouteProps {
interface RootLayoutProps {
children: ReactNode
}
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
export default function RootLayout({ children }: RootLayoutProps): JSX.Element {
return (
<html>
<body>
@@ -10,12 +10,12 @@ const routes = [
routePath: '/posts/my-post',
variableName: 'PostsMyPost',
parent: {
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
routePath: '/posts/__root',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__layout.tsx',
routePath: '/posts/__layout',
variableName: 'Postsroot',
path: '/posts/__root',
path: '/posts/__layout',
cleanedPath: '/posts',
children: undefined,
},
@@ -29,12 +29,12 @@ const routes = [
routePath: '/posts/',
variableName: 'PostsIndex',
parent: {
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/home/valerio/Documents/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
routePath: '/posts/__root',
'/home/valerio/Documents/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__layout.tsx',
routePath: '/posts/__layout',
variableName: 'Postsroot',
path: '/posts/__root',
path: '/posts/__layout',
cleanedPath: '/posts',
children: undefined,
},
@@ -48,12 +48,12 @@ const routes = [
routePath: '/posts/',
variableName: 'PostspostIndex',
parent: {
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
routePath: '/posts/__root',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__layout.tsx',
routePath: '/posts/__layout',
variableName: 'Postsroot',
path: '/posts/__root',
path: '/posts/__layout',
cleanedPath: '/posts',
children: undefined,
},
@@ -1,3 +1,3 @@
export const ROUTES_FOLDER = './src/routes/'
export const ROOT_PATH_ID = '__root'
export const LAYOUT_PATH_ID = '__layout'
export const GENERATED_ROUTE_TREE = './.tuono/routeTree.gen.ts'
@@ -18,7 +18,11 @@ import {
} from './utils'
import type { Config, RouteNode } from './types'
import { ROUTES_FOLDER, ROOT_PATH_ID, GENERATED_ROUTE_TREE } from './constants'
import {
ROUTES_FOLDER,
LAYOUT_PATH_ID,
GENERATED_ROUTE_TREE,
} from './constants'
import { sortRouteNodes } from './sort-route-nodes'
import isDefaultExported from './utils/is-default-exported'
@@ -154,7 +158,7 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
const routeConfigChildrenText = buildRouteConfig(routeNodes)
const sortedRouteNodes = multiSortBy(routeNodes, [
(d): number => (d.routePath.includes(`/${ROOT_PATH_ID}`) ? -1 : 1),
(d): number => (d.routePath.includes(`/${LAYOUT_PATH_ID}`) ? -1 : 1),
(d): number => d.routePath.split('/').length,
(d): number => (d.routePath.endsWith("index'") ? -1 : 1),
(d): RouteNode => d,
@@ -179,7 +183,7 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
const createRoutes = [
...sortedRouteNodes.map((node) => {
const isRoot = node.routePath.endsWith(ROOT_PATH_ID)
const isRoot = node.routePath.endsWith(LAYOUT_PATH_ID)
const rootDeclaration = isRoot ? ', isRoot: true' : ''
const variableName = node.variableName as string
@@ -195,7 +199,7 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
return [
`const ${variableName}Route = ${variableName}.update({
${[
!node.path?.endsWith(ROOT_PATH_ID) && `path: '${cleanedPath}'`,
!node.path?.endsWith(LAYOUT_PATH_ID) && `path: '${cleanedPath}'`,
`getParentRoute: () => ${node.parent?.variableName ?? 'root'}Route`,
rustHandlersNodes.includes(node.path || '')
? 'hasHandler: true'
@@ -213,15 +217,15 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
'// This file is auto-generated by Tuono',
"import { createRoute, dynamic } from 'tuono'",
[
`import RootImport from './${replaceBackslash(
`import RootLayoutImport from './${replaceBackslash(
path.relative(
path.dirname(config.generatedRouteTree),
path.resolve(config.folderName, ROOT_PATH_ID),
path.resolve(config.folderName, LAYOUT_PATH_ID),
),
)}'`,
].join('\n'),
imports,
'const rootRoute = createRoute({ isRoot: true, component: RootImport });',
'const rootRoute = createRoute({ isRoot: true, component: RootLayoutImport });',
createRoutes,
'// Create/Update Routes',
createRouteUpdates,
@@ -13,12 +13,12 @@ const routes = [
cleanedPath: '/posts/[post]',
},
{
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/__root.tsx',
routePath: '/posts/__root',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/__layout.tsx',
routePath: '/posts/__layout',
variableName: 'Postsroot',
path: '/posts/__root',
path: '/posts/__layout',
cleanedPath: '/posts',
},
{
@@ -42,12 +42,12 @@ const routes = [
]
const parent = {
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/__root.tsx',
routePath: '/posts/__root',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/__layout.tsx',
routePath: '/posts/__layout',
variableName: 'Postsroot',
path: '/posts/__root',
path: '/posts/__layout',
cleanedPath: '/posts',
}
@@ -1,4 +1,4 @@
import { ROOT_PATH_ID } from './constants'
import { LAYOUT_PATH_ID } from './constants'
import { multiSortBy } from './utils'
import type { RouteNode } from './types'
@@ -19,15 +19,15 @@ export function hasParentRoute(
(d): number => d.routePath.length * -1,
(d): string | undefined => d.variableName,
])
// Exclude base __root file
.filter((d) => d.routePath !== `/${ROOT_PATH_ID}`)
// Exclude base __layout file
.filter((d) => d.routePath !== `/${LAYOUT_PATH_ID}`)
for (const route of sortedNodes) {
if (route.routePath === '/') continue
if (
route.routePath.startsWith(parentRoutePath) &&
route.routePath.endsWith(ROOT_PATH_ID)
route.routePath.endsWith(LAYOUT_PATH_ID)
) {
return route
}
@@ -22,10 +22,10 @@ const routes = [
cleanedPath: '/about',
},
{
filePath: '__root.tsx',
filePath: '__layout.tsx',
fullPath:
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/__root.tsx',
routePath: '/__root',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/__layout.tsx',
routePath: '/__layout',
variableName: 'root',
},
{
@@ -50,10 +50,10 @@ const routes = [
variableName: 'PostsIndex',
},
{
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
routePath: '/posts/__root',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__layout.tsx',
routePath: '/posts/__layout',
variableName: 'Postsroot',
},
]
@@ -78,10 +78,10 @@ const expectedSorting = [
cleanedPath: '/about',
},
{
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
routePath: '/posts/__root',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__layout.tsx',
routePath: '/posts/__layout',
variableName: 'Postsroot',
},
{
@@ -1,6 +1,6 @@
import type { RouteNode } from './types'
import { multiSortBy } from './utils'
import { ROOT_PATH_ID } from './constants'
import { LAYOUT_PATH_ID } from './constants'
// Routes need to be sorted in order to iterate over the handleNode fn
// with first the items that might be parent routes
@@ -14,4 +14,4 @@ export const sortRouteNodes = (routes: Array<RouteNode>): Array<RouteNode> =>
(d): number => (d.filePath.match(/[./]route[.]/) ? -1 : 1),
(d): number => (d.routePath.endsWith('/') ? -1 : 1),
(d): string => d.routePath,
]).filter((d) => ![`/${ROOT_PATH_ID}`].includes(d.routePath || ''))
]).filter((d) => ![`/${LAYOUT_PATH_ID}`].includes(d.routePath || ''))
@@ -2,14 +2,14 @@
import { createRoute, dynamic } from 'tuono'
import RootImport from './routes/__root'
import RootLayoutImport from './routes/__layout'
const IndexImport = dynamic(() => import('./routes/index'))
const PostscatchallImport = dynamic(
() => import('./routes/posts/[...catch_all]'),
)
const rootRoute = createRoute({ isRoot: true, component: RootImport })
const rootRoute = createRoute({ isRoot: true, component: RootLayoutImport })
const Index = createRoute({ component: IndexImport })
const Postscatchall = createRoute({ component: PostscatchallImport })
@@ -2,12 +2,12 @@
import { createRoute, dynamic } from 'tuono'
import RootImport from './routes/__root'
import RootLayoutImport from './routes/__layout'
const AboutImport = dynamic(() => import('./routes/about.mdx'))
const IndexImport = dynamic(() => import('./routes/index'))
const rootRoute = createRoute({ isRoot: true, component: RootImport })
const rootRoute = createRoute({ isRoot: true, component: RootLayoutImport })
const About = createRoute({ component: AboutImport })
const Index = createRoute({ component: IndexImport })
@@ -2,18 +2,18 @@
import { createRoute, dynamic } from 'tuono'
import RootImport from './routes/__root'
import RootLayoutImport from './routes/__layout'
const PostsrootImport = dynamic(() => import('./routes/posts/__root'))
const PostslayoutImport = dynamic(() => import('./routes/posts/__layout'))
const AboutImport = dynamic(() => import('./routes/about'))
const IndexImport = dynamic(() => import('./routes/index'))
const PostspostImport = dynamic(() => import('./routes/posts/[post]'))
const PostsIndexImport = dynamic(() => import('./routes/posts/index'))
const PostsMyPostImport = dynamic(() => import('./routes/posts/my-post'))
const rootRoute = createRoute({ isRoot: true, component: RootImport })
const rootRoute = createRoute({ isRoot: true, component: RootLayoutImport })
const Postsroot = createRoute({ component: PostsrootImport, isRoot: true })
const Postslayout = createRoute({ component: PostslayoutImport, isRoot: true })
const About = createRoute({ component: AboutImport })
const Index = createRoute({ component: IndexImport })
const Postspost = createRoute({ component: PostspostImport })
@@ -22,7 +22,7 @@ const PostsMyPost = createRoute({ component: PostsMyPostImport })
// Create/Update Routes
const PostsrootRoute = Postsroot.update({
const PostslayoutRoute = Postslayout.update({
getParentRoute: () => rootRoute,
})
@@ -38,17 +38,17 @@ const IndexRoute = Index.update({
const PostspostRoute = Postspost.update({
path: '/posts/[post]',
getParentRoute: () => PostsrootRoute,
getParentRoute: () => PostslayoutRoute,
})
const PostsIndexRoute = PostsIndex.update({
path: '/posts',
getParentRoute: () => PostsrootRoute,
getParentRoute: () => PostslayoutRoute,
})
const PostsMyPostRoute = PostsMyPost.update({
path: '/posts/my-post',
getParentRoute: () => PostsrootRoute,
getParentRoute: () => PostslayoutRoute,
})
// Create and export the route tree
@@ -56,7 +56,7 @@ const PostsMyPostRoute = PostsMyPost.update({
export const routeTree = rootRoute.addChildren([
IndexRoute,
AboutRoute,
PostsrootRoute.addChildren([
PostslayoutRoute.addChildren([
PostsMyPostRoute,
PostsIndexRoute,
PostspostRoute,
@@ -2,13 +2,13 @@
import { createRoute, dynamic } from 'tuono'
import RootImport from './routes/__root'
import RootLayoutImport from './routes/__layout'
const AboutImport = dynamic(() => import('./routes/about'))
const IndexImport = dynamic(() => import('./routes/index'))
const PostsMyPostImport = dynamic(() => import('./routes/posts/my-post'))
const rootRoute = createRoute({ isRoot: true, component: RootImport })
const rootRoute = createRoute({ isRoot: true, component: RootLayoutImport })
const About = createRoute({ component: AboutImport })
const Index = createRoute({ component: IndexImport })
@@ -2,12 +2,12 @@
import { createRoute, dynamic } from 'tuono'
import RootImport from './routes/__root'
import RootLayoutImport from './routes/__layout'
const AboutImport = dynamic(() => import('./routes/about'))
const IndexImport = dynamic(() => import('./routes/index'))
const rootRoute = createRoute({ isRoot: true, component: RootImport })
const rootRoute = createRoute({ isRoot: true, component: RootLayoutImport })
const About = createRoute({ component: AboutImport })
const Index = createRoute({ component: IndexImport })
@@ -42,7 +42,7 @@ interface TraverseRootComponentsProps<TData = unknown> {
/*
* This component traverses and renders
* all the components that wraps the selected route (__root).
* all the components that wraps the selected route (__layout).
* The parents components need to be memoized in order to avoid
* re-rendering bugs when changing route.
*/