Compare commits

...

47 Commits

Author SHA1 Message Date
Marco Pasqualetti 0ef1252e1f feat(packages/tuono-(router|fs-router-vite-plugin)): replace __route with __layout (#283) 2025-01-03 17:08:18 +01:00
Valerio Ageno cf56b3a53e Load template by tag (#284) 2025-01-03 17:08:10 +01:00
Marco Pasqualetti 592f402ffd feat: add support for react 19 (#264)
Co-authored-by: Valerio Ageno <valerioageno@yahoo.it>
2025-01-03 11:00:44 +01:00
renovate[bot] 1dc77fc473 fix(deps): update babel monorepo (#271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 13:36:04 +01:00
Marco Pasqualetti 09cd772d74 chore: refine eslint ignores and fix format script of tuono-fs-router-vite-plugin (#265) 2025-01-02 13:35:22 +01:00
Marco Pasqualetti 1c69e10e6a docs: update tuono to 0.16.9 (#280) 2025-01-02 10:57:39 +01:00
Marco Pasqualetti 1af18e4c91 chore(renovate): setup a package rule for eslint (#279) 2025-01-02 10:19:28 +01:00
renovate[bot] a3709609c5 chore(deps): update dependency @testing-library/react to v16.1.0 (#275)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 10:11:38 +01:00
renovate[bot] 70a79faee0 fix(deps): update dependency @types/node to v22.10.3 (#272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 09:37:33 +01:00
renovate[bot] 07cb7dae9d chore(deps): update dependency vitest to v2.1.8 (#269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 09:34:03 +01:00
renovate[bot] 9a1fb9c4a1 chore(deps): update dependency eslint-plugin-react to v7.37.3 (#267)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 09:32:48 +01:00
renovate[bot] a3f7ed7f61 fix(deps): update dependency prettier to v3.4.2 (#273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 09:28:52 +01:00
renovate[bot] 5302fd2779 chore(deps): update react monorepo (#270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 08:57:01 +01:00
Marco Pasqualetti ccd4e02b6a docs(local-development): use pnpm instead of turbo (#277) 2025-01-02 08:43:41 +01:00
Valerio Ageno f6593097b6 feat: update version to v0.16.9 2024-12-24 16:20:55 +01:00
gravity 230cd22bff feat(crates/tuono): tuono new - download template by cli version and git tag (#261) 2024-12-24 16:03:29 +01:00
Valerio Ageno f4367f1755 feat: update version to v0.16.8 2024-12-24 11:57:21 +01:00
Marco Pasqualetti 7b7447f9a0 feat(packages/vite-plugins): use named export and consistent names across plugins (#260) 2024-12-24 11:41:58 +01:00
Valerio Ageno d12d8404ec feat(packages/tuono-router): replace zustand with React.Context (#256)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2024-12-24 11:41:45 +01:00
Valerio Ageno d1b12173d5 feat: update version to v0.16.7 2024-12-23 18:32:15 +01:00
Marco Pasqualetti f3bf13da59 refactor(packages/*): add tuono- prefix to all relevant folders (#255) 2024-12-23 18:28:06 +01:00
Marco Pasqualetti f236b75f55 refactor(packages/*): use @vitejs/plugin-react-swc and make all package dependencies explicit (#254) 2024-12-23 18:20:29 +01:00
Marco Pasqualetti 2434231f9b chore: add eslint-plugin-react (#251) 2024-12-23 11:43:04 +01:00
Valerio Ageno b8b39e69a1 Add message event polyfill (#250) 2024-12-22 21:35:08 +01:00
Jovan Milosevic f8158dfd8e chore(examples): Remove tsconfig.node.js file from examples (#245) 2024-12-22 20:12:19 +01:00
Valerio Ageno f1ab7df5ce feat: udpdate version to v0.16.6 2024-12-22 18:06:40 +01:00
Marco Pasqualetti 986de49052 refactor: add eslint-plugin-react-hooks (#248) 2024-12-22 18:03:03 +01:00
Marco Pasqualetti d09ca5548a chore: add .editorconfig (#249) 2024-12-22 17:52:03 +01:00
Marco Pasqualetti 77e22c40b6 refactor: enable typescript-eslint strict rules (#243) 2024-12-22 12:08:21 +01:00
Marco Pasqualetti de74647c43 test(packages/tuono): messageChannel - use vi.fn (#241) 2024-12-22 09:30:18 +01:00
Mustafa Zaki Assagaf 498601c21a feat: add MessageChannel polyfill to support react 19 (#238) 2024-12-21 16:55:04 +01:00
Marco Pasqualetti e99f06ddf5 chore(bug_report): make system-info fields required and add CLI command to retrieve them (#239) 2024-12-20 07:34:55 +01:00
Marco Pasqualetti f06af59168 chore: add CODEOWNERS (#237) 2024-12-19 08:22:16 +01:00
Valerio Ageno d50e3a7fc0 feat: update version to v0.16.5 2024-12-18 22:10:05 +01:00
Valerio Ageno 3e4e7ff1ab feat: update rendering from renderToString to renderToReadableStream (#233)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2024-12-18 21:58:19 +01:00
Marco Pasqualetti 21ff3c8655 docs: update tuono (#236) 2024-12-18 21:53:24 +01:00
Valerio Ageno 166e61bab4 feat: update version to v0.16.4 2024-12-18 14:14:51 +01:00
Mustafa Zaki Assagaf 93cd8bd8da fix: catch-all paths replace double underscore with single underscore (#229) 2024-12-18 14:06:14 +01:00
Marco Pasqualetti f5c1641af2 chore(apps/documentation): remove tsconfig.node.json (#228) 2024-12-18 09:54:52 +01:00
Mustafa Zaki Assagaf 62ffed0624 docs: added website link to creates documentation (#230) 2024-12-18 09:13:03 +01:00
Marco Pasqualetti 8d0a4a7417 refactor(packages/lazy-fn-vite-plugin): add filter to avoid processing unrelated files (#227) 2024-12-18 09:11:30 +01:00
Mustafa Zaki Assagaf b1b274ea12 feat: update tuono macro handler naming to avoid naming collisions (#225) 2024-12-17 16:47:23 +01:00
Valerio Ageno fc38865965 fix: README.md 2024-12-16 13:57:25 +01:00
Valerio Ageno 911adaaecb feat: update version to v0.16.3 2024-12-16 08:08:27 +01:00
Marco Pasqualetti 7626ed8d1d fix(packages/lazy-fn-vite-plugin): make dynamic function detection stricter (#223) 2024-12-16 08:03:37 +01:00
Valerio Ageno 6135fd7e1b docs: add dynamic routes page (#219)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2024-12-15 13:39:06 +01:00
Valerio Ageno 14bea36356 fix: update lazyLoadComponent fn name to __tuono__internal__lazyLoadComponent (#222) 2024-12-15 09:22:19 +01:00
170 changed files with 2644 additions and 2060 deletions
+7
View File
@@ -0,0 +1,7 @@
[*]
charset = utf-8
insert_final_newline = true
end_of_line = lf
indent_style = space
indent_size = 2
max_line_length = 80
+14 -45
View File
@@ -32,55 +32,24 @@ body:
description: If applicable, add screenshots to help explain your problem.
validations:
required: false
- type: input
id: tuono-version
- type: textarea
id: system-info
attributes:
label: Tuono version
placeholder: '[e.g. 0.4.0]'
label: System Info
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
render: shell
placeholder: System, Binaries, Browsers
validations:
required: false
- type: input
id: os-version
required: true
- type: textarea
id: system-info-rust
attributes:
label: OS
placeholder: '[e.g. MacOS, Windows]'
label: System info (Rust)
description: Output of `rustc --version && cargo --version && tuono --version`
render: shell
placeholder: 'rustc, cargo and tuono binary version'
validations:
required: false
- type: input
id: browser-version
attributes:
label: Browser
placeholder: '[e.g. chrome, safari]'
validations:
required: false
- type: input
id: node-version
attributes:
label: Node version
placeholder: '[e.g. 20.0.0]'
validations:
required: false
- type: input
id: rust-version
attributes:
label: Rust version
placeholder: '[e.g. 1.79.0]'
validations:
required: false
- type: input
id: create-version
attributes:
label: Crate version
placeholder: '[e.g. 1.78.0]'
validations:
required: false
- type: input
id: node-package-manager-version
attributes:
label: Node Package Manger version
placeholder: '[e.g. pnpm: 9.5.0] '
validations:
required: false
required: true
- type: textarea
id: additional-context
attributes:
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install tuono
run: cargo install tuono@0.15.0
run: cargo install tuono@0.16.9
- name: Build project
working-directory: ./apps/documentation
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install tuono
run: cargo install tuono@0.16.0
run: cargo install tuono@0.16.9
- name: Build project
working-directory: ./apps/documentation
+2
View File
@@ -8,6 +8,8 @@ dist/
examples/*/pnpm-lock.yaml
vite.config.ts.timestamp*
## Rust related ignores
# Generated by Cargo
+8 -1
View File
@@ -1 +1,8 @@
pnpm-lock.yaml
pnpm-lock.yaml
dist
.tuono
vite.config.ts.timestamp-*
packages/tuono-lazy-fn-vite-plugin/tests/sources/*
+9
View File
@@ -0,0 +1,9 @@
* @Valerioageno @marcalexiei
# Rust
/crates/ @Valerioageno
/Cargo.toml @Valerioageno
# Misc
/.github/ @marcalexiei
+1 -1
View File
@@ -26,7 +26,7 @@ Some of its features are:
## 📖 Documentation
The [documentation](https://tuono.dev/documentation) is available on
The [documentation](https://tuono.dev/) is available on
[tuono.dev](https://tuono.dev/).
## Introduction
+17
View File
@@ -0,0 +1,17 @@
# Documentation Contributing
## Update `tuono` version in `apps/documentation`
Update `tuono` version in the following files
- `.github/workflows/ci-documentation.yml`
- `.github/workflows/deploy-documentation.yml`
- `apps/documentation/Cargo.toml`
- `apps/documentation/package.json`
[Refer to this PR for additional information](https://github.com/tuono-labs/tuono/pull/236/files)
In the future we might:
- move the documentation in a separate repo
- make a script to perform this operation
+1 -1
View File
@@ -8,7 +8,7 @@ name = "tuono"
path = ".tuono/main.rs"
[dependencies]
tuono_lib = "0.15.0"
tuono_lib = "0.16.9"
glob = "0.3.1"
time = { version = "0.3", features = ["macros"] }
serde = { version = "1.0.202", features = ["derive"] }
+4 -4
View File
@@ -6,7 +6,7 @@
"type": "module",
"scripts": {
"lint": "eslint .",
"format": "prettier -u --write --ignore-unknown .",
"format": "prettier --write --ignore-unknown .",
"format:check": "prettier --check --ignore-unknown .",
"types": "tsc --noEmit"
},
@@ -19,12 +19,12 @@
"clsx": "2.1.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"tuono": "npm:tuono@0.16.0"
"tuono": "npm:tuono@0.16.9"
},
"devDependencies": {
"@mdx-js/rollup": "3.1.0",
"@types/react": "18.3.13",
"@types/react-dom": "18.3.0",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"postcss": "8.4.49",
"postcss-preset-mantine": "1.17.0",
"postcss-simple-vars": "7.0.1"
@@ -16,6 +16,12 @@ export default function MdxTitle(props: TitleProps): JSX.Element {
)
}
export const h =
(order: 1 | 2 | 3 | 4 | 5 | 6) =>
(props: TitleProps): JSX.Element => <MdxTitle order={order} {...props} />
export const h = (
order: 1 | 2 | 3 | 4 | 5 | 6,
): React.ElementType<TitleProps> => {
function render(props: TitleProps): JSX.Element {
return <MdxTitle order={order} {...props} />
}
render.displayName = 'H'
return render
}
@@ -111,6 +111,11 @@ export const sidebarElements: Array<SidebarElement> = [
label: 'Defining routes',
href: '/documentation/routing/defining-routes',
},
{
type: 'element',
label: 'Dynamic routes',
href: '/documentation/routing/dynamic-routes',
},
{
type: 'element',
label: 'Link and navigation',
@@ -126,11 +131,6 @@ export const sidebarElements: Array<SidebarElement> = [
label: 'Loading state',
href: '/documentation/routing/loading-state',
},
{
type: 'element',
label: 'Dynamic routes',
href: '/documentation/routing/dynamic-routes',
},
{
type: 'element',
label: 'Layouts',
@@ -50,26 +50,30 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
<AppShell.Section component={ScrollArea}>
{sidebarElements.map((el, i) => {
if (el.type === 'divider') {
return <Divider my="md" mx={10} />
return <Divider key={`${el.type}-${i}`} my="md" mx={10} />
}
if (el.type === 'title') {
return (
<Text
key={`${el.type}-${i}`}
size="xs"
fw={700}
fz={12}
pl={12}
py={5}
children={el.label}
/>
>
{el.label}
</Text>
)
}
if (el.children?.length) {
return (
<SidebarLink
key={`${el.type}-${i}`}
href={el.href}
label={el.label}
key={i}
leftSection={el.leftIcon}
>
{el.children.map((child, index) => (
@@ -83,12 +87,13 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
</SidebarLink>
)
}
return (
<SidebarLink
key={`${el.type}-${i}`}
href={el.href}
label={el.label}
onClick={close}
key={i}
/>
)
})}
@@ -64,7 +64,9 @@ export function TableOfContents({
),
)
window.addEventListener('scroll', handleScroll)
return (): void => window.removeEventListener('scroll', handleScroll)
return (): void => {
window.removeEventListener('scroll', handleScroll)
}
}, [router.pathname])
if (filteredHeadings.length === 0) {
@@ -17,9 +17,9 @@ export default function ThemeBtn(): JSX.Element {
return (
<ActionIcon
onClick={() =>
onClick={() => {
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
}
}}
variant="default"
size="lg"
aria-label="Toggle color scheme"
@@ -61,7 +61,7 @@ You can see which version of yarn we use by checking the `packageManager` field
To check that everything is working properly, run:
```sh
turbo run check-all
pnpm run check-all
cargo build
```
@@ -70,7 +70,7 @@ cargo build
1. Start tuono frontend build using
```sh
turbo run dev
pnpm run dev
```
2. In another terminal run
@@ -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>
```
@@ -9,6 +9,53 @@ import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Dynamic routes' }]} />
# Loading state
# Dynamic routes
Todo
> 🚧 Only available for SSR apps. This feature will be soon available also for static generated apps.
When the exact segment names are not known in advance and you need to generate routes from dynamic data,
you can use dynamic segments, which are populated at request time.
## Convention
A dynamic segment can be created by wrapping a
file or a folder name in square brackets: `[segmentName]`.
The dynamic segment can be used in folders as well as file names:
- `src/routes/blog/[slug].tsx`
- `src/routes/blog/[slug]/index.tsx`
A path can contain multiple dynamic segments, allowing for more flexible routing:
- `src/routes/blog/[post]/[comment].tsx`
### Example
Assuming you're working on a blog with multiple posts,
you can use a dynamic segment to define the post page: `src/routes/blog/[slug].tsx`.
The `[slug]` is the dynamic segment which will be used to identify individual blog posts.
```tsx
import { useRouter } from 'tuono'
export default function Page() {
const router = useRouter()
return <p>Post: {router.pathname}</p>
}
```
## Catch all segments
Dynamic Segments can be extended to catch-all subsequent segments by adding
an ellipsis inside the brackets `[...segmentName]`.
Given the following `src/routes/shop/[...slug].tsx`,
the following paths will be matched:
- `/shop/clothes`
- `/shop/clothes/tops`
- `/shop`
- `/shop/clothes/tops/t-shirts`
- and so on.
+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))
{
+1 -2
View File
@@ -32,6 +32,5 @@
// Completeness
"skipLibCheck": true
},
"include": ["src", "tuono.config.ts", "postcss.config.js"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": ["src", "tuono.config.ts", "postcss.config.js"]
}
-11
View File
@@ -1,11 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}
-3
View File
@@ -7,9 +7,6 @@ const config: TuonoConfig = {
'@': 'src',
'@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs',
},
optimizeDeps: {
exclude: ['@mdx-js/react'],
},
plugins: [
{ enforce: 'pre', ...mdx({ providerImportSource: '@mdx-js/react' }) },
],
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tuono"
version = "0.16.2"
version = "0.17.0"
edition = "2021"
authors = ["V. Ageno <valerioageno@yahoo.it>"]
description = "Superfast React fullstack framework"
+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| {
+5
View File
@@ -1,3 +1,8 @@
//! ## Tuono
//! Tuono is a full-stack web framework for building React applications using Rust as the backend with a strong focus on usability and performance.
//!
//! You can find the full documentation at [tuono.dev](https://tuono.dev/)
mod app;
pub mod cli;
mod mode;
+1 -1
View File
@@ -52,7 +52,7 @@ impl AxumInfo {
.replace('/', "_")
.replace('-', "_hyphen_")
.replace('[', "dyn_")
.replace("...", "_catch_all_")
.replace("...", "catch_all_")
.replace(']', ""),
axum_route: axum_route
.replace("[...", "*")
+41 -12
View File
@@ -6,10 +6,12 @@ use std::fs::{self, create_dir, File, OpenOptions};
use std::io::{self, prelude::*};
use std::path::{Path, PathBuf};
const GITHUB_TUONO_REPO_URL: &str =
"https://api.github.com/repos/tuono-labs/tuono/git/trees/main?recursive=1";
const GITHUB_TUONO_TAGS_URL: &str = "https://api.github.com/repos/tuono-labs/tuono/git/ref/tags/";
const GITHUB_RAW_CONTENT_URL: &str = "https://raw.githubusercontent.com/tuono-labs/tuono/main/";
const GITHUB_TUONO_TAG_COMMIT_TREES_URL: &str =
"https://api.github.com/repos/tuono-labs/tuono/git/trees/";
const GITHUB_RAW_CONTENT_URL: &str = "https://raw.githubusercontent.com/tuono-labs/tuono";
#[derive(Deserialize, Debug)]
enum GithubFileType {
@@ -20,7 +22,17 @@ enum GithubFileType {
}
#[derive(Deserialize, Debug)]
struct GithubResponse<T> {
struct GithubTagObject {
sha: String,
}
#[derive(Deserialize, Debug)]
struct GithubTagResponse {
object: GithubTagObject,
}
#[derive(Deserialize, Debug)]
struct GithubTreeResponse<T> {
tree: Vec<T>,
}
@@ -49,14 +61,31 @@ pub fn create_new_project(folder_name: Option<String>, template: Option<String>)
.build()
.expect("Failed to build reqwest client");
let res = client
.get(GITHUB_TUONO_REPO_URL)
.send()
.expect("Failed to call the folder github API")
.json::<GithubResponse<GithubFile>>()
.expect("Failed to parse the repo structure");
// This string does not include the "v" version prefix
let cli_version: &str = crate_version!();
let new_project_files = res
let res_tag = client
.get(format!("{}v{}", GITHUB_TUONO_TAGS_URL, cli_version))
.send()
.unwrap_or_else(|_| panic!("Failed to call the tag github API for v{cli_version}"))
.json::<GithubTagResponse>()
.expect("Failed to parse the tag response");
let sha_tagged_commit = res_tag.object.sha;
let res_tree = client
.get(format!(
"{}{}?recursive=1",
GITHUB_TUONO_TAG_COMMIT_TREES_URL, sha_tagged_commit
))
.send()
.unwrap_or_else(|_| {
panic!("Failed to call the tagged commit tree github API for v{cli_version}")
})
.json::<GithubTreeResponse<GithubFile>>()
.expect("Failed to parse the tree structure");
let new_project_files = res_tree
.tree
.iter()
.filter(|GithubFile { path, .. }| path.starts_with(&format!("examples/{template}/")))
@@ -91,7 +120,7 @@ pub fn create_new_project(folder_name: Option<String>, template: Option<String>)
{
if let GithubFileType::Blob = element_type {
let file_content = client
.get(format!("{GITHUB_RAW_CONTENT_URL}{path}"))
.get(format!("{GITHUB_RAW_CONTENT_URL}/v{cli_version}/{path}"))
.send()
.expect("Failed to call the folder github API")
.text()
+2 -2
View File
@@ -84,11 +84,11 @@ fn create_routes_declaration(routes: &HashMap<String, Route>) -> String {
if !route.is_api() {
route_declarations.push_str(&format!(
r#".route("{axum_route}", get({module_import}::route))"#
r#".route("{axum_route}", get({module_import}::tuono__internal__route))"#
));
route_declarations.push_str(&format!(
r#".route("/__tuono/data{axum_route}", get({module_import}::api))"#
r#".route("/__tuono/data{axum_route}", get({module_import}::tuono__internal__api))"#
));
} else {
for method in route.api_data.as_ref().unwrap().methods.clone() {
+13 -8
View File
@@ -30,7 +30,7 @@ fn it_successfully_create_the_index_route() {
assert!(temp_main_rs_content.contains("mod index;"));
assert!(temp_main_rs_content
.contains(r#".route("/", get(index::route)).route("/__tuono/data/", get(index::api))"#));
.contains(r#".route("/", get(index::tuono__internal__route)).route("/__tuono/data/", get(index::tuono__internal__api))"#));
}
#[test]
@@ -120,18 +120,23 @@ fn it_successfully_create_catch_all_routes() {
fs::read_to_string(&temp_main_rs_path).expect("Failed to read '.tuono/main.rs' content.");
assert!(temp_main_rs_content.contains(r#"#[path="../src/routes/api/[...all_apis].rs"]"#));
assert!(temp_main_rs_content.contains("mod api_dyn__catch_all_all_apis;"));
assert!(temp_main_rs_content.contains("mod api_dyn_catch_all_all_apis;"));
assert!(temp_main_rs_content.contains(r#"#[path="../src/routes/[...all_routes].rs"]"#));
assert!(temp_main_rs_content.contains("mod dyn__catch_all_all_routes;"));
assert!(temp_main_rs_content.contains("mod dyn_catch_all_all_routes;"));
assert!(temp_main_rs_content.contains(
r#".route("/api/*all_apis", post(api_dyn__catch_all_all_apis::post__tuono_internal_api))"#
r#".route("/api/*all_apis", post(api_dyn_catch_all_all_apis::post__tuono_internal_api))"#
));
assert!(temp_main_rs_content.contains(
r#".route("/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__route))"#
));
assert!(temp_main_rs_content.contains(
r#".route("/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__route))"#
));
assert!(temp_main_rs_content
.contains(r#".route("/*all_routes", get(dyn__catch_all_all_routes::route))"#));
assert!(temp_main_rs_content
.contains(r#".route("/__tuono/data/*all_routes", get(dyn__catch_all_all_routes::api))"#));
.contains(r#".route("/__tuono/data/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__api))"#));
}
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tuono_lib"
version = "0.16.2"
version = "0.17.0"
edition = "2021"
authors = ["V. Ageno <valerioageno@yahoo.it>"]
description = "Superfast React fullstack framework"
@@ -32,7 +32,7 @@ either = "1.13.0"
tower-http = {version = "0.6.0", features = ["fs"]}
colored = "2.1.0"
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.16.2"}
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.17.0"}
# Match the same version used by axum
tokio-tungstenite = "0.24.0"
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
+5
View File
@@ -1,3 +1,8 @@
//! ## Tuono
//! Tuono is a full-stack web framework for building React applications using Rust as the backend with a strong focus on usability and performance.
//!
//! You can find the full documentation at [tuono.dev](https://tuono.dev/)
mod catch_all;
mod logger;
mod manifest;
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tuono_lib_macros"
version = "0.16.2"
version = "0.17.0"
edition = "2021"
description = "Superfast React fullstack framework"
homepage = "https://tuono.dev"
+2 -2
View File
@@ -45,7 +45,7 @@ pub fn handler_core(_args: TokenStream, item: TokenStream) -> TokenStream {
#item
pub async fn route(
pub async fn tuono__internal__route(
#axum_arguments
) -> impl tuono_lib::axum::response::IntoResponse {
@@ -59,7 +59,7 @@ pub fn handler_core(_args: TokenStream, item: TokenStream) -> TokenStream {
#fn_name(req.clone(), #argument_names).await.render_to_string(req)
}
pub async fn api(
pub async fn tuono__internal__api(
#axum_arguments
) -> impl tuono_lib::axum::response::IntoResponse {
+5
View File
@@ -1,3 +1,8 @@
//! ## Tuono
//! Tuono is a full-stack web framework for building React applications using Rust as the backend with a strong focus on usability and performance.
//!
//! You can find the full documentation at [tuono.dev](https://tuono.dev/)
extern crate proc_macro;
use proc_macro::TokenStream;
+49 -13
View File
@@ -2,21 +2,29 @@ import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
// @ts-expect-error no types are available for this plugin
import eslintPluginImport from 'eslint-plugin-import'
import eslintPluginReact from 'eslint-plugin-react'
// @ts-expect-error no types are available for this plugin
import eslintPluginReactHooks from 'eslint-plugin-react-hooks'
export default tseslint.config(
{
ignores: [
// #region shared
'**/dist',
'**/out',
'**/.tuono',
'**/vite.config.ts.timestamp**',
// #endregion shared
// #region package-specific
'packages/fs-router-vite-plugin/tests/generator/**',
'packages/tuono-fs-router-vite-plugin/tests/generator/**',
'packages/tuono-lazy-fn-vite-plugin/tests/sources/**',
'packages/tuono/bin/**',
// #endregion package-specific
'examples/**',
],
},
{
@@ -24,10 +32,34 @@ export default tseslint.config(
reportUnusedDisableDirectives: 'error',
},
},
eslint.configs.recommended,
/* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access */
eslintPluginImport.flatConfigs.recommended,
eslintPluginImport.flatConfigs.typescript,
tseslint.configs.recommendedTypeChecked,
/* eslint-enable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access */
// eslint-disable-next-line import/no-named-as-default-member
tseslint.configs.strictTypeChecked,
// @ts-expect-error flat is optional but always defined on runtime
eslintPluginReact.configs.flat.recommended,
// @ts-expect-error flat is optional but always defined on runtime
eslintPluginReact.configs.flat['jsx-runtime'],
{
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
plugins: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'react-hooks': eslintPluginReactHooks,
},
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
},
},
{
languageOptions: {
parserOptions: {
@@ -49,16 +81,13 @@ export default tseslint.config(
rules: {
// #region @typescript-eslint
'@typescript-eslint/array-type': ['error', { default: 'generic' }],
'@typescript-eslint/no-wrapper-object-types': 'error',
'@typescript-eslint/no-empty-object-type': 'error',
'@typescript-eslint/no-unsafe-function-type': 'error',
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/consistent-type-definitions': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports' },
],
'@typescript-eslint/explicit-module-boundary-types': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/method-signature-style': ['error', 'property'],
'@typescript-eslint/naming-convention': [
'error',
@@ -73,19 +102,24 @@ export default tseslint.config(
},
},
],
'@typescript-eslint/no-deprecated': 'error',
'@typescript-eslint/no-empty-function': 'error',
'@typescript-eslint/no-empty-interface': 'error',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/no-unnecessary-condition': 'error',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/no-inferrable-types': [
'error',
{ ignoreParameters: true },
],
'@typescript-eslint/restrict-template-expressions': [
'error',
{
allowAny: false,
allowBoolean: false,
allowNever: false,
allowNullish: false,
allowNumber: true,
allowRegExp: false,
},
],
// #endregion @typescript-eslint
// #region import
@@ -126,8 +160,10 @@ export default tseslint.config(
'no-shadow': 'error',
'no-undef': 'off',
'sort-imports': 'off',
// #endregion misc
},
},
{
files: ['apps/documentation/**'],
settings: {
+5 -5
View File
@@ -3,13 +3,13 @@
"description": "Basic tuono application",
"version": "0.0.1",
"dependencies": {
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tuono": "link:../../packages/tuono"
},
"devDependencies": {
"@types/react": "^18.3.13",
"@types/react-dom": "^18.3.0",
"typescript": "^5.6.3"
"@types/react": "19.0.2",
"@types/react-dom": "19.0.2",
"typescript": "5.6.3"
}
}
@@ -0,0 +1,15 @@
import type { ReactNode, JSX } from 'react'
interface RootLayoutProps {
children: ReactNode
}
export default function RootLayout({ children }: RootLayoutProps): JSX.Element {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}
-9
View File
@@ -1,9 +0,0 @@
import type { ReactNode, JSX } from 'react'
interface RootRouteProps {
children: ReactNode
}
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
return <main className="main">{children}</main>
}
+1 -2
View File
@@ -20,6 +20,5 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "tuono.config.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": ["src", "tuono.config.ts"]
}
-11
View File
@@ -1,11 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}
+4 -4
View File
@@ -3,13 +3,13 @@
"description": "The basic tutorial for tuono applications",
"version": "0.0.1",
"dependencies": {
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tuono": "link:../../packages/tuono"
},
"devDependencies": {
"@types/react": "^18.3.13",
"@types/react-dom": "^18.3.0",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"typescript": "^5.6.3"
}
}
@@ -0,0 +1,15 @@
import type { ReactNode, JSX } from 'react'
interface RootLayoutProps {
children: ReactNode
}
export default function RootLayout({ children }: RootLayoutProps): JSX.Element {
return (
<html>
<body>
<main>{children}</main>
</body>
</html>
)
}
@@ -1,9 +0,0 @@
import type { ReactNode, JSX } from 'react'
interface RootRouteProps {
children: ReactNode
}
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
return <main className="main">{children}</main>
}
+18 -12
View File
@@ -1,6 +1,6 @@
// src/routes/index.tsx
import type { JSX } from 'react'
import { Head, type TuonoProps } from 'tuono'
import type { TuonoProps } from 'tuono'
import PokemonLink from '@/components/PokemonLink'
@@ -15,20 +15,25 @@ interface IndexProps {
export default function IndexPage({
data,
}: TuonoProps<IndexProps>): JSX.Element | null {
if (!data?.results) {
return null
}
if (!data?.results) return null
return (
<>
<Head>
<title>Tuono tutorial</title>
</Head>
<title>Tuono tutorial</title>
<header className="header">
<a href="https://crates.io/crates/tuono" target="_blank">
<a
href="https://crates.io/crates/tuono"
target="_blank"
rel="noreferrer"
>
Crates
</a>
<a href="https://www.npmjs.com/package/tuono" target="_blank">
<a
href="https://www.npmjs.com/package/tuono"
target="_blank"
rel="noreferrer"
>
Npm
</a>
</header>
@@ -43,9 +48,10 @@ export default function IndexPage({
</div>
<ul style={{ flexWrap: 'wrap', display: 'flex', gap: 10 }}>
<PokemonLink pokemon={{ name: 'GOAT' }} id={0} />
{data.results.map((pokemon, i) => {
return <PokemonLink pokemon={pokemon} id={i + 1} key={i} />
})}
{data.results.map((pokemon, i) => (
<PokemonLink key={i} pokemon={pokemon} id={i + 1} />
))}
</ul>
</>
)
@@ -1,5 +1,5 @@
import type { JSX } from 'react'
import { Head, type TuonoProps } from 'tuono'
import type { TuonoProps } from 'tuono'
import PokemonView from '@/components/PokemonView'
@@ -15,9 +15,8 @@ export default function PokemonPage({
}: TuonoProps<Pokemon>): JSX.Element {
return (
<>
<Head>
<title>{`Pokemon: ${data?.name}`}</title>
</Head>
<title>{`Pokemon: ${data?.name ?? ''}`}</title>
<PokemonView pokemon={data} />
</>
)
@@ -4,6 +4,7 @@
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
+1 -2
View File
@@ -23,6 +23,5 @@
"@/*": ["./src/*"]
}
},
"include": ["src", "tuono.config.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": ["src", "tuono.config.ts"]
}
@@ -1,11 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}
+4 -4
View File
@@ -4,14 +4,14 @@
"version": "0.0.1",
"dependencies": {
"@mdx-js/react": "3.1.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tuono": "link:../../packages/tuono"
},
"devDependencies": {
"@mdx-js/rollup": "3.1.0",
"@types/react": "18.3.13",
"@types/react-dom": "18.3.0",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"typescript": "^5.6.3"
}
}
+18
View File
@@ -0,0 +1,18 @@
import type { ReactNode, JSX } from 'react'
import { MDXProvider } from '@mdx-js/react'
interface RootLayoutProps {
children: ReactNode
}
export default function RootLayout({ children }: RootLayoutProps): JSX.Element {
return (
<html>
<body>
<main>
<MDXProvider components={{}}>{children}</MDXProvider>
</main>
</body>
</html>
)
}
-14
View File
@@ -1,14 +0,0 @@
import type { ReactNode, JSX } from 'react'
import { MDXProvider } from '@mdx-js/react'
interface RootRouteProps {
children: ReactNode
}
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
return (
<main className="main">
<MDXProvider components={{}}>{children}</MDXProvider>
</main>
)
}
+1 -2
View File
@@ -20,6 +20,5 @@
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "tuono.config.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": ["src", "tuono.config.ts"]
}
-11
View File
@@ -1,11 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}
+9 -14
View File
@@ -24,20 +24,15 @@
"license": "MIT",
"devDependencies": {
"@eslint/js": "9.15.0",
"@tanstack/config": "^0.7.0",
"@types/node": "^22.0.0",
"@types/react": "^18.3.13",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^9.15.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^5.0.0",
"prettier": "^3.2.4",
"turbo": "^2.2.3",
"@types/node": "22.10.3",
"eslint": "9.15.0",
"eslint-import-resolver-typescript": "3.6.3",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-react": "7.37.3",
"eslint-plugin-react-hooks": "5.1.0",
"prettier": "3.4.1",
"turbo": "2.2.3",
"typescript": "5.6.3",
"typescript-eslint": "8.15.0",
"vite": "^5.2.11",
"vitest": "^2.0.0"
"typescript-eslint": "8.15.0"
}
}
@@ -1,2 +0,0 @@
dist
pnpm-lock.yaml
@@ -1,2 +0,0 @@
dist
pnpm-lock.yaml
-119
View File
@@ -1,119 +0,0 @@
import * as babel from '@babel/core'
import * as t from '@babel/types'
import type { Plugin } from 'vite'
import type { PluginItem } from '@babel/core'
import type {
Identifier,
CallExpression,
ArrowFunctionExpression,
StringLiteral,
} from '@babel/types'
import {
TUONO_MAIN_PACKAGE,
TUONO_DYNAMIC_FN_ID,
TUONO_LAZY_FN_ID,
} from './constants'
import { isTuonoDynamicFnImported } from './utils'
/**
* [SERVER build]
* This plugin just removes the `dynamic` imported function from any tuono import
*/
const RemoveTuonoLazyImport: PluginItem = {
name: 'remove-tuono-lazy-import-plugin',
visitor: {
ImportSpecifier: (path) => {
if (isTuonoDynamicFnImported(path)) {
path.remove()
}
},
},
}
/**
* [CLIENT build]
* This plugin replace the `dynamic` function with the `lazyLoadComponent` one
*/
const ReplaceTuonoLazyImport: PluginItem = {
name: 'remove-tuono-lazy-import-plugin',
visitor: {
ImportSpecifier: (path) => {
if (isTuonoDynamicFnImported(path)) {
;(path.node.imported as Identifier).name = TUONO_LAZY_FN_ID
}
},
},
}
const turnLazyIntoStatic = {
VariableDeclaration: (path: babel.NodePath<t.VariableDeclaration>): void => {
path.node.declarations.forEach((el) => {
const init = el.init as CallExpression
if ((init.callee as Identifier).name === TUONO_DYNAMIC_FN_ID) {
const importName = (el.id as Identifier).name
const importPath = (
(
(init.arguments[0] as ArrowFunctionExpression)
.body as CallExpression
).arguments[0] as StringLiteral
).value
if (importName && importPath) {
const importDeclaration = t.importDeclaration(
[t.importDefaultSpecifier(t.identifier(importName))],
t.stringLiteral(importPath),
)
path.replaceWith(importDeclaration)
}
}
})
},
}
/**
* [SERVER build]
* This plugin statically imports the lazy loaded components
*/
const TurnLazyIntoStaticImport: PluginItem = {
name: 'turn-lazy-into-static-import-plugin',
visitor: {
Program: (path) => {
path.traverse({
ImportSpecifier: (subPath) => {
if (isTuonoDynamicFnImported(subPath)) {
path.traverse(turnLazyIntoStatic)
}
},
})
},
},
}
export function LazyLoadingPlugin(): Plugin {
return {
name: 'vite-plugin-tuono-lazy-loading',
enforce: 'pre',
transform(code, _id, opts): string | undefined | null {
if (
code.includes(TUONO_DYNAMIC_FN_ID) &&
code.includes(TUONO_MAIN_PACKAGE)
) {
const res = babel.transformSync(code, {
plugins: [
['@babel/plugin-syntax-jsx', {}],
['@babel/plugin-syntax-typescript', { isTSX: true }],
[!opts?.ssr ? ReplaceTuonoLazyImport : []],
[opts?.ssr ? RemoveTuonoLazyImport : []],
[opts?.ssr ? TurnLazyIntoStaticImport : []],
],
sourceMaps: true,
})
return res?.code
}
return code
},
}
}
-22
View File
@@ -1,22 +0,0 @@
import type {
Identifier,
ImportDeclaration,
ImportSpecifier,
} from '@babel/types'
import { TUONO_MAIN_PACKAGE, TUONO_DYNAMIC_FN_ID } from './constants'
export const isTuonoDynamicFnImported = (
path: babel.NodePath<ImportSpecifier>,
): boolean => {
if ((path.node.imported as Identifier).name !== TUONO_DYNAMIC_FN_ID) {
return false
}
if (
(path.parentPath.node as ImportDeclaration).source.value !==
TUONO_MAIN_PACKAGE
) {
return false
}
return true
}
@@ -1,77 +0,0 @@
import { it, expect, describe } from 'vitest'
import type { Plugin } from 'vite'
import { LazyLoadingPlugin } from '../src'
const SOURCE_CODE = `
import { createRoute, dynamic } from 'tuono'
const IndexImport = dynamic(() => import('./../src/routes/index'))
const PokemonspokemonImport = dynamic(
() => import('./../src/routes/pokemons/[pokemon]'),
)
`
const NON_DYNAMIC_SOURCE = `
import { createRoute } from 'tuono'
import {dynamic} from 'external-lib'
const IndexImport = dynamic(() => import('./../src/routes/index'))
const PokemonspokemonImport = dynamic(
() => import('./../src/routes/pokemons/[pokemon]'),
)
`
type ViteTransformHandler = Exclude<
Plugin['transform'],
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
Function | undefined
>['handler']
// Create a type-safe transform method
function getTransform(): (...args: Parameters<ViteTransformHandler>) => string {
return LazyLoadingPlugin().transform as never
}
describe('Transpile tuono source', () => {
it('Into the client bundle', () => {
const pluginTransform = getTransform()
const bundle = pluginTransform(SOURCE_CODE, 'id')
expect(bundle)
.toBe(`import { createRoute, lazyLoadComponent as dynamic } from 'tuono';
const IndexImport = dynamic(() => import('./../src/routes/index'));
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));`)
})
it('Into the server bundle', () => {
const pluginTransform = getTransform()
const bundle = pluginTransform(SOURCE_CODE, 'id', {
ssr: true,
})
expect(bundle).toBe(`import { createRoute } from 'tuono';
import IndexImport from "./../src/routes/index";
import PokemonspokemonImport from "./../src/routes/pokemons/[pokemon]";`)
})
})
describe('Non tuono dynamic function', () => {
it('Into the client bundle', () => {
const pluginTransform = getTransform()
const bundle = pluginTransform(NON_DYNAMIC_SOURCE, 'id')
expect(bundle).toBe(`import { createRoute } from 'tuono';
import { dynamic } from 'external-lib';
const IndexImport = dynamic(() => import('./../src/routes/index'));
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));`)
})
it('Into the server bundle', () => {
const pluginTransform = getTransform()
const bundle = pluginTransform(NON_DYNAMIC_SOURCE, 'id', {
ssr: true,
})
expect(bundle).toBe(`import { createRoute } from 'tuono';
import { dynamic } from 'external-lib';
const IndexImport = dynamic(() => import('./../src/routes/index'));
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));`)
})
})
@@ -1,4 +0,0 @@
{
"extends": "../../tsconfig.json",
"include": ["src", "tests", "vite.config.ts"]
}
-2
View File
@@ -1,2 +0,0 @@
dist
pnpm-lock.yaml
@@ -1,22 +0,0 @@
import React from 'react'
import type { Router } from '../router'
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const routerContext = React.createContext<Router>(null!)
const TUONO_CONTEXT_GLOBAL_NAME = '__TUONO_CONTEXT__'
export function getRouterContext(): React.Context<Router> {
if (typeof document === 'undefined') {
return routerContext
}
if (window[TUONO_CONTEXT_GLOBAL_NAME]) {
return window[TUONO_CONTEXT_GLOBAL_NAME]
}
window[TUONO_CONTEXT_GLOBAL_NAME] = routerContext
return routerContext
}
@@ -1,51 +0,0 @@
import React from 'react'
import type { ReactNode, JSX } from 'react'
import { useListenBrowserUrlUpdates } from '../hooks/useListenBrowserUrlUpdates'
import { initRouterStore } from '../hooks/useRouterStore'
import type { ServerProps } from '../types'
import type { Router } from '../router'
import { getRouterContext } from './RouterContext'
import { Matches } from './Matches'
interface RouterContextProviderProps {
router: Router
children: ReactNode
}
function RouterContextProvider({
router,
children,
}: RouterContextProviderProps): JSX.Element {
// Allow the router to update options on the router instance
router.update({ ...router.options } as Parameters<typeof router.update>[0])
const routerContext = getRouterContext()
return (
<React.Suspense>
<routerContext.Provider value={router}>{children}</routerContext.Provider>
</React.Suspense>
)
}
interface RouterProviderProps {
router: Router
serverProps?: ServerProps
}
export function RouterProvider({
router,
serverProps,
}: RouterProviderProps): JSX.Element {
initRouterStore(serverProps)
useListenBrowserUrlUpdates()
return (
<RouterContextProvider router={router}>
<Matches serverSideProps={serverProps?.props} />
</RouterContextProvider>
)
}
@@ -1,8 +0,0 @@
import * as React from 'react'
import { getRouterContext } from '../components/RouterContext'
import type { Router } from '../router'
export function useInternalRouter(): Router {
return React.useContext(getRouterContext())
}
@@ -1,31 +0,0 @@
import { useEffect } from 'react'
import { useRouterStore } from './useRouterStore'
/**
* This hook is meant to handle just browser related location updates
* like the back and forward buttons.
*/
export const useListenBrowserUrlUpdates = (): void => {
const updateLocation = useRouterStore((st) => st.updateLocation)
const updateLocationOnPopStateChange = ({ target }: PopStateEvent): void => {
const { pathname, hash, href, search } = (target as typeof window).location
updateLocation({
pathname,
hash,
href,
searchStr: search,
search: Object.fromEntries(new URLSearchParams(search)),
})
}
useEffect(() => {
window.addEventListener('popstate', updateLocationOnPopStateChange)
return (): void => {
window.removeEventListener('popstate', updateLocationOnPopStateChange)
}
}, [])
}
-56
View File
@@ -1,56 +0,0 @@
import { useRouterStore } from './useRouterStore'
interface PushOptions {
/**
* If "false" the scroll offset will be kept across page navigation. Default "true"
*/
scroll?: boolean
}
interface UseRouterHook {
/**
* Redirects to the path passed as argument updating the browser history.
*/
push: (path: string, opt?: PushOptions) => void
/**
* This object contains all the query params of the current route
*/
query: Record<string, any>
/**
* Returns the current pathname
*/
pathname: string
}
export const useRouter = (): UseRouterHook => {
const [location, updateLocation] = useRouterStore((st) => [
st.location,
st.updateLocation,
])
const push = (path: string, opt?: PushOptions): void => {
const { scroll = true } = opt || {}
const url = new URL(path, window.location.origin)
updateLocation({
href: url.href,
pathname: url.pathname,
search: Object.fromEntries(url.searchParams),
searchStr: url.search,
hash: url.hash,
})
history.pushState(path, '', path)
if (scroll) {
window.scroll(0, 0)
}
}
return {
push,
query: location.search,
pathname: location.pathname,
}
}
@@ -1,71 +0,0 @@
import { create } from 'zustand'
import { useLayoutEffect } from 'react'
import type { ServerProps } from '../types'
export interface ParsedLocation {
href: string
pathname: string
search: Record<string, string>
searchStr: string
hash: string
}
interface RouterState {
isLoading: boolean
isTransitioning: boolean
status: 'idle'
location: ParsedLocation
matches: Array<string>
pendingMatches: Array<string>
cachedMatches: Array<string>
statusCode: 200
updateLocation: (loc: ParsedLocation) => void
}
export const initRouterStore = (props?: ServerProps): void => {
const updateLocation = useRouterStore((st) => st.updateLocation)
// Init the store in the server in order to correctly
// SSR the components that depend on the router.
if (typeof window === 'undefined') {
updateLocation({
pathname: props?.router.pathname || '',
hash: '',
href: props?.router.href || '',
searchStr: props?.router.searchStr || '',
search: {},
})
}
// Update the store on the client side before the first
// rendering
useLayoutEffect(() => {
const { pathname, hash, href, search } = window.location
updateLocation({
pathname,
hash,
href,
searchStr: search,
search: Object.fromEntries(new URLSearchParams(search)),
})
}, [])
}
export const useRouterStore = create<RouterState>()((set) => ({
isLoading: false,
isTransitioning: false,
status: 'idle',
location: {
href: '',
pathname: '',
search: {},
searchStr: '',
hash: '',
},
matches: [],
pendingMatches: [],
cachedMatches: [],
statusCode: 200,
updateLocation: (location: ParsedLocation): void => set({ location }),
}))
@@ -1,14 +1,14 @@
{
"name": "tuono-fs-router-vite-plugin",
"version": "0.16.2",
"version": "0.17.0",
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
"homepage": "https://tuono.dev",
"scripts": {
"dev": "vite build --watch",
"build": "vite build",
"lint": "eslint .",
"format": "prettier -u --write --ignore-unknown '**/*'",
"format:check": "prettier --check --ignore-unknown '**/*'",
"format": "prettier --write --ignore-unknown --ignore-path ../../.prettierignore .",
"format:check": "prettier --check --ignore-unknown --ignore-path ../../.prettierignore .",
"types": "tsc --noEmit",
"test:watch": "vitest",
"test": "vitest run"
@@ -16,7 +16,7 @@
"repository": {
"type": "git",
"url": "git+https://github.com/tuono-labs/tuono.git",
"directory": "packages/fs-router-vite-plugin"
"directory": "packages/tuono-fs-router-vite-plugin"
},
"keywords": [],
"author": "Valerio Ageno",
@@ -50,8 +50,8 @@
"vite": "^5.2.11"
},
"devDependencies": {
"@tanstack/config": "^0.7.11",
"@tanstack/config": "0.7.13",
"@types/babel__core": "^7.20.5",
"vitest": "^2.0.0"
"vitest": "2.1.8"
}
}
@@ -6,16 +6,16 @@ const routes = [
{
filePath: 'posts/my-post.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/my-post.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/my-post.tsx',
routePath: '/posts/my-post',
variableName: 'PostsMyPost',
parent: {
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/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,
},
@@ -25,16 +25,16 @@ const routes = [
{
filePath: 'posts/index.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/index.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/index.tsx',
routePath: '/posts/',
variableName: 'PostsIndex',
parent: {
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/home/valerio/Documents/tuono/packages/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,
},
@@ -44,16 +44,16 @@ const routes = [
{
filePath: 'posts/[post].tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/index.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/index.tsx',
routePath: '/posts/',
variableName: 'PostspostIndex',
parent: {
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/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,
},
@@ -3,7 +3,7 @@ import type { RouteNode } from './types'
export function buildRouteConfig(nodes: Array<RouteNode>, depth = 1): string {
const children = nodes.map((node) => {
const route = `${node.variableName}Route`
const route = `${node.variableName as string}Route`
if (node.children?.length) {
const childConfigs = buildRouteConfig(node.children, depth + 1)
@@ -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,17 +158,17 @@ 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): any => d,
(d): RouteNode => d,
])
const imports = [
...sortedRouteNodes.map((node) => {
const extension = node.filePath.endsWith('mdx') ? '.mdx' : ''
return `const ${
node.variableName
node.variableName as string
}Import = dynamic(() => import('./${replaceBackslash(
removeExt(
path.relative(
@@ -179,20 +183,23 @@ 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
return `const ${node.variableName} = createRoute({ component: ${node.variableName}Import${rootDeclaration} })`
return `const ${variableName} = createRoute({ component: ${variableName}Import${rootDeclaration} })`
}),
].join('\n')
const createRouteUpdates = [
sortedRouteNodes
.map((node) => {
const variableName = node.variableName as string
const cleanedPath = node.cleanedPath as string
return [
`const ${node.variableName}Route = ${node.variableName}.update({
`const ${variableName}Route = ${variableName}.update({
${[
!node.path?.endsWith(ROOT_PATH_ID) && `path: '${node.cleanedPath}'`,
!node.path?.endsWith(LAYOUT_PATH_ID) && `path: '${cleanedPath}'`,
`getParentRoute: () => ${node.parent?.variableName ?? 'root'}Route`,
rustHandlersNodes.includes(node.path || '')
? 'hasHandler: true'
@@ -210,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,
@@ -236,7 +243,7 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
const routeTreeContent = await fsp
.readFile(path.resolve(config.generatedRouteTree), 'utf-8')
.catch((e) => {
.catch((e: unknown) => {
const err = e as Error & { code?: string }
if (err.code === 'ENOENT') {
return undefined
@@ -6,25 +6,25 @@ const routes = [
{
filePath: 'posts/[post].tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/[post].tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/[post].tsx',
routePath: '/posts/[post]',
variableName: 'Postspost',
path: '/posts/[post]',
cleanedPath: '/posts/[post]',
},
{
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/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',
},
{
filePath: 'index.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root/routes/index.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/index.tsx',
routePath: '/',
variableName: 'Index',
path: '/',
@@ -33,7 +33,7 @@ const routes = [
{
filePath: 'posts/my-post.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/my-post.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/my-post.tsx',
routePath: '/posts/my-post',
variableName: 'PostsMyPost',
path: '/posts/my-post',
@@ -42,19 +42,19 @@ const routes = [
]
const parent = {
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/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',
}
const myPost = {
filePath: 'posts/my-post.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/my-post.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/my-post.tsx',
routePath: '/posts/my-post',
variableName: 'PostsMyPost',
path: '/posts/my-post',
@@ -64,7 +64,7 @@ const myPost = {
const dynamicRoute = {
filePath: 'posts/[post].tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/[post].tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/[post].tsx',
routePath: '/posts/[post]',
variableName: 'Postspost',
path: '/posts/[post]',
@@ -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
}
@@ -8,7 +8,7 @@ const ROUTES_DIRECTORY_PATH = './src/routes'
let lock = false
export default function RouterGenerator(): Plugin {
export function TuonoFsRouterPlugin(): Plugin {
const generate = async (): Promise<void> => {
if (lock) return
lock = true
@@ -6,7 +6,7 @@ const routes = [
{
filePath: 'index.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/index.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/index.tsx',
routePath: '/',
variableName: 'Index',
path: '/',
@@ -15,45 +15,45 @@ const routes = [
{
filePath: 'about.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/about.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/about.tsx',
routePath: '/about',
variableName: 'About',
path: '/about',
cleanedPath: '/about',
},
{
filePath: '__root.tsx',
filePath: '__layout.tsx',
fullPath:
'/tuono/packages/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',
},
{
filePath: 'posts/[post].tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/[post].tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/[post].tsx',
routePath: '/posts/[post]',
variableName: 'Postspost',
},
{
filePath: 'posts/my-post.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/my-post.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/my-post.tsx',
routePath: '/posts/my-post',
variableName: 'PostsMyPost',
},
{
filePath: 'posts/index.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/index.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/index.tsx',
routePath: '/posts/',
variableName: 'PostsIndex',
},
{
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/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',
},
]
@@ -62,7 +62,7 @@ const expectedSorting = [
{
filePath: 'index.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/index.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/index.tsx',
routePath: '/',
variableName: 'Index',
path: '/',
@@ -71,37 +71,37 @@ const expectedSorting = [
{
filePath: 'about.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/about.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/about.tsx',
routePath: '/about',
variableName: 'About',
path: '/about',
cleanedPath: '/about',
},
{
filePath: 'posts/__root.tsx',
filePath: 'posts/__layout.tsx',
fullPath:
'/tuono/packages/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',
},
{
filePath: 'posts/my-post.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/my-post.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/my-post.tsx',
routePath: '/posts/my-post',
variableName: 'PostsMyPost',
},
{
filePath: 'posts/index.tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/index.tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/index.tsx',
routePath: '/posts/',
variableName: 'PostsIndex',
},
{
filePath: 'posts/[post].tsx',
fullPath:
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/[post].tsx',
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/[post].tsx',
routePath: '/posts/[post]',
variableName: 'Postspost',
},
@@ -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 || ''))
@@ -5,7 +5,7 @@ import { describe, it, expect } from 'vitest'
import { routeGenerator } from '../src/generator'
describe('generator works', async () => {
const folderNames = await fs.readdir(process.cwd() + '/tests/generator')
const folderNames = await fs.readdir(`${process.cwd()}/tests/generator`)
it.each(folderNames)(
'should wire-up the routes for a "%s" tree',
@@ -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 })

Some files were not shown because too many files have changed in this diff Show More