mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-25 21:02:45 -07:00
Correct documentation spelling (#68)
* fix: documentation/index spelling * fix: documentation/development-setup spelling * fix: documentation/api-fetching spelling * fix: documentation/components spelling * fix: documentation/dynamic-routes spelling * feat: add correct tutorial link to README.md * chore: remove legacy folder * fix: formatting * fix: documentation/error-handling spelling * fix: documentation/seo spelling * fix: documentation/redirections spelling * fix: documentation/production spelling * fix: documentation/conclusion spelling * feat: update version to v0.10.4 * fix: redirections spelling * fix: API fetching spelling * feat: create CLI doc page * fix: correct CLI spelling * fix: update CLI output spelling
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
[Documentation](https://tuono.dev) | [Tutorial](https://github.com/Valerioageno/tuono/blob/main/docs/tutorial.md) |
|
||||
[Documentation](https://tuono.dev) | [Tutorial](https://tuono.dev/documentation/tutorial) |
|
||||
[✨Contributing](https://tuono.dev/documentation/contributing)
|
||||
|
||||
# Tuono
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function Sidebar({ close }: { close: () => void }): JSX.Element {
|
||||
onClick={close}
|
||||
>
|
||||
<SidebarLink
|
||||
href="/documentation/tutorial/overview"
|
||||
href="/documentation/tutorial/development-setup"
|
||||
label="Development setup"
|
||||
onClick={close}
|
||||
/>
|
||||
@@ -62,11 +62,7 @@ export default function Sidebar({ close }: { close: () => void }): JSX.Element {
|
||||
onClick={close}
|
||||
/>
|
||||
</SidebarLink>
|
||||
<SidebarLink
|
||||
href="/documentation/getting-started"
|
||||
label="Getting started"
|
||||
onClick={close}
|
||||
/>
|
||||
<SidebarLink href="/documentation/cli" label="CLI" onClick={close} />
|
||||
<SidebarLink
|
||||
label="Routing"
|
||||
href="/documentation/routing"
|
||||
|
||||
+19
-5
@@ -1,18 +1,16 @@
|
||||
import { Head } from 'tuono'
|
||||
|
||||
<Head>
|
||||
<title>Tuono - Getting started</title>
|
||||
<title>Tuono - CLI</title>
|
||||
</Head>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Getting started" />
|
||||
<Element label="CLI" />
|
||||
</Breadcrumbs>
|
||||
|
||||
# Getting started
|
||||
|
||||
## The CLI
|
||||
# CLI
|
||||
|
||||
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
|
||||
|
||||
@@ -21,6 +19,22 @@ Tuono is the CLI that provides all the needed commands to handle the full-stack
|
||||
|
||||
To list all the available commands, run `tuono -h`.
|
||||
|
||||
```bash
|
||||
The React/Rust full-stack framework
|
||||
|
||||
Usage: tuono <COMMAND>
|
||||
|
||||
Commands:
|
||||
dev Start the development environment
|
||||
build Build the production assets
|
||||
new Scaffold a new project
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
|
||||
To create a new project, run `tuono new [NAME]` (optionally you can pass the --template (or -t) flag - check the examples' folder).
|
||||
|
||||
Then to run the local development environment run inside the project folder `tuono dev`
|
||||
@@ -40,7 +40,7 @@ tuono --version
|
||||
Run `tuono -h` to see all the available commands.
|
||||
|
||||
```bash
|
||||
The react/rust fullstack framework
|
||||
The React/Rust full-stack framework
|
||||
|
||||
Usage: tuono <COMMAND>
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
# API fetching
|
||||
|
||||
The goal is to use the [PokeAPI](https://pokeapi.co/docs/v2) to list all the pokemons of the first generation (the best one btw) and then reserve a \_\_ page for each one separately.
|
||||
The goal is to use the [PokeAPI](https://pokeapi.co/docs/v2) to list all the Pokémon of the first generation (the best one, btw) and then reserve a dynamic page for each one separately.
|
||||
|
||||
## Fetch all the pokemons
|
||||
## Fetch all the Pokémon
|
||||
|
||||
To start let’s fetch all of them in the root page; since we want to render them on the server side we gonna need to implement the logic in the `index.rs` file.
|
||||
To start, let’s fetch all of them from the root page. Since we want to render them on the server side, we are going to need to implement the logic in the `index.rs` file.
|
||||
|
||||
Clear the `index.rs` file and paste:
|
||||
|
||||
@@ -52,7 +52,7 @@ async fn get_all_pokemons(_req: Request, fetch: Client) -> Response {
|
||||
}
|
||||
```
|
||||
|
||||
Now the pokemons are correctly fetched and hydrated on the client side so we can actually use them. Clear the `index.tsx` file and paste:
|
||||
Now the Pokémon are correctly fetched and hydrated on the client side, so we can actually use them. Clear the `index.tsx` file and paste:
|
||||
|
||||
```tsx
|
||||
// src/routes/index.tsx
|
||||
@@ -102,4 +102,4 @@ export default function IndexPage({
|
||||
}
|
||||
```
|
||||
|
||||
Refresh now the browser! A bit ugly but all the pokemons are finally printed on screen!
|
||||
Refresh the browser now! A bit ugly, but all the Pokémon are finally printed on screen!
|
||||
|
||||
@@ -15,7 +15,7 @@ import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
## Creating a stand-alone component
|
||||
|
||||
Let’s then create the button needed for displaying the list of pokemons.
|
||||
Let’s then create the button needed for displaying the list of Pokémon.
|
||||
|
||||
Create the following file `src/components/PokemonLink.tsx` and fill the content with:
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function PokemonLink({
|
||||
}
|
||||
```
|
||||
|
||||
Now that the link is done let’s import it in the `index.tsx` file
|
||||
Now that the link is done, let’s import it into the `index.tsx` file
|
||||
|
||||
```diff
|
||||
// src/routes/index.tsx
|
||||
@@ -63,8 +63,8 @@ Now that the link is done let’s import it in the `index.tsx` file
|
||||
// ...
|
||||
```
|
||||
|
||||
Now the links work. Clicking on any of them we get redirected to the 404 page because we haven’t yet implemented the `pokemons/[pokemon]` page.
|
||||
As previously said CSS modules are enabled out of the box so let’s make those links a little bit nicer.
|
||||
Now the links work. Clicking on any of them, we get redirected to the 404 page because we haven’t yet implemented the `pokemons/[pokemon]` page.
|
||||
As previously said, CSS modules are enabled out of the box, so let’s make those links a little bit nicer.
|
||||
|
||||
Create alongside the `PokemonLink.tsx` component the CSS module `PokemonLink.module.css` and copy the following content into it:
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
# Conclusion
|
||||
|
||||
That’s it! You just created a multi thread full stack application with rust and react.
|
||||
That’s it! You just created a multi thread full stack application with Rust and React.
|
||||
|
||||
The project is still under heavy development and many features are not ready yet but
|
||||
The project is still under heavy development and many features are not ready yet, but
|
||||
I hope you got the taste of what is like working with rust and react in the same stack.
|
||||
|
||||
As I mentioned in the introduction I'd love to hear what you thought about the framework and the tutorial - feel free to reach me
|
||||
As I mentioned in the introduction, I'd love to hear what you thought about the framework and the tutorial - feel free to reach me
|
||||
at [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it) or in Twitter (X) DMs [@valerioageno](https://twitter.com/valerioageno).
|
||||
|
||||
Ciao
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
import { Head } from 'tuono'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - Scaffold new project</title>
|
||||
<title>Tutorial - Development setup</title>
|
||||
</Head>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
@@ -15,7 +15,7 @@ import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
## Scaffold a new project
|
||||
|
||||
To setup a new fresh project you just need to run the following command:
|
||||
To set up a new fresh project you just need to run the following command:
|
||||
|
||||
```
|
||||
$ tuono new tuono-tutorial
|
||||
@@ -27,7 +27,7 @@ Get into the project folder and install the dependencies with:
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Open it with your favourite code editor.
|
||||
Open it with your favorite code editor.
|
||||
|
||||
The project will have the following structure:
|
||||
|
||||
@@ -45,20 +45,20 @@ The project will have the following structure:
|
||||
`public/`: put here all the files you want to be public
|
||||
|
||||
`src/routes/`: All the files in this folder are considered routes. All the routes are server side rendered out of the box.
|
||||
To add server side capabilities just create a rust file with the same name as the route (i.e. `about.tsx` → `about.rs`).
|
||||
To add server side capabilities, just create a Rust file with the same name as the route (i.e. `about.tsx` → `about.rs`).
|
||||
|
||||
`src/styles/`: In this folder there is the `global.css` file that stores all the global styles. For the rest of the project you can use
|
||||
`src/styles/`: In this folder there is the `global.css` file that stores all the global styles. For the rest of the project, you can use
|
||||
CSS modules.
|
||||
|
||||
## Start the dev environment
|
||||
|
||||
To start the development environment you just need to run the following command within the project folder:
|
||||
To start the development environment, you just need to run the following command within the project folder:
|
||||
|
||||
```
|
||||
$ tuono dev
|
||||
```
|
||||
|
||||
The first time might take a little bit because it will install all the rust’s dependencies. All the other execution will be pretty quick!
|
||||
The first time might take a little bit because it will install all the Rust’s dependencies. All the other execution will be pretty quick!
|
||||
|
||||
> 💡 The tuono dev development script is currently under strong optimization improvements.
|
||||
> In case you face any error delete the cache `.tuono` folder and run it again!
|
||||
@@ -1,27 +1,27 @@
|
||||
import { Head } from 'tuono'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - __ routes</title>
|
||||
<title>Tutorial - Dynamic routes</title>
|
||||
</Head>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="__ routes" />
|
||||
<Element label="Dynamic routes" />
|
||||
</Breadcrumbs>
|
||||
|
||||
# \_\_ routes
|
||||
# Dynamic routes
|
||||
|
||||
## Create the pokemon route
|
||||
## Create the Pokémon route
|
||||
|
||||
The homepage is ready. We have the full list of pokemons and they are all links. Great!
|
||||
The homepage is ready. We have the full list of Pokémon and they are all links. Great!
|
||||
|
||||
Now we want to make those links actually pointing to a real page. Let’s create the \_\_ route.
|
||||
Now we want to make those links actually point to a real page. Let’s create the \_\_ route.
|
||||
|
||||
Create the folder `routes/pokemons` and then create the two files `[pokemon].tsx` and `[pokemon].rs`.
|
||||
|
||||
These two will handle every requests that points to `http://localhost:3000/pokemons/bulbasaur..mew`.
|
||||
These two will handle every request that points to `http://localhost:3000/pokemons/bulbasaur..mew`.
|
||||
|
||||
Let’s first work on the server side file. Paste into the new `[pokemon].rs` file the following code:
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
# Error handling
|
||||
|
||||
With the current setup all the routes always return a `200 Success` http status no matter the response type.
|
||||
With the current setup, all the routes always return a `200 Success` HTTP status, no matter the response type.
|
||||
|
||||
In order to return a more meaningful status code to the browser the `Props` struct can be initialized with also the
|
||||
In order to return a more meaningful status code to the browser, the `Props` struct can also be initialized with the
|
||||
`Props::new_with_status()` method.
|
||||
|
||||
Let's see how it works!
|
||||
@@ -98,5 +98,5 @@ async fn get_all_pokemons(_req: Request, fetch: Client) -> Response {
|
||||
}
|
||||
```
|
||||
|
||||
If you now try to load a not existing pokemon (`http://localhost:3000/pokemons/tuono-pokemon`) you will
|
||||
If you now try to load a not-existing Pokémon (`http://localhost:3000/pokemons/tuono-pokemon`) you will
|
||||
correctly receive a 404 status code in the console.
|
||||
|
||||
@@ -12,13 +12,13 @@ import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
# Tutorial
|
||||
|
||||
This tutorial is meant for giving you a sneak peek of the framework and is intended to evolve during the
|
||||
This tutorial is meant to give you a sneak peek at the framework and is intended to evolve during the
|
||||
development - be sure to have [installed](/documentation/installation) the [latest version](https://crates.io/crates/tuono).
|
||||
|
||||
This tutorial is not meant for people that don't know React - in that case I suggest you to first read the
|
||||
[React doc](https://react.dev/); Typescript and Rust knowledge is not a requirement though!
|
||||
This tutorial is not meant for people who don't know React - in that case, I suggest you first read the
|
||||
[React Doc](https://react.dev/); Typescript and Rust knowledge is not a requirement though!
|
||||
|
||||
If you prefer to just read the code rather than writing it you can download the finished tutorial project with:
|
||||
If you prefer to just read the code rather than write you can download the finished tutorial project with:
|
||||
|
||||
```bash
|
||||
$ tuono new tutorial --template tutorial
|
||||
@@ -26,18 +26,3 @@ $ tuono new tutorial --template tutorial
|
||||
|
||||
> I'd love to hear your thoughts about the framework and the tutorial - feel free to reach me at [valerioageno@yahoo.it](mailto:valerioageno@ahoo.it)
|
||||
> or on Twitter (X) DM [@valerioageno](https://twitter.com/valerioageno)
|
||||
|
||||
## Table of contents
|
||||
|
||||
- Project scaffold
|
||||
- Start the dev environment
|
||||
- The “/” route
|
||||
- Tutorial introduction
|
||||
- Fetch all the pokemons
|
||||
- Create a stand-alone component
|
||||
- Create the /pokemons/[pokemon] route
|
||||
- Error handling
|
||||
- Seo and meta tags
|
||||
- Handle redirections
|
||||
- Building for production
|
||||
- Conclusion
|
||||
|
||||
@@ -13,21 +13,21 @@ import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
# Production build
|
||||
|
||||
The source now is ready to be released. Both server and client have been managed in a unoptimized way
|
||||
to easy the development experience. To build the project to the production state just run:
|
||||
The source is now ready to be released. Both server and client have been managed in an unoptimized way
|
||||
to ease the development experience. To build the project to the production state, just run:
|
||||
|
||||
```shell
|
||||
$ tuono build
|
||||
```
|
||||
|
||||
This command just created the final assets within the `out` directory. To run then the prodiction server
|
||||
This command just created the final assets within the `out` directory. To run the production server,
|
||||
run:
|
||||
|
||||
```shell
|
||||
$ cargo run --release
|
||||
```
|
||||
|
||||
Check again [`http://localhost:3000/`](http://localhost:3000/) - This environment now has all the
|
||||
optimizations ready to unleash the power of a rust server that seamessly renders a React application!🚀
|
||||
Check again [`http://localhost:3000/`](http://localhost:3000/) This environment now has all the
|
||||
optimizations ready to unleash the power of a rust server that seamlessly renders a React application!🚀
|
||||
|
||||
> Note: The `out` directory is not standalone. You can't rely just on it to run the production server.
|
||||
|
||||
@@ -13,13 +13,11 @@ import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
# Redirections
|
||||
|
||||
## Handle redirections
|
||||
|
||||
What if there is a pokemon among all of them that should be considered the GOAT? What
|
||||
What if there is a Pokémon among all of them that should be considered the GOAT? What
|
||||
we are going to do right now is creating a new route `/pokemons/GOAT` that points to the best
|
||||
pokemon of the first generation.
|
||||
Pokémon of the first generation.
|
||||
|
||||
First let's create a new route by just creating an new file `/pokemons/GOAT.rs` and pasting the following code:
|
||||
First, let's create a new route by just creating a new file `/pokemons/GOAT.rs` and pasting the following code:
|
||||
|
||||
```rs
|
||||
// src/routes/pokemons/GOAT.rs
|
||||
@@ -45,5 +43,5 @@ Now let's create the button in the home page to actually point to it!
|
||||
</ul>
|
||||
```
|
||||
|
||||
Now at [http://localhost:3000/](http:/localhost:3000/) you will find a new link at the beginning of the list.
|
||||
Now at [http://localhost:3000/](http:/localhost:3000/) You will find a new link at the beginning of the list.
|
||||
Click on it and see the application automatically redirecting you to your favourite pokemon's route!
|
||||
|
||||
@@ -13,11 +13,11 @@ import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
# SEO and meta tags
|
||||
|
||||
The website now works and the http errors are meaningful but we should also take care to be meaningful
|
||||
The website now works and the HTTP errors are meaningful, but we should also take care to be meaningful
|
||||
for the web crawlers. The best way to do it is to enrich the meta tags like the `<title>` and the
|
||||
`<description>`.
|
||||
|
||||
To do so `tuono` exposes also the `<Head />` component useful exactly for handling this scenario. Let's update the `/` and the
|
||||
To do so `tuono` also exposes the `<Head />` component useful exactly for handling this scenario. Let's update the `/` and the
|
||||
`/pokemons/[pokemon]` routes with this.
|
||||
|
||||
```diff
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono"
|
||||
version = "0.10.3"
|
||||
version = "0.10.4"
|
||||
edition = "2021"
|
||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||
description = "The react/rust fullstack framework"
|
||||
|
||||
@@ -33,7 +33,7 @@ enum Actions {
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about = "The react/rust fullstack framework")]
|
||||
#[command(version, about = "The React/Rust full-stack framework")]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
action: Actions,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono_lib"
|
||||
version = "0.10.3"
|
||||
version = "0.10.4"
|
||||
edition = "2021"
|
||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||
description = "The react/rust fullstack framework"
|
||||
@@ -33,7 +33,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.10.3"}
|
||||
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.10.4"}
|
||||
# Match the same version used by axum
|
||||
tokio-tungstenite = "0.24.0"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono_lib_macros"
|
||||
version = "0.10.3"
|
||||
version = "0.10.4"
|
||||
edition = "2021"
|
||||
description = "The react/rust fullstack framework"
|
||||
keywords = [ "react", "typescript", "fullstack", "web", "ssr"]
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# Documentation
|
||||
|
||||
This folder contains the documentation related to the `tuono` framework.
|
||||
|
||||
The framework expects to enable SSG and MDX support in the next months.
|
||||
At that time a standalone website will be built with tuono to hold the whole documentation.
|
||||
|
||||
The following docs are currently actively maintained:
|
||||
- [tutorial](https://github.com/Valerioageno/tuono/blob/main/docs/tutorial.md)
|
||||
|
||||
## Contributions
|
||||
Any help or suggestion will be strongly appreciated.
|
||||
@@ -1,697 +0,0 @@
|
||||
# Tuono tutorial
|
||||
|
||||
This tutorial is meant for giving you a sneak peek of the framework and is intended to evolve along the development - be sure to have installed the latest version.
|
||||
|
||||
The first part is about the project setup and the base knowledge needed to work with tuono. The actual tutorial starts at [Tutorial introduction](#tutorial-introduction).
|
||||
|
||||
> I'd love to hear your thoughts about the framework and the tutorial - feel free to reach me
|
||||
at [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it) or on Twitter (X) DMs
|
||||
[@valerioageno](https://twitter.com/valerioageno)
|
||||
|
||||
This tutorial is **not meant** for people that don't know React - in that case I suggest you to first read the [React doc](https://react.dev/);
|
||||
|
||||
Typescript and Rust knowledge is not a requirement though!
|
||||
|
||||
## Table of Content
|
||||
|
||||
* [CLI Installation](#cli-installation)
|
||||
* [Project scaffold](#project-scaffold)
|
||||
* [Start the dev environment](#start-the-dev-environment)
|
||||
* [The “/” route](#the--route)
|
||||
* [Tutorial introduction](#tutorial-introduction)
|
||||
* [Fetch all the pokemons](#fetch-all-the-pokemons)
|
||||
* [Create a stand-alone component](#create-a-stand-alone-component)
|
||||
* [Create the /pokemons/[pokemon] route](#create-the-pokemonspokemon-route)
|
||||
* [Error handling](#error-handling)
|
||||
* [Seo and meta tags](#seo-and-meta-tags)
|
||||
* [Handle redirections](#handle-redirections)
|
||||
* [Building for production](#building-for-production)
|
||||
* [Conclusion](#conclusion)
|
||||
|
||||
## CLI Installation
|
||||
|
||||
The tuono CLI is hosted on [crates.io](https://crates.io/crates/tuono); to download and install it just run on a terminal:
|
||||
|
||||
```bash
|
||||
$ cargo install tuono
|
||||
```
|
||||
|
||||
To check that is correctly installed run:
|
||||
|
||||
```bash
|
||||
$ tuono --version
|
||||
```
|
||||
|
||||
Run `tuono -h` to see all the available commands.
|
||||
|
||||
## Project scaffold
|
||||
|
||||
To setup a new fresh project you just need to run the following command:
|
||||
|
||||
```bash
|
||||
$ tuono new tuono-tutorial
|
||||
```
|
||||
|
||||
Get into the project folder and install the dependencies with:
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Open it with your favourite code editor.
|
||||
|
||||
The project will have the following structure:
|
||||
|
||||
```
|
||||
├── package.json
|
||||
├── public
|
||||
├── src
|
||||
│ ├── routes
|
||||
│ └── styles
|
||||
├── Cargo.toml
|
||||
├── README.md
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
**public/**: put here all the files you want to be public
|
||||
|
||||
**src/routes/**: All the files in this folder are considered routes. All the routes are server side rendered out of the box. To add server side capabilities just create a rust file with the same name as the route (i.e. `about.tsx` → `about.rs`).
|
||||
|
||||
**src/styles/**: In this folder there is the `global.css` file that stores all the global styles. For the rest of the project you can use CSS modules (⚠️ CSS modules on routes are forbidden).
|
||||
|
||||
## Start the dev environment
|
||||
|
||||
To start the development environment you just need to run the following command within the project folder:
|
||||
|
||||
```bash
|
||||
$ tuono dev
|
||||
```
|
||||
The first time might take a little bit because it will install all the rust’s dependencies. All the other execution will be pretty quick!
|
||||
|
||||
> 💡 The `tuono dev` development script is currently under strong optimization improvements. In case you face any error delete the cache `.tuono` folder and run it again!
|
||||
|
||||
Then open [`http://localhost:3000/`](http://localhost:3000/) on the browser.
|
||||
|
||||
## The “/” route
|
||||
|
||||
All the `index.tsx` files represent the folder root page (i.e. `src/routes/posts/index.tsx` is [`http://localhost:3000/posts`](http://localhost:3000/posts) as well as `src/routes/posts.tsx`).
|
||||
|
||||
The file `index.rs` represents the server side capabilities for the index route. On this file you can:
|
||||
|
||||
- Passing server side props
|
||||
- Changing http status code
|
||||
- Redirecting to a different route
|
||||
|
||||
## Tutorial introduction
|
||||
|
||||
Now that we have some knowledge about the project structure let’s start the real tutorial.
|
||||
|
||||
The goal is to use the [PokeAPI](https://pokeapi.co/docs/v2) to list all the pokemons of the first generation (the best one btw) and then reserve a dynamic page for each one separately.
|
||||
|
||||
> If you have already installed the tuono CLI and you prefer read the code instead of writing it yourself
|
||||
you can download the tutorial source with `tuono new tuono-tutorial --template tutorial`
|
||||
|
||||
## Fetch all the pokemons
|
||||
|
||||
To start let’s fetch all of them in the root page; since we want to render them on the server side we gonna need to implement the logic in the `index.rs` file.
|
||||
|
||||
Clear the `index.rs` file and paste:
|
||||
|
||||
```rust
|
||||
// src/routes/index.rs
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tuono_lib::reqwest::Client;
|
||||
use tuono_lib::{Props, Request, Response};
|
||||
|
||||
const ALL_POKEMON: &str = "https://pokeapi.co/api/v2/pokemon?limit=151";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Pokemons {
|
||||
results: Vec<Pokemon>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Pokemon {
|
||||
name: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
#[tuono_lib::handler]
|
||||
async fn get_all_pokemons(_req: Request, fetch: Client) -> Response {
|
||||
return match fetch.get(ALL_POKEMON).send().await {
|
||||
Ok(res) => {
|
||||
let data = res.json::<Pokemons>().await.unwrap();
|
||||
Response::Props(Props::new(data))
|
||||
}
|
||||
Err(_err) => Response::Props(Props::new("{}")),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Now the pokemons are correctly fetched and hydrated on the client side so we can actually use them. Clear the `index.tsx` file and paste:
|
||||
|
||||
```tsx
|
||||
// src/routes/index.tsx
|
||||
import type { TuonoProps } from "tuono";
|
||||
|
||||
interface Pokemon {
|
||||
name: string
|
||||
}
|
||||
|
||||
interface IndexProps {
|
||||
results: Pokemon[]
|
||||
}
|
||||
|
||||
export default function IndexPage({
|
||||
data,
|
||||
}: TuonoProps<IndexProps>): JSX.Element {
|
||||
if (!data?.results) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="header">
|
||||
<a href="https://crates.io/crates/tuono" target="_blank">
|
||||
Crates
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/tuono" target="_blank">
|
||||
Npm
|
||||
</a>
|
||||
</header>
|
||||
<div className="title-wrap">
|
||||
<h1 className="title">
|
||||
TU<span>O</span>NO
|
||||
</h1>
|
||||
<div className="logo">
|
||||
<img src="rust.svg" className="rust" />
|
||||
<img src="react.svg" className="react" />
|
||||
</div>
|
||||
</div>
|
||||
<ul style={{ flexWrap: "wrap", display: "flex", gap: 10 }}>
|
||||
{data.results.map((pokemon) => {
|
||||
return pokemon.name;
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Refresh now the browser! A bit ugly but all the pokemons are finally printed on screen!
|
||||
|
||||
## Create a stand-alone component
|
||||
|
||||
Let’s then create the button needed for displaying the list of pokemons.
|
||||
|
||||
Create the following file `src/components/PokemonLink.tsx` and fill the content with:
|
||||
|
||||
```tsx
|
||||
// src/components/PokemonLink.tsx
|
||||
import { Link } from "tuono";
|
||||
|
||||
interface Pokemon {
|
||||
name: string
|
||||
}
|
||||
|
||||
export default function PokemonLink({
|
||||
pokemon,
|
||||
id,
|
||||
}: {
|
||||
pokemon: Pokemon;
|
||||
id: number;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<Link href={`/pokemons/${pokemon.name}`}>
|
||||
{pokemon.name}
|
||||
<img
|
||||
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${id}.png`}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Now that the link is done let’s import it in the `index.tsx` file
|
||||
|
||||
```diff
|
||||
// src/routes/index.tsx
|
||||
|
||||
++ import PokemonLink from '../components/PokemonLink'
|
||||
|
||||
// ...
|
||||
<ul style={{ flexWrap: "wrap", display: "flex", gap: 10 }}>
|
||||
-- {pokemons.map((pokemon) => {
|
||||
-- return pokemon.name;
|
||||
++ {pokemons.map((pokemon, i) => {
|
||||
++ return <PokemonLink pokemon={pokemon} id={i + 1} key={i} />;
|
||||
})}
|
||||
</ul>
|
||||
// ...
|
||||
```
|
||||
|
||||
Now the links work. Clicking on any of them we get redirected to the 404 page because we haven’t yet implemented the `pokemons/[pokemon]` page.
|
||||
As previously said CSS modules are enabled out of the box so let’s make those links a little bit nicer.
|
||||
|
||||
Create alongside the `PokemonLink.tsx` component the CSS module `PokemonLink.module.css` and copy the following content into it:
|
||||
|
||||
```css
|
||||
/* src/components/PokemonLink.module.css */
|
||||
|
||||
.link {
|
||||
width: 100%;
|
||||
max-width: 216px;
|
||||
position: relative;
|
||||
background: white;
|
||||
margin-bottom: 10px;
|
||||
border: solid #f0f0f0 1px;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
padding: 5px 5px 5px 15px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
transition: 0.2s;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
|
||||
}
|
||||
|
||||
.link img {
|
||||
width: 70px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
}
|
||||
```
|
||||
|
||||
> 💡 SASS is supported out of the box. Just install the processor in the devDependencies `npm i -D sass` and run again `tuono dev`
|
||||
|
||||
Then import the styles into the `PokemonLink` component as following:
|
||||
|
||||
```diff
|
||||
// src/components/PokemonLink.tsx
|
||||
import { Link } from "tuono";
|
||||
import type { Pokemon } from "./../types/pokemon";
|
||||
++ import styles from './PokemonLink.module.css'
|
||||
|
||||
export default function PokemonLink({
|
||||
pokemon,
|
||||
id,
|
||||
}: {
|
||||
pokemon: Pokemon;
|
||||
id: number;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
-- <Link href={`/pokemons/${pokemon.name}`}>
|
||||
++ <Link className={styles.link} href={`/pokemons/${pokemon.name}`}>
|
||||
{pokemon.name}
|
||||
<img
|
||||
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${id}.png`}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Create the `/pokemons/[pokemon]` route
|
||||
|
||||
The homepage is ready. We have the full list of pokemons and they are all links. Great!
|
||||
|
||||
Now we want to make those links actually pointing to a real page. Let’s create the dynamic route.
|
||||
|
||||
Create the folder `routes/pokemons` and then create the two files `[pokemon].tsx` and `[pokemon].rs`.
|
||||
|
||||
These two will handle every requests that points to `http://localhost:3000/pokemons/bulbasaur..mew`.
|
||||
|
||||
Let’s first work on the server side file. Paste into the new `[pokemon].rs` file the following code:
|
||||
|
||||
```rust
|
||||
// src/routes/pokemons/[pokemon].rs
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tuono_lib::reqwest::Client;
|
||||
use tuono_lib::{Props, Request, Response};
|
||||
|
||||
const POKEMON_API: &str = "https://pokeapi.co/api/v2/pokemon";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Pokemon {
|
||||
name: String,
|
||||
id: u16,
|
||||
weight: u16,
|
||||
height: u16,
|
||||
}
|
||||
|
||||
#[tuono_lib::handler]
|
||||
async fn get_pokemon(req: Request, fetch: Client) -> Response {
|
||||
// The param `pokemon` is defined in the route filename [pokemon].rs
|
||||
let pokemon = req.params.get("pokemon").unwrap();
|
||||
|
||||
return match fetch.get(format!("{POKEMON_API}/{pokemon}")).send().await {
|
||||
Ok(res) => {
|
||||
let data = res.json::<Pokemon>().await.unwrap();
|
||||
Response::Props(Props::new(data))
|
||||
}
|
||||
Err(_err) => Response::Props(Props::new("{}"))
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Then let’s work on the frontend. Paste into the `[pokemon].tsx` file the following code:
|
||||
|
||||
```tsx
|
||||
import { TuonoProps } from "tuono";
|
||||
import PokemonView from "../../components/PokemonView";
|
||||
|
||||
export default function Pokemon({ data }: TuonoProps): JSX.Element {
|
||||
return <PokemonView pokemon={data} />;
|
||||
}
|
||||
```
|
||||
|
||||
The browser should complain that the component `PokemonView` does not exist. Let’s create it then!
|
||||
|
||||
```tsx
|
||||
// components/PokemonView.tsx
|
||||
import { Link } from "tuono";
|
||||
import styles from "./PokemonView.module.css";
|
||||
|
||||
interface Pokemon {
|
||||
name: string
|
||||
id: string
|
||||
weight: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export default function PokemonView({
|
||||
pokemon,
|
||||
}: {
|
||||
pokemon?: Pokemon;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<Link className={styles["back-btn"]} href="/">
|
||||
Back
|
||||
</Link>
|
||||
{pokemon?.name && (
|
||||
<div className={styles.pokemon}>
|
||||
<div>
|
||||
<h1 className={styles.name}>
|
||||
{pokemon.name}
|
||||
</h1>
|
||||
<dl className={styles.spec}>
|
||||
<dt className={styles.label}>Weight:</dt>
|
||||
<dd>{pokemon.weight}lbs</dd>
|
||||
</dl>
|
||||
<dl className={styles.spec}>
|
||||
<dt className={styles.label}>Height:</dt>
|
||||
<dd>{pokemon.height}ft</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<img
|
||||
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${pokemon.id}.png`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* components/PokemonView.module.css */
|
||||
.back-btn {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 7px 15px;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
border: solid #f0f0f0 1px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
|
||||
}
|
||||
|
||||
.pokemon {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 50px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.pokemon img {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.spec {
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 700;
|
||||
}
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
With the current setup all the routes always return a `200 Success` http status no matter the response type.
|
||||
|
||||
In order to return a more meaningful status code to the browser the `Props` struct can be initialized with also the
|
||||
`Props::new_with_status()` method.
|
||||
|
||||
Let's see how it works!
|
||||
|
||||
```diff
|
||||
// src/routes/pokemons/[pokemon].rs
|
||||
use serde::{Deserialize, Serialize};
|
||||
-- use tuono_lib::reqwest::Client;
|
||||
++ use tuono_lib::reqwest::{Client, StatusCode};
|
||||
use tuono_lib::{Props, Request, Response};
|
||||
|
||||
const POKEMON_API: &str = "https://pokeapi.co/api/v2/pokemon";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Pokemon {
|
||||
name: String,
|
||||
id: u16,
|
||||
weight: u16,
|
||||
height: u16,
|
||||
}
|
||||
|
||||
#[tuono_lib::handler]
|
||||
async fn get_pokemon(req: Request, fetch: Client) -> Response {
|
||||
// The param `pokemon` is defined in the route filename [pokemon].rs
|
||||
let pokemon = req.params.get("pokemon").unwrap();
|
||||
|
||||
return match fetch.get(format!("{POKEMON_API}/{pokemon}")).send().await {
|
||||
Ok(res) => {
|
||||
++ if res.status() == StatusCode::NOT_FOUND {
|
||||
++ return Response::Props(Props::new_with_status("{}", StatusCode::NOT_FOUND));
|
||||
++ }
|
||||
|
||||
let data = res.json::<Pokemon>().await.unwrap();
|
||||
Response::Props(Props::new(data))
|
||||
}
|
||||
-- Err(_err) => Response::Props(Props::new(
|
||||
++ Err(_err) => Response::Props(Props::new_with_status(
|
||||
++ "{}",
|
||||
++ StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
```diff
|
||||
// src/routes/index.rs
|
||||
use serde::{Deserialize, Serialize};
|
||||
-- use tuono_lib::reqwest::Client;
|
||||
++ use tuono_lib::reqwest::{Client, StatusCode};
|
||||
use tuono_lib::{Props, Request, Response};
|
||||
|
||||
const ALL_POKEMON: &str = "https://pokeapi.co/api/v2/pokemon?limit=151";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Pokemons {
|
||||
results: Vec<Pokemon>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Pokemon {
|
||||
name: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
#[tuono_lib::handler]
|
||||
async fn get_all_pokemons(_req: Request, fetch: Client) -> Response {
|
||||
return match fetch.get(ALL_POKEMON).send().await {
|
||||
Ok(res) => {
|
||||
let data = res.json::<Pokemons>().await.unwrap();
|
||||
Response::Props(Props::new(data))
|
||||
}
|
||||
-- Err(_err) => Response::Props(Props::new(
|
||||
++ Err(_err) => Response::Props(Props::new_with_status(
|
||||
++ "{}", // Return empty JSON
|
||||
++ StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
If you now try to load a not existing pokemon (`http://localhost:3000/pokemons/tuono-pokemon`) you will
|
||||
correctly receive a 404 status code in the console.
|
||||
|
||||
## Seo and meta tags
|
||||
|
||||
The website now works and the http errors are meaningful but we should also take care to be meaningful
|
||||
for the web crawlers. The best way to do it is to enrich the meta tags like the `<title>` and the
|
||||
`<description>`.
|
||||
|
||||
To do so `tuono` exposes also the `<Head />` component useful exactly for handling this scenario. Let's update the `/` and the
|
||||
`/pokemons/[pokemon]` routes with this.
|
||||
|
||||
```diff
|
||||
// src/routes/index.tsx
|
||||
import type { TuonoProps } from "tuono";
|
||||
++ import { Head } from "tuono"
|
||||
|
||||
interface Pokemon {
|
||||
name: string
|
||||
}
|
||||
|
||||
interface IndexProps {
|
||||
results: Pokemon[]
|
||||
}
|
||||
|
||||
export default function IndexPage({
|
||||
data,
|
||||
}: TuonoProps<IndexProps>): JSX.Element {
|
||||
if (!data?.results) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
++ <Head>
|
||||
++ <title>Tuono tutorial</title>
|
||||
++ </Head>
|
||||
<header className="header">
|
||||
<a href="https://crates.io/crates/tuono" target="_blank">
|
||||
Crates
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/tuono" target="_blank">
|
||||
Npm
|
||||
</a>
|
||||
</header>
|
||||
<div className="title-wrap">
|
||||
<h1 className="title">
|
||||
TU<span>O</span>NO
|
||||
</h1>
|
||||
<div className="logo">
|
||||
<img src="rust.svg" className="rust" />
|
||||
<img src="react.svg" className="react" />
|
||||
</div>
|
||||
</div>
|
||||
<ul style={{ flexWrap: "wrap", display: "flex", gap: 10 }}>
|
||||
{data.results.map((pokemon) => {
|
||||
return pokemon.name;
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```diff
|
||||
// src/routes/pokemons/[pokemon].tsx
|
||||
-- import { TuonoProps } from "tuono";
|
||||
++ import { TuonoProps, Head } from "tuono";
|
||||
import PokemonView from "../../components/PokemonView";
|
||||
|
||||
export default function Pokemon({ data }: TuonoProps): JSX.Element {
|
||||
-- return <PokemonView pokemon={data} />;
|
||||
++ return (
|
||||
++ <>
|
||||
++ <Head>
|
||||
++ <title>Pokemon: ${data?.name}</title>
|
||||
++ </Head>
|
||||
++ <PokemonView pokemon={data} />
|
||||
++ </>
|
||||
++ )
|
||||
}
|
||||
```
|
||||
|
||||
The `Head` component takes as children any valid HTML meta tag.
|
||||
|
||||
## Handle redirections
|
||||
|
||||
What if there is a pokemon among all of them that should be considered the GOAT? What
|
||||
we are going to do right now is creating a new route `/pokemons/GOAT` that points to the best
|
||||
pokemon of the first generation.
|
||||
|
||||
First let's create a new route by just creating an new file `/pokemons/GOAT.rs` and pasting the following code:
|
||||
|
||||
```rs
|
||||
// src/routes/pokemons/GOAT.rs
|
||||
use tuono_lib::{reqwest::Client, Request, Response};
|
||||
|
||||
#[tuono_lib::handler]
|
||||
async fn redirect_to_goat(_: Request, _: Client) -> Response {
|
||||
// Of course the GOAT is mewtwo - feel free to select your favourite 😉
|
||||
Response::Redirect("/pokemons/mewtwo".to_string())
|
||||
}
|
||||
```
|
||||
|
||||
Now let's create the button in the home page to actually point to it!
|
||||
|
||||
```diff
|
||||
// src/routes/index.tsx
|
||||
|
||||
<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} />
|
||||
})}
|
||||
</ul>
|
||||
```
|
||||
|
||||
Now at [http://localhost:3000/](http:/localhost:3000/) you will find a new link at the beginning of the list.
|
||||
Click on it and see the application automatically redirecting you to your favourite pokemon's route!
|
||||
|
||||
## Building for production
|
||||
|
||||
The source now is ready to be released. Both server and client have been managed in a unoptimized way
|
||||
to easy the development experience. To build the project to the production state just run:
|
||||
|
||||
```shell
|
||||
$ tuono build
|
||||
```
|
||||
|
||||
This command just created the final assets within the `out` directory. To run then the prodiction server
|
||||
run:
|
||||
|
||||
```shell
|
||||
$ cargo run --release
|
||||
```
|
||||
|
||||
Check again [`http://localhost:3000/`](http://localhost:3000/) - This environment now has all the
|
||||
optimizations ready to unleash the power of a rust server that seamessly renders a React application!🚀
|
||||
|
||||
> Note: The `out` directory is not standalone. You can't rely just on it to run the production server.
|
||||
|
||||
## Conclusion
|
||||
|
||||
That’s it! You just created a multi thread full stack application with rust and react.
|
||||
|
||||
The project is still under heavy development and many features are not ready yet but
|
||||
I hope you got the taste of what is like working with rust and react in the same stack.
|
||||
|
||||
As I mentioned in the introduction I'd love to hear what you thought about the framework and the tutorial - feel free to reach me
|
||||
at [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it) or in Twitter (X) DMs [@valerioageno](https://twitter.com/valerioageno).
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-fs-router-vite-plugin",
|
||||
"version": "0.10.3",
|
||||
"version": "0.10.4",
|
||||
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-lazy-fn-vite-plugin",
|
||||
"version": "0.10.3",
|
||||
"version": "0.10.4",
|
||||
"description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-router",
|
||||
"version": "0.10.3",
|
||||
"version": "0.10.4",
|
||||
"description": "React routing component for the framework tuono. Tuono is the react/rust fullstack framework",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono",
|
||||
"version": "0.10.3",
|
||||
"version": "0.10.4",
|
||||
"description": "The react/rust fullstack framework",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
|
||||
Reference in New Issue
Block a user