From ea82d2b1df481fd4f53bc0ea382e9a249135041e Mon Sep 17 00:00:00 2001 From: Valerio Ageno <51341197+Valerioageno@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:00:23 +0200 Subject: [PATCH] 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 --- README.md | 2 +- .../src/components/sidebar/sidebar.tsx | 8 +- .../{getting-started.mdx => cli.mdx} | 24 +- .../src/routes/documentation/installation.mdx | 2 +- .../documentation/tutorial/api-fetching.mdx | 10 +- .../documentation/tutorial/components.mdx | 8 +- .../documentation/tutorial/conclusion.mdx | 6 +- .../{overview.mdx => development-setup.mdx} | 14 +- .../documentation/tutorial/dynamic-routes.mdx | 14 +- .../documentation/tutorial/error-handling.mdx | 6 +- .../routes/documentation/tutorial/index.mdx | 23 +- .../documentation/tutorial/production.mdx | 10 +- .../documentation/tutorial/redirections.mdx | 10 +- .../src/routes/documentation/tutorial/seo.mdx | 4 +- crates/tuono/Cargo.toml | 2 +- crates/tuono/src/cli.rs | 2 +- crates/tuono_lib/Cargo.toml | 4 +- crates/tuono_lib_macros/Cargo.toml | 2 +- docs/README.md | 12 - docs/tutorial.md | 697 ------------------ packages/fs-router-vite-plugin/package.json | 2 +- packages/lazy-fn-vite-plugin/package.json | 2 +- packages/router/package.json | 2 +- packages/tuono/package.json | 2 +- 24 files changed, 76 insertions(+), 792 deletions(-) rename apps/documentation/src/routes/documentation/{getting-started.mdx => cli.mdx} (68%) rename apps/documentation/src/routes/documentation/tutorial/{overview.mdx => development-setup.mdx} (70%) delete mode 100644 docs/README.md delete mode 100644 docs/tutorial.md diff --git a/README.md b/README.md index c43d0747..1514324b 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/apps/documentation/src/components/sidebar/sidebar.tsx b/apps/documentation/src/components/sidebar/sidebar.tsx index a1bc6793..883dd89d 100644 --- a/apps/documentation/src/components/sidebar/sidebar.tsx +++ b/apps/documentation/src/components/sidebar/sidebar.tsx @@ -17,7 +17,7 @@ export default function Sidebar({ close }: { close: () => void }): JSX.Element { onClick={close} > @@ -62,11 +62,7 @@ export default function Sidebar({ close }: { close: () => void }): JSX.Element { onClick={close} /> - + - Tuono - Getting started + Tuono - CLI import Breadcrumbs, { Element } from '../../components/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 + +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` diff --git a/apps/documentation/src/routes/documentation/installation.mdx b/apps/documentation/src/routes/documentation/installation.mdx index 368f6846..7404dc45 100644 --- a/apps/documentation/src/routes/documentation/installation.mdx +++ b/apps/documentation/src/routes/documentation/installation.mdx @@ -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 diff --git a/apps/documentation/src/routes/documentation/tutorial/api-fetching.mdx b/apps/documentation/src/routes/documentation/tutorial/api-fetching.mdx index 967220b3..d1bf42c8 100644 --- a/apps/documentation/src/routes/documentation/tutorial/api-fetching.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/api-fetching.mdx @@ -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! diff --git a/apps/documentation/src/routes/documentation/tutorial/components.mdx b/apps/documentation/src/routes/documentation/tutorial/components.mdx index 45c84230..416c151c 100644 --- a/apps/documentation/src/routes/documentation/tutorial/components.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/components.mdx @@ -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: diff --git a/apps/documentation/src/routes/documentation/tutorial/conclusion.mdx b/apps/documentation/src/routes/documentation/tutorial/conclusion.mdx index a2854fd4..36b6c5ad 100644 --- a/apps/documentation/src/routes/documentation/tutorial/conclusion.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/conclusion.mdx @@ -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 diff --git a/apps/documentation/src/routes/documentation/tutorial/overview.mdx b/apps/documentation/src/routes/documentation/tutorial/development-setup.mdx similarity index 70% rename from apps/documentation/src/routes/documentation/tutorial/overview.mdx rename to apps/documentation/src/routes/documentation/tutorial/development-setup.mdx index c75bf1e9..2c753e7b 100644 --- a/apps/documentation/src/routes/documentation/tutorial/overview.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/development-setup.mdx @@ -1,7 +1,7 @@ import { Head } from 'tuono' - Tutorial - Scaffold new project + Tutorial - Development setup 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! diff --git a/apps/documentation/src/routes/documentation/tutorial/dynamic-routes.mdx b/apps/documentation/src/routes/documentation/tutorial/dynamic-routes.mdx index b93b13e8..9f78ae50 100644 --- a/apps/documentation/src/routes/documentation/tutorial/dynamic-routes.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/dynamic-routes.mdx @@ -1,27 +1,27 @@ import { Head } from 'tuono' - Tutorial - __ routes + Tutorial - Dynamic routes import Breadcrumbs, { Element } from '../../../components/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: diff --git a/apps/documentation/src/routes/documentation/tutorial/error-handling.mdx b/apps/documentation/src/routes/documentation/tutorial/error-handling.mdx index 8af27efc..4d1e205d 100644 --- a/apps/documentation/src/routes/documentation/tutorial/error-handling.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/error-handling.mdx @@ -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. diff --git a/apps/documentation/src/routes/documentation/tutorial/index.mdx b/apps/documentation/src/routes/documentation/tutorial/index.mdx index 9e3405f7..65d9cc88 100644 --- a/apps/documentation/src/routes/documentation/tutorial/index.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/index.mdx @@ -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 diff --git a/apps/documentation/src/routes/documentation/tutorial/production.mdx b/apps/documentation/src/routes/documentation/tutorial/production.mdx index d600e0e0..18ed02e6 100644 --- a/apps/documentation/src/routes/documentation/tutorial/production.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/production.mdx @@ -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. diff --git a/apps/documentation/src/routes/documentation/tutorial/redirections.mdx b/apps/documentation/src/routes/documentation/tutorial/redirections.mdx index 5def6e49..d8eec77a 100644 --- a/apps/documentation/src/routes/documentation/tutorial/redirections.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/redirections.mdx @@ -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! ``` -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! diff --git a/apps/documentation/src/routes/documentation/tutorial/seo.mdx b/apps/documentation/src/routes/documentation/tutorial/seo.mdx index a35e49a4..6e6dc8d8 100644 --- a/apps/documentation/src/routes/documentation/tutorial/seo.mdx +++ b/apps/documentation/src/routes/documentation/tutorial/seo.mdx @@ -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 `` 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 diff --git a/crates/tuono/Cargo.toml b/crates/tuono/Cargo.toml index 28d60e7a..cc696e40 100644 --- a/crates/tuono/Cargo.toml +++ b/crates/tuono/Cargo.toml @@ -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" diff --git a/crates/tuono/src/cli.rs b/crates/tuono/src/cli.rs index 9b0a105c..27f96a20 100644 --- a/crates/tuono/src/cli.rs +++ b/crates/tuono/src/cli.rs @@ -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, diff --git a/crates/tuono_lib/Cargo.toml b/crates/tuono_lib/Cargo.toml index d5c89ae8..927df0b1 100644 --- a/crates/tuono_lib/Cargo.toml +++ b/crates/tuono_lib/Cargo.toml @@ -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"] } diff --git a/crates/tuono_lib_macros/Cargo.toml b/crates/tuono_lib_macros/Cargo.toml index 671dbc70..fa4aa629 100644 --- a/crates/tuono_lib_macros/Cargo.toml +++ b/crates/tuono_lib_macros/Cargo.toml @@ -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"] diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 03a9bc7c..00000000 --- a/docs/README.md +++ /dev/null @@ -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. diff --git a/docs/tutorial.md b/docs/tutorial.md deleted file mode 100644 index 0a6d8e90..00000000 --- a/docs/tutorial.md +++ /dev/null @@ -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 -++ -
- - Crates - - - Npm - -
-
-

- TUONO -

-
- - -
-
-
    - {data.results.map((pokemon) => { - return pokemon.name; - })} -
- - ); -} -``` - -```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 ; -++ return ( -++ <> -++ -++ Pokemon: ${data?.name} -++ -++ -++ -++ ) -} -``` - -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 - -
    -++ - {data.results.map((pokemon, i) => { - return - })} -
-``` - -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). diff --git a/packages/fs-router-vite-plugin/package.json b/packages/fs-router-vite-plugin/package.json index 18aca9d7..abb68888 100644 --- a/packages/fs-router-vite-plugin/package.json +++ b/packages/fs-router-vite-plugin/package.json @@ -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", diff --git a/packages/lazy-fn-vite-plugin/package.json b/packages/lazy-fn-vite-plugin/package.json index 8f6778ae..d9eca17a 100644 --- a/packages/lazy-fn-vite-plugin/package.json +++ b/packages/lazy-fn-vite-plugin/package.json @@ -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", diff --git a/packages/router/package.json b/packages/router/package.json index 0cc6a794..1e735ead 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -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", diff --git a/packages/tuono/package.json b/packages/tuono/package.json index 5cc69a44..9670ae1b 100644 --- a/packages/tuono/package.json +++ b/packages/tuono/package.json @@ -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",