mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-29 13:52:46 -07:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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.
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: input
|
- type: textarea
|
||||||
id: tuono-version
|
id: system-info
|
||||||
attributes:
|
attributes:
|
||||||
label: Tuono version
|
label: System Info
|
||||||
placeholder: '[e.g. 0.4.0]'
|
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
|
||||||
|
render: shell
|
||||||
|
placeholder: System, Binaries, Browsers
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: input
|
- type: textarea
|
||||||
id: os-version
|
id: system-info-rust
|
||||||
attributes:
|
attributes:
|
||||||
label: OS
|
label: System info (Rust)
|
||||||
placeholder: '[e.g. MacOS, Windows]'
|
description: Output of `rustc --version && cargo --version && tuono --version`
|
||||||
|
render: shell
|
||||||
|
placeholder: 'rustc, cargo and tuono binary version'
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- 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
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: additional-context
|
id: additional-context
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
- name: Install tuono
|
- name: Install tuono
|
||||||
run: cargo install tuono@0.15.0
|
run: cargo install tuono@0.16.4
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
working-directory: ./apps/documentation
|
working-directory: ./apps/documentation
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
- name: Install tuono
|
- name: Install tuono
|
||||||
run: cargo install tuono@0.16.0
|
run: cargo install tuono@0.16.4
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
working-directory: ./apps/documentation
|
working-directory: ./apps/documentation
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ dist/
|
|||||||
|
|
||||||
examples/*/pnpm-lock.yaml
|
examples/*/pnpm-lock.yaml
|
||||||
|
|
||||||
|
vite.config.ts.timestamp*
|
||||||
|
|
||||||
## Rust related ignores
|
## Rust related ignores
|
||||||
|
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
|
|||||||
+6
-1
@@ -1 +1,6 @@
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
dist
|
||||||
|
.tuono
|
||||||
|
|
||||||
|
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
|
## 📖 Documentation
|
||||||
|
|
||||||
The [documentation](https://tuono.dev/documentation) is available on
|
The [documentation](https://tuono.dev/) is available on
|
||||||
[tuono.dev](https://tuono.dev/).
|
[tuono.dev](https://tuono.dev/).
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ name = "tuono"
|
|||||||
path = ".tuono/main.rs"
|
path = ".tuono/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tuono_lib = "0.15.0"
|
tuono_lib = "0.16.4"
|
||||||
glob = "0.3.1"
|
glob = "0.3.1"
|
||||||
time = { version = "0.3", features = ["macros"] }
|
time = { version = "0.3", features = ["macros"] }
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"format": "prettier -u --write --ignore-unknown .",
|
"format": "prettier --write --ignore-unknown .",
|
||||||
"format:check": "prettier --check --ignore-unknown .",
|
"format:check": "prettier --check --ignore-unknown .",
|
||||||
"types": "tsc --noEmit"
|
"types": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"clsx": "2.1.1",
|
"clsx": "2.1.1",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"tuono": "npm:tuono@0.16.0"
|
"tuono": "npm:tuono@0.16.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mdx-js/rollup": "3.1.0",
|
"@mdx-js/rollup": "3.1.0",
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ export default function MdxTitle(props: TitleProps): JSX.Element {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const h =
|
export const h = (
|
||||||
(order: 1 | 2 | 3 | 4 | 5 | 6) =>
|
order: 1 | 2 | 3 | 4 | 5 | 6,
|
||||||
(props: TitleProps): JSX.Element => <MdxTitle order={order} {...props} />
|
): 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',
|
label: 'Defining routes',
|
||||||
href: '/documentation/routing/defining-routes',
|
href: '/documentation/routing/defining-routes',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Dynamic routes',
|
||||||
|
href: '/documentation/routing/dynamic-routes',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'element',
|
type: 'element',
|
||||||
label: 'Link and navigation',
|
label: 'Link and navigation',
|
||||||
@@ -126,11 +131,6 @@ export const sidebarElements: Array<SidebarElement> = [
|
|||||||
label: 'Loading state',
|
label: 'Loading state',
|
||||||
href: '/documentation/routing/loading-state',
|
href: '/documentation/routing/loading-state',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'element',
|
|
||||||
label: 'Dynamic routes',
|
|
||||||
href: '/documentation/routing/dynamic-routes',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'element',
|
type: 'element',
|
||||||
label: 'Layouts',
|
label: 'Layouts',
|
||||||
|
|||||||
@@ -50,26 +50,30 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
|
|||||||
<AppShell.Section component={ScrollArea}>
|
<AppShell.Section component={ScrollArea}>
|
||||||
{sidebarElements.map((el, i) => {
|
{sidebarElements.map((el, i) => {
|
||||||
if (el.type === 'divider') {
|
if (el.type === 'divider') {
|
||||||
return <Divider my="md" mx={10} />
|
return <Divider key={`${el.type}-${i}`} my="md" mx={10} />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.type === 'title') {
|
if (el.type === 'title') {
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
|
key={`${el.type}-${i}`}
|
||||||
size="xs"
|
size="xs"
|
||||||
fw={700}
|
fw={700}
|
||||||
fz={12}
|
fz={12}
|
||||||
pl={12}
|
pl={12}
|
||||||
py={5}
|
py={5}
|
||||||
children={el.label}
|
>
|
||||||
/>
|
{el.label}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.children?.length) {
|
if (el.children?.length) {
|
||||||
return (
|
return (
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
|
key={`${el.type}-${i}`}
|
||||||
href={el.href}
|
href={el.href}
|
||||||
label={el.label}
|
label={el.label}
|
||||||
key={i}
|
|
||||||
leftSection={el.leftIcon}
|
leftSection={el.leftIcon}
|
||||||
>
|
>
|
||||||
{el.children.map((child, index) => (
|
{el.children.map((child, index) => (
|
||||||
@@ -83,12 +87,13 @@ function SidebarElements({ close }: SidebarProps): JSX.Element {
|
|||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
|
key={`${el.type}-${i}`}
|
||||||
href={el.href}
|
href={el.href}
|
||||||
label={el.label}
|
label={el.label}
|
||||||
onClick={close}
|
onClick={close}
|
||||||
key={i}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ export function TableOfContents({
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
window.addEventListener('scroll', handleScroll)
|
window.addEventListener('scroll', handleScroll)
|
||||||
return (): void => window.removeEventListener('scroll', handleScroll)
|
return (): void => {
|
||||||
|
window.removeEventListener('scroll', handleScroll)
|
||||||
|
}
|
||||||
}, [router.pathname])
|
}, [router.pathname])
|
||||||
|
|
||||||
if (filteredHeadings.length === 0) {
|
if (filteredHeadings.length === 0) {
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ export default function ThemeBtn(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
||||||
}
|
}}
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
aria-label="Toggle color scheme"
|
aria-label="Toggle color scheme"
|
||||||
|
|||||||
@@ -9,6 +9,53 @@ import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
|||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Dynamic routes' }]} />
|
<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.
|
||||||
|
|||||||
@@ -32,6 +32,5 @@
|
|||||||
// Completeness
|
// Completeness
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": ["src", "tuono.config.ts", "postcss.config.js"],
|
"include": ["src", "tuono.config.ts", "postcss.config.js"]
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
'@': 'src',
|
||||||
'@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs',
|
'@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs',
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
|
||||||
exclude: ['@mdx-js/react'],
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
{ enforce: 'pre', ...mdx({ providerImportSource: '@mdx-js/react' }) },
|
{ enforce: 'pre', ...mdx({ providerImportSource: '@mdx-js/react' }) },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tuono"
|
name = "tuono"
|
||||||
version = "0.16.2"
|
version = "0.16.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||||
description = "Superfast React fullstack framework"
|
description = "Superfast React fullstack framework"
|
||||||
|
|||||||
@@ -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;
|
mod app;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
mod mode;
|
mod mode;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ impl AxumInfo {
|
|||||||
.replace('/', "_")
|
.replace('/', "_")
|
||||||
.replace('-', "_hyphen_")
|
.replace('-', "_hyphen_")
|
||||||
.replace('[', "dyn_")
|
.replace('[', "dyn_")
|
||||||
.replace("...", "_catch_all_")
|
.replace("...", "catch_all_")
|
||||||
.replace(']', ""),
|
.replace(']', ""),
|
||||||
axum_route: axum_route
|
axum_route: axum_route
|
||||||
.replace("[...", "*")
|
.replace("[...", "*")
|
||||||
|
|||||||
@@ -84,11 +84,11 @@ fn create_routes_declaration(routes: &HashMap<String, Route>) -> String {
|
|||||||
|
|
||||||
if !route.is_api() {
|
if !route.is_api() {
|
||||||
route_declarations.push_str(&format!(
|
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!(
|
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 {
|
} else {
|
||||||
for method in route.api_data.as_ref().unwrap().methods.clone() {
|
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("mod index;"));
|
||||||
|
|
||||||
assert!(temp_main_rs_content
|
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]
|
#[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.");
|
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(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(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(
|
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
|
assert!(temp_main_rs_content
|
||||||
.contains(r#".route("/*all_routes", get(dyn__catch_all_all_routes::route))"#));
|
.contains(r#".route("/__tuono/data/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__api))"#));
|
||||||
|
|
||||||
assert!(temp_main_rs_content
|
|
||||||
.contains(r#".route("/__tuono/data/*all_routes", get(dyn__catch_all_all_routes::api))"#));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tuono_lib"
|
name = "tuono_lib"
|
||||||
version = "0.16.2"
|
version = "0.16.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||||
description = "Superfast React fullstack framework"
|
description = "Superfast React fullstack framework"
|
||||||
@@ -32,7 +32,7 @@ either = "1.13.0"
|
|||||||
tower-http = {version = "0.6.0", features = ["fs"]}
|
tower-http = {version = "0.6.0", features = ["fs"]}
|
||||||
colored = "2.1.0"
|
colored = "2.1.0"
|
||||||
|
|
||||||
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.16.2"}
|
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.16.7"}
|
||||||
# Match the same version used by axum
|
# Match the same version used by axum
|
||||||
tokio-tungstenite = "0.24.0"
|
tokio-tungstenite = "0.24.0"
|
||||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
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 catch_all;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod manifest;
|
mod manifest;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tuono_lib_macros"
|
name = "tuono_lib_macros"
|
||||||
version = "0.16.2"
|
version = "0.16.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Superfast React fullstack framework"
|
description = "Superfast React fullstack framework"
|
||||||
homepage = "https://tuono.dev"
|
homepage = "https://tuono.dev"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ pub fn handler_core(_args: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
|
|
||||||
#item
|
#item
|
||||||
|
|
||||||
pub async fn route(
|
pub async fn tuono__internal__route(
|
||||||
#axum_arguments
|
#axum_arguments
|
||||||
) -> impl tuono_lib::axum::response::IntoResponse {
|
) -> 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)
|
#fn_name(req.clone(), #argument_names).await.render_to_string(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn api(
|
pub async fn tuono__internal__api(
|
||||||
#axum_arguments
|
#axum_arguments
|
||||||
) -> impl tuono_lib::axum::response::IntoResponse {
|
) -> 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;
|
extern crate proc_macro;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
|
|||||||
+45
-13
@@ -2,6 +2,9 @@ import eslint from '@eslint/js'
|
|||||||
import tseslint from 'typescript-eslint'
|
import tseslint from 'typescript-eslint'
|
||||||
// @ts-expect-error no types are available for this plugin
|
// @ts-expect-error no types are available for this plugin
|
||||||
import eslintPluginImport from 'eslint-plugin-import'
|
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(
|
export default tseslint.config(
|
||||||
{
|
{
|
||||||
@@ -13,7 +16,8 @@ export default tseslint.config(
|
|||||||
// #endregion shared
|
// #endregion shared
|
||||||
|
|
||||||
// #region package-specific
|
// #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/**',
|
'packages/tuono/bin/**',
|
||||||
// #endregion package-specific
|
// #endregion package-specific
|
||||||
@@ -24,10 +28,34 @@ export default tseslint.config(
|
|||||||
reportUnusedDisableDirectives: 'error',
|
reportUnusedDisableDirectives: 'error',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access */
|
||||||
eslintPluginImport.flatConfigs.recommended,
|
eslintPluginImport.flatConfigs.recommended,
|
||||||
eslintPluginImport.flatConfigs.typescript,
|
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: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
@@ -49,16 +77,13 @@ export default tseslint.config(
|
|||||||
rules: {
|
rules: {
|
||||||
// #region @typescript-eslint
|
// #region @typescript-eslint
|
||||||
'@typescript-eslint/array-type': ['error', { default: 'generic' }],
|
'@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-definitions': 'error',
|
||||||
'@typescript-eslint/consistent-type-imports': [
|
'@typescript-eslint/consistent-type-imports': [
|
||||||
'error',
|
'error',
|
||||||
{ prefer: 'type-imports' },
|
{ prefer: 'type-imports' },
|
||||||
],
|
],
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||||
'@typescript-eslint/method-signature-style': ['error', 'property'],
|
'@typescript-eslint/method-signature-style': ['error', 'property'],
|
||||||
'@typescript-eslint/naming-convention': [
|
'@typescript-eslint/naming-convention': [
|
||||||
'error',
|
'error',
|
||||||
@@ -73,19 +98,24 @@ export default tseslint.config(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'@typescript-eslint/no-deprecated': 'error',
|
|
||||||
'@typescript-eslint/no-empty-function': 'error',
|
'@typescript-eslint/no-empty-function': 'error',
|
||||||
'@typescript-eslint/no-empty-interface': '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': [
|
'@typescript-eslint/no-inferrable-types': [
|
||||||
'error',
|
'error',
|
||||||
{ ignoreParameters: true },
|
{ ignoreParameters: true },
|
||||||
],
|
],
|
||||||
|
'@typescript-eslint/restrict-template-expressions': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allowAny: false,
|
||||||
|
allowBoolean: false,
|
||||||
|
allowNever: false,
|
||||||
|
allowNullish: false,
|
||||||
|
allowNumber: true,
|
||||||
|
allowRegExp: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
// #endregion @typescript-eslint
|
// #endregion @typescript-eslint
|
||||||
|
|
||||||
// #region import
|
// #region import
|
||||||
@@ -126,8 +156,10 @@ export default tseslint.config(
|
|||||||
'no-shadow': 'error',
|
'no-shadow': 'error',
|
||||||
'no-undef': 'off',
|
'no-undef': 'off',
|
||||||
'sort-imports': 'off',
|
'sort-imports': 'off',
|
||||||
|
// #endregion misc
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
files: ['apps/documentation/**'],
|
files: ['apps/documentation/**'],
|
||||||
settings: {
|
settings: {
|
||||||
|
|||||||
@@ -20,6 +20,5 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["src", "tuono.config.ts"],
|
"include": ["src", "tuono.config.ts"]
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,5 @@
|
|||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src", "tuono.config.ts"],
|
"include": ["src", "tuono.config.ts"]
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,5 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["src", "tuono.config.ts"],
|
"include": ["src", "tuono.config.ts"]
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.15.0",
|
"@eslint/js": "9.15.0",
|
||||||
"@tanstack/config": "^0.7.0",
|
"@types/node": "22.10.0",
|
||||||
"@types/node": "^22.0.0",
|
"eslint": "9.15.0",
|
||||||
"@types/react": "^18.3.13",
|
"eslint-import-resolver-typescript": "3.6.3",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint": "^9.15.0",
|
"eslint-plugin-react": "7.37.2",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-plugin-react-hooks": "5.1.0",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"prettier": "3.4.1",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"turbo": "2.2.3",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
|
||||||
"prettier": "^3.2.4",
|
|
||||||
"turbo": "^2.2.3",
|
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"typescript-eslint": "8.15.0",
|
"typescript-eslint": "8.15.0"
|
||||||
"vite": "^5.2.11",
|
|
||||||
"vitest": "^2.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tuono-fs-router-vite-plugin",
|
"name": "tuono-fs-router-vite-plugin",
|
||||||
"version": "0.16.2",
|
"version": "0.16.7",
|
||||||
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
|
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
|
||||||
"homepage": "https://tuono.dev",
|
"homepage": "https://tuono.dev",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/tuono-labs/tuono.git",
|
"url": "git+https://github.com/tuono-labs/tuono.git",
|
||||||
"directory": "packages/fs-router-vite-plugin"
|
"directory": "packages/tuono-fs-router-vite-plugin"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Valerio Ageno",
|
"author": "Valerio Ageno",
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
"vite": "^5.2.11"
|
"vite": "^5.2.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tanstack/config": "^0.7.11",
|
"@tanstack/config": "0.7.13",
|
||||||
"@types/babel__core": "^7.20.5",
|
"@types/babel__core": "^7.20.5",
|
||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
}
|
}
|
||||||
+6
-6
@@ -6,13 +6,13 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: 'posts/my-post.tsx',
|
filePath: 'posts/my-post.tsx',
|
||||||
fullPath:
|
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',
|
routePath: '/posts/my-post',
|
||||||
variableName: 'PostsMyPost',
|
variableName: 'PostsMyPost',
|
||||||
parent: {
|
parent: {
|
||||||
filePath: 'posts/__root.tsx',
|
filePath: 'posts/__root.tsx',
|
||||||
fullPath:
|
fullPath:
|
||||||
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
||||||
routePath: '/posts/__root',
|
routePath: '/posts/__root',
|
||||||
variableName: 'Postsroot',
|
variableName: 'Postsroot',
|
||||||
path: '/posts/__root',
|
path: '/posts/__root',
|
||||||
@@ -25,13 +25,13 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: 'posts/index.tsx',
|
filePath: 'posts/index.tsx',
|
||||||
fullPath:
|
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/',
|
routePath: '/posts/',
|
||||||
variableName: 'PostsIndex',
|
variableName: 'PostsIndex',
|
||||||
parent: {
|
parent: {
|
||||||
filePath: 'posts/__root.tsx',
|
filePath: 'posts/__root.tsx',
|
||||||
fullPath:
|
fullPath:
|
||||||
'/home/valerio/Documents/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
'/home/valerio/Documents/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
||||||
routePath: '/posts/__root',
|
routePath: '/posts/__root',
|
||||||
variableName: 'Postsroot',
|
variableName: 'Postsroot',
|
||||||
path: '/posts/__root',
|
path: '/posts/__root',
|
||||||
@@ -44,13 +44,13 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: 'posts/[post].tsx',
|
filePath: 'posts/[post].tsx',
|
||||||
fullPath:
|
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/',
|
routePath: '/posts/',
|
||||||
variableName: 'PostspostIndex',
|
variableName: 'PostspostIndex',
|
||||||
parent: {
|
parent: {
|
||||||
filePath: 'posts/__root.tsx',
|
filePath: 'posts/__root.tsx',
|
||||||
fullPath:
|
fullPath:
|
||||||
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
||||||
routePath: '/posts/__root',
|
routePath: '/posts/__root',
|
||||||
variableName: 'Postsroot',
|
variableName: 'Postsroot',
|
||||||
path: '/posts/__root',
|
path: '/posts/__root',
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import type { RouteNode } from './types'
|
|||||||
|
|
||||||
export function buildRouteConfig(nodes: Array<RouteNode>, depth = 1): string {
|
export function buildRouteConfig(nodes: Array<RouteNode>, depth = 1): string {
|
||||||
const children = nodes.map((node) => {
|
const children = nodes.map((node) => {
|
||||||
const route = `${node.variableName}Route`
|
const route = `${node.variableName as string}Route`
|
||||||
|
|
||||||
if (node.children?.length) {
|
if (node.children?.length) {
|
||||||
const childConfigs = buildRouteConfig(node.children, depth + 1)
|
const childConfigs = buildRouteConfig(node.children, depth + 1)
|
||||||
+9
-6
@@ -157,14 +157,14 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
|
|||||||
(d): number => (d.routePath.includes(`/${ROOT_PATH_ID}`) ? -1 : 1),
|
(d): number => (d.routePath.includes(`/${ROOT_PATH_ID}`) ? -1 : 1),
|
||||||
(d): number => d.routePath.split('/').length,
|
(d): number => d.routePath.split('/').length,
|
||||||
(d): number => (d.routePath.endsWith("index'") ? -1 : 1),
|
(d): number => (d.routePath.endsWith("index'") ? -1 : 1),
|
||||||
(d): any => d,
|
(d): RouteNode => d,
|
||||||
])
|
])
|
||||||
|
|
||||||
const imports = [
|
const imports = [
|
||||||
...sortedRouteNodes.map((node) => {
|
...sortedRouteNodes.map((node) => {
|
||||||
const extension = node.filePath.endsWith('mdx') ? '.mdx' : ''
|
const extension = node.filePath.endsWith('mdx') ? '.mdx' : ''
|
||||||
return `const ${
|
return `const ${
|
||||||
node.variableName
|
node.variableName as string
|
||||||
}Import = dynamic(() => import('./${replaceBackslash(
|
}Import = dynamic(() => import('./${replaceBackslash(
|
||||||
removeExt(
|
removeExt(
|
||||||
path.relative(
|
path.relative(
|
||||||
@@ -181,18 +181,21 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
|
|||||||
...sortedRouteNodes.map((node) => {
|
...sortedRouteNodes.map((node) => {
|
||||||
const isRoot = node.routePath.endsWith(ROOT_PATH_ID)
|
const isRoot = node.routePath.endsWith(ROOT_PATH_ID)
|
||||||
const rootDeclaration = isRoot ? ', isRoot: true' : ''
|
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')
|
].join('\n')
|
||||||
|
|
||||||
const createRouteUpdates = [
|
const createRouteUpdates = [
|
||||||
sortedRouteNodes
|
sortedRouteNodes
|
||||||
.map((node) => {
|
.map((node) => {
|
||||||
|
const variableName = node.variableName as string
|
||||||
|
const cleanedPath = node.cleanedPath as string
|
||||||
return [
|
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(ROOT_PATH_ID) && `path: '${cleanedPath}'`,
|
||||||
`getParentRoute: () => ${node.parent?.variableName ?? 'root'}Route`,
|
`getParentRoute: () => ${node.parent?.variableName ?? 'root'}Route`,
|
||||||
rustHandlersNodes.includes(node.path || '')
|
rustHandlersNodes.includes(node.path || '')
|
||||||
? 'hasHandler: true'
|
? 'hasHandler: true'
|
||||||
@@ -236,7 +239,7 @@ export async function routeGenerator(config = defaultConfig): Promise<void> {
|
|||||||
|
|
||||||
const routeTreeContent = await fsp
|
const routeTreeContent = await fsp
|
||||||
.readFile(path.resolve(config.generatedRouteTree), 'utf-8')
|
.readFile(path.resolve(config.generatedRouteTree), 'utf-8')
|
||||||
.catch((e) => {
|
.catch((e: unknown) => {
|
||||||
const err = e as Error & { code?: string }
|
const err = e as Error & { code?: string }
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
return undefined
|
return undefined
|
||||||
+7
-7
@@ -6,7 +6,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: 'posts/[post].tsx',
|
filePath: 'posts/[post].tsx',
|
||||||
fullPath:
|
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]',
|
routePath: '/posts/[post]',
|
||||||
variableName: 'Postspost',
|
variableName: 'Postspost',
|
||||||
path: '/posts/[post]',
|
path: '/posts/[post]',
|
||||||
@@ -15,7 +15,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: 'posts/__root.tsx',
|
filePath: 'posts/__root.tsx',
|
||||||
fullPath:
|
fullPath:
|
||||||
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/__root.tsx',
|
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/__root.tsx',
|
||||||
routePath: '/posts/__root',
|
routePath: '/posts/__root',
|
||||||
variableName: 'Postsroot',
|
variableName: 'Postsroot',
|
||||||
path: '/posts/__root',
|
path: '/posts/__root',
|
||||||
@@ -24,7 +24,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: 'index.tsx',
|
filePath: 'index.tsx',
|
||||||
fullPath:
|
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: '/',
|
routePath: '/',
|
||||||
variableName: 'Index',
|
variableName: 'Index',
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -33,7 +33,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: 'posts/my-post.tsx',
|
filePath: 'posts/my-post.tsx',
|
||||||
fullPath:
|
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',
|
routePath: '/posts/my-post',
|
||||||
variableName: 'PostsMyPost',
|
variableName: 'PostsMyPost',
|
||||||
path: '/posts/my-post',
|
path: '/posts/my-post',
|
||||||
@@ -44,7 +44,7 @@ const routes = [
|
|||||||
const parent = {
|
const parent = {
|
||||||
filePath: 'posts/__root.tsx',
|
filePath: 'posts/__root.tsx',
|
||||||
fullPath:
|
fullPath:
|
||||||
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/__root.tsx',
|
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root/routes/posts/__root.tsx',
|
||||||
routePath: '/posts/__root',
|
routePath: '/posts/__root',
|
||||||
variableName: 'Postsroot',
|
variableName: 'Postsroot',
|
||||||
path: '/posts/__root',
|
path: '/posts/__root',
|
||||||
@@ -54,7 +54,7 @@ const parent = {
|
|||||||
const myPost = {
|
const myPost = {
|
||||||
filePath: 'posts/my-post.tsx',
|
filePath: 'posts/my-post.tsx',
|
||||||
fullPath:
|
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',
|
routePath: '/posts/my-post',
|
||||||
variableName: 'PostsMyPost',
|
variableName: 'PostsMyPost',
|
||||||
path: '/posts/my-post',
|
path: '/posts/my-post',
|
||||||
@@ -64,7 +64,7 @@ const myPost = {
|
|||||||
const dynamicRoute = {
|
const dynamicRoute = {
|
||||||
filePath: 'posts/[post].tsx',
|
filePath: 'posts/[post].tsx',
|
||||||
fullPath:
|
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]',
|
routePath: '/posts/[post]',
|
||||||
variableName: 'Postspost',
|
variableName: 'Postspost',
|
||||||
path: '/posts/[post]',
|
path: '/posts/[post]',
|
||||||
+13
-13
@@ -6,7 +6,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: 'index.tsx',
|
filePath: 'index.tsx',
|
||||||
fullPath:
|
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: '/',
|
routePath: '/',
|
||||||
variableName: 'Index',
|
variableName: 'Index',
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -15,7 +15,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: 'about.tsx',
|
filePath: 'about.tsx',
|
||||||
fullPath:
|
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',
|
routePath: '/about',
|
||||||
variableName: 'About',
|
variableName: 'About',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
@@ -24,35 +24,35 @@ const routes = [
|
|||||||
{
|
{
|
||||||
filePath: '__root.tsx',
|
filePath: '__root.tsx',
|
||||||
fullPath:
|
fullPath:
|
||||||
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/__root.tsx',
|
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/__root.tsx',
|
||||||
routePath: '/__root',
|
routePath: '/__root',
|
||||||
variableName: 'root',
|
variableName: 'root',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filePath: 'posts/[post].tsx',
|
filePath: 'posts/[post].tsx',
|
||||||
fullPath:
|
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]',
|
routePath: '/posts/[post]',
|
||||||
variableName: 'Postspost',
|
variableName: 'Postspost',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filePath: 'posts/my-post.tsx',
|
filePath: 'posts/my-post.tsx',
|
||||||
fullPath:
|
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',
|
routePath: '/posts/my-post',
|
||||||
variableName: 'PostsMyPost',
|
variableName: 'PostsMyPost',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filePath: 'posts/index.tsx',
|
filePath: 'posts/index.tsx',
|
||||||
fullPath:
|
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/',
|
routePath: '/posts/',
|
||||||
variableName: 'PostsIndex',
|
variableName: 'PostsIndex',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filePath: 'posts/__root.tsx',
|
filePath: 'posts/__root.tsx',
|
||||||
fullPath:
|
fullPath:
|
||||||
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
||||||
routePath: '/posts/__root',
|
routePath: '/posts/__root',
|
||||||
variableName: 'Postsroot',
|
variableName: 'Postsroot',
|
||||||
},
|
},
|
||||||
@@ -62,7 +62,7 @@ const expectedSorting = [
|
|||||||
{
|
{
|
||||||
filePath: 'index.tsx',
|
filePath: 'index.tsx',
|
||||||
fullPath:
|
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: '/',
|
routePath: '/',
|
||||||
variableName: 'Index',
|
variableName: 'Index',
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -71,7 +71,7 @@ const expectedSorting = [
|
|||||||
{
|
{
|
||||||
filePath: 'about.tsx',
|
filePath: 'about.tsx',
|
||||||
fullPath:
|
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',
|
routePath: '/about',
|
||||||
variableName: 'About',
|
variableName: 'About',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
@@ -80,28 +80,28 @@ const expectedSorting = [
|
|||||||
{
|
{
|
||||||
filePath: 'posts/__root.tsx',
|
filePath: 'posts/__root.tsx',
|
||||||
fullPath:
|
fullPath:
|
||||||
'/tuono/packages/fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
'/tuono/packages/tuono-fs-router-vite-plugin/tests/generator/multi-level-root-dynamic/routes/posts/__root.tsx',
|
||||||
routePath: '/posts/__root',
|
routePath: '/posts/__root',
|
||||||
variableName: 'Postsroot',
|
variableName: 'Postsroot',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filePath: 'posts/my-post.tsx',
|
filePath: 'posts/my-post.tsx',
|
||||||
fullPath:
|
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',
|
routePath: '/posts/my-post',
|
||||||
variableName: 'PostsMyPost',
|
variableName: 'PostsMyPost',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filePath: 'posts/index.tsx',
|
filePath: 'posts/index.tsx',
|
||||||
fullPath:
|
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/',
|
routePath: '/posts/',
|
||||||
variableName: 'PostsIndex',
|
variableName: 'PostsIndex',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
filePath: 'posts/[post].tsx',
|
filePath: 'posts/[post].tsx',
|
||||||
fullPath:
|
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]',
|
routePath: '/posts/[post]',
|
||||||
variableName: 'Postspost',
|
variableName: 'Postspost',
|
||||||
},
|
},
|
||||||
+1
-1
@@ -5,7 +5,7 @@ import { describe, it, expect } from 'vitest'
|
|||||||
import { routeGenerator } from '../src/generator'
|
import { routeGenerator } from '../src/generator'
|
||||||
|
|
||||||
describe('generator works', async () => {
|
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)(
|
it.each(folderNames)(
|
||||||
'should wire-up the routes for a "%s" tree',
|
'should wire-up the routes for a "%s" tree',
|
||||||
+5
-5
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "tuono-lazy-fn-vite-plugin",
|
"name": "tuono-lazy-fn-vite-plugin",
|
||||||
"version": "0.16.2",
|
"version": "0.16.7",
|
||||||
"description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework",
|
"description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework",
|
||||||
"homepage": "https://tuono.dev",
|
"homepage": "https://tuono.dev",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch",
|
"dev": "vite build --watch",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"format": "prettier -u --write --ignore-unknown '**/*'",
|
"format": "prettier --write --ignore-unknown --ignore-path ../../.prettierignore .",
|
||||||
"format:check": "prettier --check --ignore-unknown '**/*'",
|
"format:check": "prettier --check --ignore-unknown --ignore-path ../../.prettierignore .",
|
||||||
"types": "tsc --noEmit",
|
"types": "tsc --noEmit",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"test": "vitest run"
|
"test": "vitest run"
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/tuono-labs/tuono.git",
|
"url": "git+https://github.com/tuono-labs/tuono.git",
|
||||||
"directory": "packages/lazy-fn-vite-plugin"
|
"directory": "packages/tuono-lazy-fn-vite-plugin"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Valerio Ageno",
|
"author": "Valerio Ageno",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"vite": "^5.2.11"
|
"vite": "^5.2.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tanstack/config": "^0.7.11",
|
"@tanstack/config": "0.7.13",
|
||||||
"@types/babel__core": "^7.20.5",
|
"@types/babel__core": "^7.20.5",
|
||||||
"prettier": "^3.2.4",
|
"prettier": "^3.2.4",
|
||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0"
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
export const TUONO_DYNAMIC_FN_ID = 'dynamic'
|
export const TUONO_DYNAMIC_FN_ID = 'dynamic'
|
||||||
export const TUONO_LAZY_FN_ID = 'lazyLoadComponent'
|
export const TUONO_LAZY_FN_ID = '__tuono__internal__lazyLoadComponent'
|
||||||
export const TUONO_MAIN_PACKAGE = 'tuono'
|
export const TUONO_MAIN_PACKAGE = 'tuono'
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
import { transformSync } from '@babel/core'
|
||||||
|
import type { PluginItem as BabelPluginItem } from '@babel/core'
|
||||||
|
import * as BabelTypes from '@babel/types'
|
||||||
|
import { createFilter } from 'vite'
|
||||||
|
import type { Plugin as VitePlugin, Rollup, FilterPattern } from 'vite'
|
||||||
|
|
||||||
|
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: BabelPluginItem = {
|
||||||
|
name: 'remove-tuono-lazy-import-plugin',
|
||||||
|
visitor: {
|
||||||
|
ImportDeclaration: (path) => {
|
||||||
|
const importNode = path.node
|
||||||
|
if (importNode.source.value !== TUONO_MAIN_PACKAGE) return
|
||||||
|
|
||||||
|
path.traverse({
|
||||||
|
ImportSpecifier: (importSpecifierPath) => {
|
||||||
|
if (isTuonoDynamicFnImported(importSpecifierPath)) {
|
||||||
|
importSpecifierPath.remove()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// If there are no specifiers left after traverse
|
||||||
|
// remove the import to avoid unwanted side effects
|
||||||
|
if (importNode.specifiers.length === 0) {
|
||||||
|
path.remove()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [CLIENT build]
|
||||||
|
* This plugin replace the `dynamic` function with the `__tuono__internal__lazyLoadComponent` one
|
||||||
|
*/
|
||||||
|
const ReplaceTuonoLazyImport: BabelPluginItem = {
|
||||||
|
name: 'replace-tuono-lazy-import-plugin',
|
||||||
|
visitor: {
|
||||||
|
ImportSpecifier: (path) => {
|
||||||
|
if (
|
||||||
|
BabelTypes.isIdentifier(path.node.imported) &&
|
||||||
|
isTuonoDynamicFnImported(path)
|
||||||
|
) {
|
||||||
|
path.node.imported.name = TUONO_LAZY_FN_ID
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const turnLazyIntoStatic = {
|
||||||
|
VariableDeclaration: (
|
||||||
|
path: babel.NodePath<BabelTypes.VariableDeclaration>,
|
||||||
|
): void => {
|
||||||
|
path.node.declarations.forEach((variableDeclarationNode) => {
|
||||||
|
const init = variableDeclarationNode.init
|
||||||
|
|
||||||
|
if (
|
||||||
|
BabelTypes.isCallExpression(init) &&
|
||||||
|
// ensures that the method call is `TUONO_DYNAMIC_FN_ID`
|
||||||
|
BabelTypes.isIdentifier(init.callee, { name: TUONO_DYNAMIC_FN_ID }) &&
|
||||||
|
// import name must be an identifier
|
||||||
|
BabelTypes.isIdentifier(variableDeclarationNode.id) &&
|
||||||
|
// check that the first function parameter is an arrow function
|
||||||
|
BabelTypes.isArrowFunctionExpression(init.arguments[0])
|
||||||
|
) {
|
||||||
|
const cmpImportFn = init.arguments[0]
|
||||||
|
|
||||||
|
// ensures that the first parameter is a call expression (may be a block statement)
|
||||||
|
if (!BabelTypes.isCallExpression(cmpImportFn.body)) return
|
||||||
|
// ensures that the first parameter is a string literal (the import path)
|
||||||
|
if (!BabelTypes.isStringLiteral(cmpImportFn.body.arguments[0])) return
|
||||||
|
|
||||||
|
const importName = variableDeclarationNode.id.name
|
||||||
|
const importPath = cmpImportFn.body.arguments[0].value
|
||||||
|
|
||||||
|
if (importName && importPath) {
|
||||||
|
const importDeclaration = BabelTypes.importDeclaration(
|
||||||
|
[
|
||||||
|
BabelTypes.importDefaultSpecifier(
|
||||||
|
BabelTypes.identifier(importName),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
BabelTypes.stringLiteral(importPath),
|
||||||
|
)
|
||||||
|
|
||||||
|
path.replaceWith(importDeclaration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [SERVER build]
|
||||||
|
* This plugin statically imports the lazy loaded components
|
||||||
|
*/
|
||||||
|
const TurnLazyIntoStaticImport: BabelPluginItem = {
|
||||||
|
name: 'turn-lazy-into-static-import-plugin',
|
||||||
|
visitor: {
|
||||||
|
Program: (path) => {
|
||||||
|
path.traverse({
|
||||||
|
ImportSpecifier: (subPath) => {
|
||||||
|
if (isTuonoDynamicFnImported(subPath)) {
|
||||||
|
path.traverse(turnLazyIntoStatic)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LazyLoadingPluginOptions {
|
||||||
|
include: FilterPattern
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LazyLoadingPlugin(
|
||||||
|
options: LazyLoadingPluginOptions,
|
||||||
|
): VitePlugin {
|
||||||
|
const { include } = options
|
||||||
|
|
||||||
|
const filter = createFilter(include)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-tuono-lazy-loading',
|
||||||
|
enforce: 'pre',
|
||||||
|
transform(code, id, opts): Rollup.TransformResult {
|
||||||
|
if (!filter(id)) return
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo we should exclude non tsx files from this transformation
|
||||||
|
* this might benefit build time avoiding running `includes` on non-tsx files.
|
||||||
|
* This can be executed using `_id` parameter
|
||||||
|
* which is the filepath that is being processed
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (
|
||||||
|
code.includes(TUONO_DYNAMIC_FN_ID) &&
|
||||||
|
code.includes(TUONO_MAIN_PACKAGE)
|
||||||
|
) {
|
||||||
|
const plugins: Array<BabelPluginItem> = [
|
||||||
|
['@babel/plugin-syntax-jsx', {}],
|
||||||
|
['@babel/plugin-syntax-typescript', { isTSX: true }],
|
||||||
|
]
|
||||||
|
|
||||||
|
if (opts?.ssr) {
|
||||||
|
plugins.push(RemoveTuonoLazyImport, TurnLazyIntoStaticImport)
|
||||||
|
} else {
|
||||||
|
plugins.push(ReplaceTuonoLazyImport)
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = transformSync(code, { plugins })
|
||||||
|
|
||||||
|
return res?.code
|
||||||
|
}
|
||||||
|
|
||||||
|
return code
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
isIdentifier,
|
||||||
|
isImportDeclaration,
|
||||||
|
isImportSpecifier,
|
||||||
|
} from '@babel/types'
|
||||||
|
|
||||||
|
import { TUONO_MAIN_PACKAGE, TUONO_DYNAMIC_FN_ID } from './constants'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By a given AST Node path returns true if the path involves an import specifier
|
||||||
|
* importing {@link TUONO_DYNAMIC_FN_ID}
|
||||||
|
*/
|
||||||
|
export const isTuonoDynamicFnImported = (path: babel.NodePath): boolean => {
|
||||||
|
// If the node isn't an import declaration there is no need to process it
|
||||||
|
if (!isImportDeclaration(path.parentPath?.node)) return false
|
||||||
|
|
||||||
|
// if the import doesn't import from 'tuono' we don't need to process it
|
||||||
|
if (path.parentPath.node.source.value !== TUONO_MAIN_PACKAGE) return false
|
||||||
|
|
||||||
|
// ensure that we are processing an import specifier
|
||||||
|
if (!isImportSpecifier(path.node)) return false
|
||||||
|
|
||||||
|
// finally check if the imported item is `TUONO_DYNAMIC_FN_ID`
|
||||||
|
return isIdentifier(path.node.imported, { name: TUONO_DYNAMIC_FN_ID })
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import React, { Suspense, type JSX } from "react";
|
||||||
|
import { __tuono__internal__lazyLoadComponent as dynamic } from "tuono";
|
||||||
|
const DynamicComponent = dynamic(() => import("../components/DynamicComponent"));
|
||||||
|
const Loading = (): JSX.Element => <>Loading</>;
|
||||||
|
export default function IndexPage(): JSX.Element {
|
||||||
|
return <Suspense fallback={<Loading />}>
|
||||||
|
<DynamicComponent />
|
||||||
|
</Suspense>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import React, { Suspense, type JSX } from "react";
|
||||||
|
import DynamicComponent from "../components/DynamicComponent";
|
||||||
|
const Loading = (): JSX.Element => <>Loading</>;
|
||||||
|
export default function IndexPage(): JSX.Element {
|
||||||
|
return <Suspense fallback={<Loading />}>
|
||||||
|
<DynamicComponent />
|
||||||
|
</Suspense>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React, { Suspense, type JSX } from "react";
|
||||||
|
import { dynamic } from "tuono";
|
||||||
|
|
||||||
|
const DynamicComponent = dynamic(() => import("../components/DynamicComponent"))
|
||||||
|
|
||||||
|
const Loading = (): JSX.Element => <>Loading</>
|
||||||
|
|
||||||
|
export default function IndexPage(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
|
<DynamicComponent />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createRoute } from 'tuono';
|
||||||
|
import { dynamic } from 'external-lib';
|
||||||
|
const IndexImport = dynamic(() => import('./../src/routes/index'));
|
||||||
|
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createRoute } from 'tuono';
|
||||||
|
import { dynamic } from 'external-lib';
|
||||||
|
const IndexImport = dynamic(() => import('./../src/routes/index'));
|
||||||
|
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { createRoute } from 'tuono'
|
||||||
|
import { dynamic } from 'external-lib'
|
||||||
|
|
||||||
|
const IndexImport = dynamic(() => import('./../src/routes/index'))
|
||||||
|
const PokemonspokemonImport = dynamic(
|
||||||
|
() => import('./../src/routes/pokemons/[pokemon]'),
|
||||||
|
)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { createRoute, __tuono__internal__lazyLoadComponent as dynamic } from 'tuono';
|
||||||
|
const IndexImport = dynamic(() => import('./../src/routes/index'));
|
||||||
|
const PokemonspokemonImport = dynamic(() => import('./../src/routes/pokemons/[pokemon]'));
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { createRoute } from 'tuono';
|
||||||
|
import IndexImport from "./../src/routes/index";
|
||||||
|
import PokemonspokemonImport from "./../src/routes/pokemons/[pokemon]";
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { createRoute, dynamic } from 'tuono'
|
||||||
|
|
||||||
|
const IndexImport = dynamic(() => import('./../src/routes/index'))
|
||||||
|
const PokemonspokemonImport = dynamic(
|
||||||
|
() => import('./../src/routes/pokemons/[pokemon]'),
|
||||||
|
)
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import fs from 'node:fs/promises'
|
||||||
|
import os from 'node:os'
|
||||||
|
|
||||||
|
import { it, expect, describe } from 'vitest'
|
||||||
|
import type { Plugin } from 'vite'
|
||||||
|
|
||||||
|
import { LazyLoadingPlugin } from '../src'
|
||||||
|
|
||||||
|
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 {
|
||||||
|
/** @warning Keep in sync with {@link createBaseViteConfigFromTuonoConfig} */
|
||||||
|
const pluginFilesInclude = /\.(jsx|js|mdx|md|tsx|ts)$/
|
||||||
|
|
||||||
|
return LazyLoadingPlugin({ include: pluginFilesInclude }).transform as never
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('"dynamic" sources', async () => {
|
||||||
|
const folderNames = await fs.readdir(`${process.cwd()}/tests/sources`)
|
||||||
|
|
||||||
|
describe.each(folderNames)('%s', async (folderName) => {
|
||||||
|
const testDirPath = `${process.cwd()}/tests/sources/${folderName}`
|
||||||
|
|
||||||
|
const sourceFilePath = `${testDirPath}/source.tsx`
|
||||||
|
|
||||||
|
const sourceRaw = await fs.readFile(sourceFilePath, 'utf-8')
|
||||||
|
/**
|
||||||
|
* When adding `packages/lazy-fn-vite-plugin/tests/sources/dynamic-only` only
|
||||||
|
* the test involving that fixture were broken on Windows... but not the one in the other fixtures:
|
||||||
|
* - packages/lazy-fn-vite-plugin/tests/sources/vanilla
|
||||||
|
* - packages/lazy-fn-vite-plugin/tests/sources/external-dynamic
|
||||||
|
*
|
||||||
|
* Awkwardly this doesn't happen on `packages/fs-router-vite-plugin/tests/generator.spec.ts`
|
||||||
|
*
|
||||||
|
* Too much pain and sadness to investigate this right now.
|
||||||
|
* Might worth creating an utility function in the future if this happens again
|
||||||
|
*/
|
||||||
|
const source = sourceRaw.replace(new RegExp(os.EOL, 'g'), '\n')
|
||||||
|
|
||||||
|
it('should generate file for client', async () => {
|
||||||
|
const pluginTransform = getTransform()
|
||||||
|
const clientBundle = pluginTransform(source, sourceFilePath)
|
||||||
|
|
||||||
|
const expectedClientSrc = `${testDirPath}/client.expected.tsx`
|
||||||
|
|
||||||
|
await expect(clientBundle).toMatchFileSnapshot(
|
||||||
|
expectedClientSrc,
|
||||||
|
`${testDirPath} client build should be equal to ${expectedClientSrc}`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should generate file for server', async () => {
|
||||||
|
const pluginTransform = getTransform()
|
||||||
|
const serverBundle = pluginTransform(source, sourceFilePath, {
|
||||||
|
ssr: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const expectedServerSrc = `${testDirPath}/server.expected.tsx`
|
||||||
|
|
||||||
|
await expect(serverBundle).toMatchFileSnapshot(
|
||||||
|
expectedServerSrc,
|
||||||
|
`${testDirPath} server build should be equal to ${expectedServerSrc}`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('not valid file should not be processed', () => {
|
||||||
|
const notValidFiles: Array<string> = [
|
||||||
|
'src/styles/module.css',
|
||||||
|
'src/pages/file-without-ext',
|
||||||
|
'src/pages/file-with-invalid-ext',
|
||||||
|
'src/components/fileWithInvalidExt',
|
||||||
|
'src/components/sidebar/sidebar-link.module.css',
|
||||||
|
]
|
||||||
|
|
||||||
|
it.each(notValidFiles)('"%s"', (fileName) => {
|
||||||
|
const pluginTransform = getTransform()
|
||||||
|
|
||||||
|
const code = [
|
||||||
|
"import { createRoute, dynamic } from 'tuono'",
|
||||||
|
"const IndexImport = dynamic(() => import('./../src/routes/index'))",
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
const result = pluginTransform(code, fileName)
|
||||||
|
|
||||||
|
expect(result).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user