mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-27 13:52:47 -07:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ef1252e1f | |||
| cf56b3a53e | |||
| 592f402ffd | |||
| 1dc77fc473 | |||
| 09cd772d74 | |||
| 1c69e10e6a | |||
| 1af18e4c91 | |||
| a3709609c5 | |||
| 70a79faee0 | |||
| 07cb7dae9d | |||
| 9a1fb9c4a1 | |||
| a3f7ed7f61 | |||
| 5302fd2779 | |||
| ccd4e02b6a | |||
| f6593097b6 | |||
| 230cd22bff | |||
| f4367f1755 | |||
| 7b7447f9a0 | |||
| d12d8404ec | |||
| d1b12173d5 | |||
| f3bf13da59 | |||
| f236b75f55 | |||
| 2434231f9b | |||
| b8b39e69a1 | |||
| f8158dfd8e | |||
| f1ab7df5ce | |||
| 986de49052 | |||
| d09ca5548a | |||
| 77e22c40b6 | |||
| de74647c43 | |||
| 498601c21a | |||
| e99f06ddf5 | |||
| f06af59168 | |||
| d50e3a7fc0 | |||
| 3e4e7ff1ab | |||
| 21ff3c8655 | |||
| 166e61bab4 | |||
| 93cd8bd8da | |||
| f5c1641af2 | |||
| 62ffed0624 | |||
| 8d0a4a7417 | |||
| b1b274ea12 | |||
| fc38865965 | |||
| 911adaaecb | |||
| 7626ed8d1d | |||
| 6135fd7e1b | |||
| 14bea36356 |
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,8 @@ dist/
|
||||
|
||||
examples/*/pnpm-lock.yaml
|
||||
|
||||
vite.config.ts.timestamp*
|
||||
|
||||
## Rust related ignores
|
||||
|
||||
# Generated by Cargo
|
||||
|
||||
+8
-1
@@ -1 +1,8 @@
|
||||
pnpm-lock.yaml
|
||||
pnpm-lock.yaml
|
||||
|
||||
dist
|
||||
.tuono
|
||||
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
packages/tuono-lazy-fn-vite-plugin/tests/sources/*
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
* @Valerioageno @marcalexiei
|
||||
|
||||
# Rust
|
||||
/crates/ @Valerioageno
|
||||
/Cargo.toml @Valerioageno
|
||||
|
||||
# Misc
|
||||
/.github/ @marcalexiei
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -52,7 +52,7 @@ impl AxumInfo {
|
||||
.replace('/', "_")
|
||||
.replace('-', "_hyphen_")
|
||||
.replace('[', "dyn_")
|
||||
.replace("...", "_catch_all_")
|
||||
.replace("...", "catch_all_")
|
||||
.replace(']', ""),
|
||||
axum_route: axum_route
|
||||
.replace("[...", "*")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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))"#));
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -20,6 +20,5 @@
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"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"]
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -20,6 +20,5 @@
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"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"]
|
||||
}
|
||||
+9
-14
@@ -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
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
@@ -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 }),
|
||||
}))
|
||||
+6
-6
@@ -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"
|
||||
}
|
||||
}
|
||||
+15
-15
@@ -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,
|
||||
},
|
||||
+1
-1
@@ -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
-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'
|
||||
+19
-12
@@ -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
|
||||
+13
-13
@@ -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]',
|
||||
+4
-4
@@ -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
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
+19
-19
@@ -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',
|
||||
},
|
||||
+2
-2
@@ -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 || ''))
|
||||
+1
-1
@@ -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
-2
@@ -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
-2
@@ -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 })
|
||||
+9
-9
@@ -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
-2
@@ -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
Reference in New Issue
Block a user