Compare commits

...

77 Commits

Author SHA1 Message Date
Valerio Ageno f4367f1755 feat: update version to v0.16.8 2024-12-24 11:57:21 +01:00
Marco Pasqualetti 7b7447f9a0 feat(packages/vite-plugins): use named export and consistent names across plugins (#260) 2024-12-24 11:41:58 +01:00
Valerio Ageno d12d8404ec feat(packages/tuono-router): replace zustand with React.Context (#256)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2024-12-24 11:41:45 +01:00
Valerio Ageno d1b12173d5 feat: update version to v0.16.7 2024-12-23 18:32:15 +01:00
Marco Pasqualetti f3bf13da59 refactor(packages/*): add tuono- prefix to all relevant folders (#255) 2024-12-23 18:28:06 +01:00
Marco Pasqualetti f236b75f55 refactor(packages/*): use @vitejs/plugin-react-swc and make all package dependencies explicit (#254) 2024-12-23 18:20:29 +01:00
Marco Pasqualetti 2434231f9b chore: add eslint-plugin-react (#251) 2024-12-23 11:43:04 +01:00
Valerio Ageno b8b39e69a1 Add message event polyfill (#250) 2024-12-22 21:35:08 +01:00
Jovan Milosevic f8158dfd8e chore(examples): Remove tsconfig.node.js file from examples (#245) 2024-12-22 20:12:19 +01:00
Valerio Ageno f1ab7df5ce feat: udpdate version to v0.16.6 2024-12-22 18:06:40 +01:00
Marco Pasqualetti 986de49052 refactor: add eslint-plugin-react-hooks (#248) 2024-12-22 18:03:03 +01:00
Marco Pasqualetti d09ca5548a chore: add .editorconfig (#249) 2024-12-22 17:52:03 +01:00
Marco Pasqualetti 77e22c40b6 refactor: enable typescript-eslint strict rules (#243) 2024-12-22 12:08:21 +01:00
Marco Pasqualetti de74647c43 test(packages/tuono): messageChannel - use vi.fn (#241) 2024-12-22 09:30:18 +01:00
Mustafa Zaki Assagaf 498601c21a feat: add MessageChannel polyfill to support react 19 (#238) 2024-12-21 16:55:04 +01:00
Marco Pasqualetti e99f06ddf5 chore(bug_report): make system-info fields required and add CLI command to retrieve them (#239) 2024-12-20 07:34:55 +01:00
Marco Pasqualetti f06af59168 chore: add CODEOWNERS (#237) 2024-12-19 08:22:16 +01:00
Valerio Ageno d50e3a7fc0 feat: update version to v0.16.5 2024-12-18 22:10:05 +01:00
Valerio Ageno 3e4e7ff1ab feat: update rendering from renderToString to renderToReadableStream (#233)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2024-12-18 21:58:19 +01:00
Marco Pasqualetti 21ff3c8655 docs: update tuono (#236) 2024-12-18 21:53:24 +01:00
Valerio Ageno 166e61bab4 feat: update version to v0.16.4 2024-12-18 14:14:51 +01:00
Mustafa Zaki Assagaf 93cd8bd8da fix: catch-all paths replace double underscore with single underscore (#229) 2024-12-18 14:06:14 +01:00
Marco Pasqualetti f5c1641af2 chore(apps/documentation): remove tsconfig.node.json (#228) 2024-12-18 09:54:52 +01:00
Mustafa Zaki Assagaf 62ffed0624 docs: added website link to creates documentation (#230) 2024-12-18 09:13:03 +01:00
Marco Pasqualetti 8d0a4a7417 refactor(packages/lazy-fn-vite-plugin): add filter to avoid processing unrelated files (#227) 2024-12-18 09:11:30 +01:00
Mustafa Zaki Assagaf b1b274ea12 feat: update tuono macro handler naming to avoid naming collisions (#225) 2024-12-17 16:47:23 +01:00
Valerio Ageno fc38865965 fix: README.md 2024-12-16 13:57:25 +01:00
Valerio Ageno 911adaaecb feat: update version to v0.16.3 2024-12-16 08:08:27 +01:00
Marco Pasqualetti 7626ed8d1d fix(packages/lazy-fn-vite-plugin): make dynamic function detection stricter (#223) 2024-12-16 08:03:37 +01:00
Valerio Ageno 6135fd7e1b docs: add dynamic routes page (#219)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2024-12-15 13:39:06 +01:00
Valerio Ageno 14bea36356 fix: update lazyLoadComponent fn name to __tuono__internal__lazyLoadComponent (#222) 2024-12-15 09:22:19 +01:00
Valerio Ageno 4abc163a59 feat: update version to v0.16.2 2024-12-11 14:04:22 +01:00
Marco Pasqualetti 5bb9cf1e7c fix(packages/tuono): styles are not in the page when running tuono dev (#220) 2024-12-11 13:59:47 +01:00
Valerio Ageno a0cdf408a1 feat: update version to v0.16.1 2024-12-10 19:33:48 +01:00
Valerio Ageno 8467506915 feat: create [...catch_all] route (#210) 2024-12-10 19:30:35 +01:00
Valerio Ageno 704255774d fix: defining-routes.mdx 2024-12-10 08:22:03 +01:00
Valerio Ageno 10bb7b1cca feat: update polyfill-db json shape 2024-12-09 20:29:53 +01:00
Valerio Ageno 7f82af3d3b feat: add polyfill db to documentation (#213)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2024-12-09 12:23:33 +01:00
Marco Pasqualetti d7c729c035 docs: update tuono to 0.16 and add mdx support (#214) 2024-12-09 08:20:18 +01:00
Marco Pasqualetti 51fa904f9b docs: optimize public images (#215) 2024-12-09 07:57:25 +01:00
Valerio Ageno 6191e55745 Revert "Add ️ as default favicon for documentation and examples (#191) (#192)"
This reverts commit 7f9bf08f76.
2024-12-08 10:48:15 +01:00
Marco Pasqualetti c6e93c266a feat(packages/tuono): config – add optimizeDeps and plugins to vite options (#197) 2024-12-08 10:44:09 +01:00
Valerio Ageno bcf7e5b5ab docs: add link and navigation page content (#211)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2024-12-08 10:18:20 +01:00
Valerio Ageno 466f122cc4 feat: update version to v0.16.0 2024-12-08 10:13:45 +01:00
Valerio Ageno 539484ea25 docs: fill defining-routes page (#207)
Co-authored-by: Marco Pasqualetti <24919330+marcalexiei@users.noreply.github.com>
2024-12-07 14:51:57 +01:00
Valerio Ageno db1cee25bf test: add integration tests for api/ build (#208) 2024-12-07 14:17:59 +01:00
Marco Pasqualetti 96502b18d0 refactor(apps/documentation): remove lint errors (#206) 2024-12-07 12:28:57 +01:00
Valerio Ageno c491ece8c5 refactor: revamp documentation (#196)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
2024-12-07 12:06:08 +01:00
Marco Pasqualetti c76464bf8a refactor(typescript): use generic instead of simple array type (#205) 2024-12-07 11:58:58 +01:00
Marco Pasqualetti 08b75b1a79 chore(eslint): remove build folder exclusion (#202) 2024-12-07 11:42:38 +01:00
Valerio Ageno 90da869277 feat: update version to v0.15.1 2024-12-07 11:14:13 +01:00
Valerio Ageno 80c4affaa8 feat: add cookies to the response (#201) 2024-12-07 11:10:47 +01:00
Marco Pasqualetti aa087193fe fix(crates/tuono_lib_macros): remove Cargo.tml warnings (#200) 2024-12-06 19:00:54 +01:00
Marco Pasqualetti af7e51f69b chore(ci): setup rust consistently across all workflows (#198) 2024-12-06 14:05:03 +01:00
Marco Pasqualetti 1b62d8ddc2 refactor(crates/tuono): build - ssg - disable zombie_processes warning and use TUONO_PORT to generate server URL (#199) 2024-12-06 13:05:24 +01:00
Valerio Ageno 0839579733 fix: documentation burger menu 2024-12-03 19:38:58 +01:00
Nayan Radadiya 7f9bf08f76 Add ️ as default favicon for documentation and examples (#191) (#192) 2024-12-03 19:35:25 +01:00
Valerio Ageno 79ec1c6922 feat: remove home page from documentation website (#187)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
2024-12-03 16:29:30 +01:00
Valerio Ageno c68246df83 chore: update contributing documentation (#194) 2024-12-03 16:08:49 +01:00
Marco Pasqualetti abc0ad7120 chore(github actions): refine on paths (#189) 2024-12-03 15:59:10 +01:00
Valerio Ageno 4356ca1cff chore: update CONTRIBUTING.md 2024-12-03 14:03:41 +01:00
Valerio Ageno d6215d009c Update health_check.rs 2024-12-03 09:51:42 +01:00
Marco Pasqualetti 0b501e95a5 chore(apps/documentation): migrate to eslint 9 (#188) 2024-12-01 16:40:58 +01:00
Marco Pasqualetti f157ff37e0 refactor(packages/router): removed unused code (#184) 2024-12-01 11:03:12 +01:00
Marco Pasqualetti 11d425faa9 chore(deps-dev): update eslint to v9 (#147) 2024-12-01 10:07:35 +01:00
Valerio Ageno 5fc68a1926 feat: update documentation to v0.15.0 and fix aliases (#182)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
2024-11-30 21:37:02 +01:00
Valerio Ageno ec4577c187 feat: add tuono.config.ts support (#153)
Co-authored-by: Marco Pasqualetti <marco.pasqualetti@live.com>
2024-11-30 19:58:04 +01:00
Valerio Ageno 8052de8ad5 feat: update version to v0.15.0 (#181) 2024-11-30 19:03:01 +01:00
Valerio Ageno 2fda959cd6 feat: update tuono new repository reference (#178)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
2024-11-30 14:16:18 +01:00
Valerio Ageno 05fa8a5ae2 feat: update docs and readme repo links (#180) 2024-11-30 14:16:10 +01:00
Valerio Ageno a341b41e9a chore: update repo reference withing manifests (#179)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
2024-11-30 14:15:36 +01:00
Valerio Ageno a2e0a4d78a feat: update version to v0.14.6 2024-11-30 14:13:21 +01:00
Valerio Ageno 4fb1d6077c Add tuono cli integration test (#177)
Co-authored-by: KingTheSim <KingTheSim@abv.bg>
Co-authored-by: KingTheSim <122887037+KingTheSim@users.noreply.github.com>
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
2024-11-29 16:34:56 +01:00
Valerio Ageno 53ab927544 feat: update version to v0.14.5 2024-11-27 21:07:50 +01:00
Valerio Ageno 897cc2daf2 feat: add --no-js-emit flag to tuono build command (#175)
Co-authored-by: Valerio Ageno <valerio.ageno@qonto.com>
2024-11-27 21:05:45 +01:00
Marco Pasqualetti 73ff31daf5 ci(rust): avoid beta and nightly toolchains to be executed on macos and windows (#174) 2024-11-27 20:40:00 +01:00
Marco Pasqualetti c387a9e17e chore(renovate): enable schedule (#172) 2024-11-27 15:33:14 +01:00
246 changed files with 4609 additions and 2740 deletions
+7
View File
@@ -0,0 +1,7 @@
[*]
charset = utf-8
insert_final_newline = true
end_of_line = lf
indent_style = space
indent_size = 2
max_line_length = 80
-106
View File
@@ -1,106 +0,0 @@
{
"root": true,
"reportUnusedDisableDirectives": true,
"ignorePatterns": ["**/build", "**/dist"],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "import"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/stylistic",
"plugin:import/recommended",
"plugin:import/typescript",
"prettier",
],
"env": {
"browser": true,
"es2020": true,
},
"parserOptions": {
"tsconfigRootDir": ".",
"project": true,
"sourceType": "module",
"ecmaVersion": 2020,
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".tsx"],
},
"import/resolver": {
"typescript": true,
},
"react": {
"version": "detect",
},
},
"rules": {
"@typescript-eslint/array-type": "error",
"@typescript-eslint/no-wrapper-object-types": "error",
"@typescript-eslint/no-empty-object-type": "error",
"@typescript-eslint/no-unsafe-function-type": "error",
"@typescript-eslint/ban-ts-comment": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/consistent-type-imports": [
"error",
{ "prefer": "type-imports" },
],
"@typescript-eslint/explicit-module-boundary-types": "error",
"@typescript-eslint/method-signature-style": ["error", "property"],
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "typeParameter",
"format": ["PascalCase"],
"leadingUnderscore": "forbid",
"trailingUnderscore": "forbid",
"custom": {
"regex": "^(T|T[A-Z][A-Za-z]+)$",
"match": true,
},
},
],
"@typescript-eslint/no-deprecated": "error",
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/no-inferrable-types": [
"error",
{ "ignoreParameters": true },
],
"import/default": "error",
"import/export": "error",
"import/namespace": "error",
"import/newline-after-import": "error",
"import/no-cycle": "error",
"import/no-duplicates": "off",
"import/no-named-as-default-member": "error",
"import/no-unused-modules": "error",
"import/order": [
"off",
{
"groups": [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
"type",
],
},
],
"no-case-declarations": "error",
"no-empty": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-shadow": "error",
"no-undef": "off",
"sort-imports": "off",
},
}
+14 -45
View File
@@ -32,55 +32,24 @@ body:
description: If applicable, add screenshots to help explain your problem.
validations:
required: false
- type: input
id: tuono-version
- type: textarea
id: system-info
attributes:
label: Tuono version
placeholder: '[e.g. 0.4.0]'
label: System Info
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
render: shell
placeholder: System, Binaries, Browsers
validations:
required: false
- type: input
id: os-version
required: true
- type: textarea
id: system-info-rust
attributes:
label: OS
placeholder: '[e.g. MacOS, Windows]'
label: System info (Rust)
description: Output of `rustc --version && cargo --version && tuono --version`
render: shell
placeholder: 'rustc, cargo and tuono binary version'
validations:
required: false
- type: input
id: browser-version
attributes:
label: Browser
placeholder: '[e.g. chrome, safari]'
validations:
required: false
- type: input
id: node-version
attributes:
label: Node version
placeholder: '[e.g. 20.0.0]'
validations:
required: false
- type: input
id: rust-version
attributes:
label: Rust version
placeholder: '[e.g. 1.79.0]'
validations:
required: false
- type: input
id: create-version
attributes:
label: Crate version
placeholder: '[e.g. 1.78.0]'
validations:
required: false
- type: input
id: node-package-manager-version
attributes:
label: Node Package Manger version
placeholder: '[e.g. pnpm: 9.5.0] '
validations:
required: false
required: true
- type: textarea
id: additional-context
attributes:
+10 -2
View File
@@ -3,13 +3,21 @@ name: Documentation Website CI
on:
push:
branches:
- main
- 'main'
paths:
# ⬇ Keep paths in sync with `typescript.yml`
- '.github/**'
- '*'
- '!Cargo.toml'
# ⬆ Keep paths in sync with `typescript.yml`
- 'apps/documentation/**'
pull_request:
paths:
# ⬇ Keep paths in sync with `typescript.yml`
- '.github/**'
- '*'
- '!Cargo.toml'
# ⬆ Keep paths in sync with `typescript.yml`
- 'apps/documentation/**'
jobs:
@@ -29,7 +37,7 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install tuono
run: cargo install tuono@0.12.3
run: cargo install tuono@0.16.4
- name: Build project
working-directory: ./apps/documentation
+2 -2
View File
@@ -3,7 +3,7 @@ name: Deploy documentation website on AWS S3
on:
push:
branches:
- main
- 'main'
paths:
- '.github/**'
- 'apps/documentation/**'
@@ -27,7 +27,7 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install tuono
run: cargo install tuono@0.12.3
run: cargo install tuono@0.16.4
- name: Build project
working-directory: ./apps/documentation
+6 -6
View File
@@ -34,10 +34,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
- name: Setup rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
toolchain: 'stable'
- uses: katyo/publish-crates@v2
with:
@@ -80,10 +80,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
- name: Setup rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
override: true
toolchain: 'stable'
- uses: katyo/publish-crates@v2
with:
+4 -3
View File
@@ -1,4 +1,5 @@
name: Repo root CI
# All workflows are triggered based on specific paths,
# the root repository is something that is not applicable to rust nor typescript
# so it has been moved in a standalone workflow
@@ -6,14 +7,14 @@ name: Repo root CI
on:
push:
branches:
- main
- 'main'
paths:
- '.github/**'
- './*'
- '*'
pull_request:
paths:
- '.github/**'
- './*'
- '*'
jobs:
build-and-test:
+21 -6
View File
@@ -3,13 +3,15 @@ name: Rust CI
on:
push:
branches:
- main
- 'main'
paths:
- '.github/**'
- 'Cargo.toml'
- 'crates/**'
pull_request:
paths:
- '.github/**'
- 'Cargo.toml'
- 'crates/**'
env:
@@ -17,7 +19,7 @@ env:
jobs:
build_and_test:
name: Build and test rust crates
name: Build and test crates on ${{ matrix.os }} with rust ${{ matrix.toolchain }}
strategy:
fail-fast: true
@@ -27,9 +29,15 @@ jobs:
- 'macos-latest'
- 'windows-latest'
toolchain:
- stable
- beta
- nightly
- 'stable'
# Test `beta` and `nightly` toolchains only on `ubuntu`
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#expanding-or-adding-matrix-configurations
include:
- toolchain: 'beta'
os: 'ubuntu-latest'
- toolchain: 'nightly'
os: 'ubuntu-latest'
runs-on: ${{ matrix.os }}
@@ -37,7 +45,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- name: Setup rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
- run: cargo build --verbose
- run: cargo test --verbose
@@ -49,5 +61,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
- run: cargo fmt --all -- --check
- run: cargo clippy -- -D warnings
+1 -1
View File
@@ -3,7 +3,7 @@ name: Spell Checker
on:
push:
branches:
- main
- 'main'
pull_request:
+9 -1
View File
@@ -3,13 +3,21 @@ name: Typescript CI
on:
push:
branches:
- main
- 'main'
paths:
# ⬇ Keep paths in sync with `ci-documentation.yml`
- '.github/**'
- '*'
- '!Cargo.toml'
# ⬆ Keep paths in sync with `ci-documentation.yml`
- 'packages/**'
pull_request:
paths:
# ⬇ Keep paths in sync with `ci-documentation.yml`
- '.github/**'
- '*'
- '!Cargo.toml'
# ⬆ Keep paths in sync with `ci-documentation.yml`
- 'packages/**'
jobs:
+5
View File
@@ -8,6 +8,8 @@ dist/
examples/*/pnpm-lock.yaml
vite.config.ts.timestamp*
## Rust related ignores
# Generated by Cargo
@@ -24,3 +26,6 @@ examples/*/pnpm-lock.yaml
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Files, generated by the code editors
.vscode/
+6 -1
View File
@@ -1 +1,6 @@
pnpm-lock.yaml
pnpm-lock.yaml
dist
.tuono
packages/tuono-lazy-fn-vite-plugin/tests/sources/*
+9
View File
@@ -0,0 +1,9 @@
* @Valerioageno @marcalexiei
# Rust
/crates/ @Valerioageno
/Cargo.toml @Valerioageno
# Misc
/.github/ @marcalexiei
+10 -6
View File
@@ -1,5 +1,5 @@
![](https://github.com/Valerioageno/tuono/actions/workflows/rust.yml/badge.svg)
![](https://github.com/Valerioageno/tuono/actions/workflows/typescript.yml/badge.svg)
![](https://github.com/tuono-labs/tuono/actions/workflows/rust.yml/badge.svg)
![](https://github.com/tuono-labs/tuono/actions/workflows/typescript.yml/badge.svg)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
# How to Contribute to Tuono
@@ -7,15 +7,19 @@
## Contributions
Any feature contribution or suggestion is strongly appreciated.
Since the current project size, there isn't yet a defined way to start a discussion. Consider [opening a new issue](https://github.com/Valerioageno/tuono/issues/new/choose)
or to reach me using my email address, [valerioageno@yahoo.it](mailto:valerioageno@ahoo.it). I'm also available on Twitter (X); DM @valerioageno.
We are currently managing the tasks' prioritization in the [Road to V1](https://github.com/orgs/tuono-labs/projects/2) project. You can pick any task that you see in the `ready` column that is not already assigned.
If you want to contribute on something that is in the `backlog` column, or it hasn't been scoped yet, please drop a message in the [#general](https://discord.com/invite/khQzPa654B) discord channel so we can all have visibility of it.
Consider [opening a new issue](https://github.com/tuono-labs/tuono/issues/new/choose) if you find a bug.
## Bugs
**Did you find a bug?**
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/Valerioageno/tuono/issues).
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/Valerioageno/tuono/issues/new/choose). Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/tuono-labs/tuono/issues).
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/tuono-labs/tuono/issues/new/choose). Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
**Did you write a patch that fixes a bug?**
+1 -1
View File
@@ -7,7 +7,7 @@ members = [
]
exclude = [
"apps/documentation",
"examples/wit-mdx",
"examples/with-mdx",
"examples/tuono-app",
"examples/tuono-tutorial",
"benches/tuono"
+6 -6
View File
@@ -3,10 +3,10 @@
# Tuono
<img align="right" src="https://raw.githubusercontent.com/Valerioageno/tuono/main/assets/logo.png" width="160px">
<img align="right" src="https://raw.githubusercontent.com/tuono-labs/tuono/main/assets/logo.png" width="160px">
![](https://github.com/Valerioageno/tuono/actions/workflows/rust.yml/badge.svg)
![](https://github.com/Valerioageno/tuono/actions/workflows/typescript.yml/badge.svg)
![](https://github.com/tuono-labs/tuono/actions/workflows/rust.yml/badge.svg)
![](https://github.com/tuono-labs/tuono/actions/workflows/typescript.yml/badge.svg)
**Tuono is a full-stack web framework for building React applications using Rust as the backend with
a strong focus on usability and performance.**
@@ -26,7 +26,7 @@ Some of its features are:
## 📖 Documentation
The [documentation](https://tuono.dev/documentation) is available on
The [documentation](https://tuono.dev/) is available on
[tuono.dev](https://tuono.dev/).
## Introduction
@@ -48,7 +48,7 @@ by Tuono based on the files defined within the `./src/routes` directory.
The Tuono API tries to stick as much as possible to the Next.js one (or at least takes a huge inspiration
from it). The major difference is the backend system. While Next.js relies entirely on Node/Deno/Bun,
Tuono runs the server without any intermediary runtime. This enables impressive performance improvements
(check the benchmarks [here](https://github.com/Valerioageno/tuono/tree/main/benches)).
(check the benchmarks [here](https://github.com/tuono-labs/tuono/tree/main/benches)).
## Getting started
@@ -61,7 +61,7 @@ Then run `cargo install tuono`.
To list all the available commands, run `tuono -h`
To create a new project, run `tuono new [NAME]` (optionally, you can pass the `--template` (or `-t`) flag - check the
[examples](https://github.com/Valerioageno/tuono/tree/main/examples) folder).
[examples](https://github.com/tuono-labs/tuono/tree/main/examples) folder).
Then, to run the local development environment, install the JS dependencies (`npm install`) and run inside the project folder `tuono dev`
-106
View File
@@ -1,106 +0,0 @@
{
"root": true,
"reportUnusedDisableDirectives": true,
"ignorePatterns": ["**/build", "**/dist"],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "import"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/stylistic",
"plugin:import/recommended",
"plugin:import/typescript",
"prettier",
],
"env": {
"browser": true,
"es2020": true,
},
"parserOptions": {
"tsconfigRootDir": ".",
"project": true,
"sourceType": "module",
"ecmaVersion": 2020,
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".tsx"],
},
"import/resolver": {
"typescript": true,
},
"react": {
"version": "detect",
},
},
"rules": {
"@typescript-eslint/array-type": "error",
"@typescript-eslint/no-wrapper-object-types": "error",
"@typescript-eslint/no-empty-object-type": "error",
"@typescript-eslint/no-unsafe-function-type": "error",
"@typescript-eslint/ban-ts-comment": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/consistent-type-imports": [
"error",
{ "prefer": "type-imports" },
],
"@typescript-eslint/explicit-module-boundary-types": "error",
"@typescript-eslint/method-signature-style": ["error", "property"],
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "typeParameter",
"format": ["PascalCase"],
"leadingUnderscore": "forbid",
"trailingUnderscore": "forbid",
"custom": {
"regex": "^(T|T[A-Z][A-Za-z]+)$",
"match": true,
},
},
],
"@typescript-eslint/no-deprecated": "error",
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/no-inferrable-types": [
"error",
{ "ignoreParameters": true },
],
"import/default": "error",
"import/export": "error",
"import/namespace": "error",
"import/newline-after-import": "error",
"import/no-cycle": "error",
"import/no-duplicates": "off",
"import/no-named-as-default-member": "error",
"import/no-unused-modules": "error",
"import/order": [
"off",
{
"groups": [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
"type",
],
},
],
"no-case-declarations": "error",
"no-empty": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-shadow": "error",
"no-undef": "off",
"sort-imports": "off",
},
}
+1 -1
View File
@@ -8,7 +8,7 @@ name = "tuono"
path = ".tuono/main.rs"
[dependencies]
tuono_lib = "0.12.3"
tuono_lib = "0.16.4"
glob = "0.3.1"
time = { version = "0.3", features = ["macros"] }
serde = { version = "1.0.202", features = ["derive"] }
+17 -16
View File
@@ -3,29 +3,30 @@
"description": "The react/rust fullstack framework documentation",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"lint": "eslint --ext .ts,.tsx ./src -c .eslintrc",
"format": "prettier -u --write --ignore-unknown './src/**/*'",
"format:check": "prettier --check --ignore-unknown './src/**/*'",
"lint": "eslint .",
"format": "prettier --write --ignore-unknown .",
"format:check": "prettier --check --ignore-unknown .",
"types": "tsc --noEmit"
},
"dependencies": {
"@mantine/code-highlight": "^7.11.2",
"@mantine/core": "^7.11.2",
"@mantine/hooks": "^7.11.2",
"@mdx-js/react": "^3.0.1",
"@tabler/icons-react": "^3.11.0",
"clsx": "^2.1.1",
"@mantine/code-highlight": "7.14.2",
"@mantine/core": "7.14.2",
"@mantine/hooks": "7.14.2",
"@mdx-js/react": "3.1.0",
"@tabler/icons-react": "3.22.0",
"clsx": "2.1.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"tuono": "npm:tuono@0.12.3"
"tuono": "npm:tuono@0.16.4"
},
"devDependencies": {
"@types/mdx": "^2.0.13",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"postcss": "^8.4.39",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1"
"@mdx-js/rollup": "3.1.0",
"@types/react": "18.3.13",
"@types/react-dom": "18.3.0",
"postcss": "8.4.49",
"postcss-preset-mantine": "1.17.0",
"postcss-simple-vars": "7.0.1"
}
}
@@ -1,4 +1,4 @@
module.exports = {
export default {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 709 B

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,18 @@
{
"polyfills": [
{
"identifier": "HTMLElement",
"import": {
"package": "@lit-labs/ssr-dom-shim",
"name": "HTMLElement"
}
},
{
"identifier": "customElements",
"import": {
"package": "@lit-labs/ssr-dom-shim",
"name": "customElements"
}
}
]
}
+19 -1
View File
@@ -1 +1,19 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
@@ -9,7 +9,7 @@ interface Breadcrumb {
label: string
}
interface BreadcrumbsProps {
breadcrumbs: Breadcrumb[]
breadcrumbs: Array<Breadcrumb>
}
export default function TuonoBreadcrumbs({
@@ -28,12 +28,6 @@ export default function TuonoBreadcrumbs({
"position": 1,
"name": "Tuono - The React/Rust fullstack framework",
"item": "https://tuono.dev"
},
{
"@type": "ListItem",
"position": 2,
"name": "Tuono - Documentation",
"item": "https://tuono.dev/documentation"
}${breadcrumbs.length > 0 ? ',' : ''}
${breadcrumbs
.map((br, i) =>
@@ -54,13 +48,7 @@ export default function TuonoBreadcrumbs({
mb="md"
mt="md"
>
<Button
href="/documentation"
component={Link}
variant="subtle"
radius="xl"
p={5}
>
<Button href="/" component={Link} variant="subtle" radius="xl" p={5}>
<IconBolt />
</Button>
{breadcrumbs.map((br) => (
@@ -4,7 +4,7 @@ import { IconEdit } from '@tabler/icons-react'
import { useRouter } from 'tuono'
const GITHUB_URL =
'https://github.com/Valerioageno/tuono/tree/main/apps/documentation/src/routes'
'https://github.com/tuono-labs/tuono/tree/main/apps/documentation/src/routes'
export default function EditPage(): JSX.Element {
const { pathname } = useRouter()
@@ -1,9 +1,8 @@
import type { JSX } from 'react'
import type { JSX, HTMLAttributes } from 'react'
import { Code } from '@mantine/core'
import type { HTMLAttributes } from 'react'
export default function MdxCode(
props: HTMLAttributes<HTMLPreElement>,
): JSX.Element {
return <Code {...props} style={{ fontSize: 14 }} />
return <Code {...props} style={{ fontSize: 'inherit' }} />
}
@@ -1,10 +1,17 @@
import type { JSX } from 'react'
import { CodeHighlight } from '@mantine/code-highlight'
import styles from './mdx-pre.module.css'
interface PreProps {
children: any
children: {
props: {
children: string
className?: string
}
}
}
export default function MdxPre({ children }: PreProps): JSX.Element {
return (
<CodeHighlight
@@ -1,8 +1,7 @@
import type { JSX } from 'react'
import type { JSX, ReactNode } from 'react'
import { MDXProvider } from '@mdx-js/react'
import MdxLink from './mdx-link'
import type { ReactNode } from 'react'
import MdxPre from './mdx-pre'
import MdxQuote from './mdx-quote'
import MdxCode from './mdx-code'
@@ -8,6 +8,7 @@ export default function MdxTitle(props: TitleProps): JSX.Element {
data-order={props.order}
mt={20}
{...props}
// eslint-disable-next-line @typescript-eslint/no-base-to-string
id={String(props.children ?? '')
.toLowerCase()
.replaceAll(' ', '-')}
@@ -15,6 +16,12 @@ export default function MdxTitle(props: TitleProps): JSX.Element {
)
}
export const h =
(order: 1 | 2 | 3 | 4 | 5 | 6) =>
(props: TitleProps): JSX.Element => <MdxTitle order={order} {...props} />
export const h = (
order: 1 | 2 | 3 | 4 | 5 | 6,
): React.ElementType<TitleProps> => {
function render(props: TitleProps): JSX.Element {
return <MdxTitle order={order} {...props} />
}
render.displayName = 'H'
return render
}
@@ -1,11 +1,6 @@
import type { JSX } from 'react'
import { Flex, Button, ActionIcon, Group } from '@mantine/core'
import {
IconBrandGithub,
IconBook,
IconBrandDiscord,
} from '@tabler/icons-react'
import { Link } from 'tuono'
import { Flex, ActionIcon, Group } from '@mantine/core'
import { IconBrandGithub, IconBrandDiscord } from '@tabler/icons-react'
import ThemeBtn from '../theme-btn'
@@ -13,21 +8,11 @@ export default function Actions(): JSX.Element {
return (
<Flex gap={8}>
<Group gap={8}>
<Button
component={Link}
href="/documentation"
visibleFrom="sm"
size="compact-lg"
rightSection={<IconBook />}
autoContrast
>
Get started
</Button>
<ActionIcon
variant="default"
size="lg"
aria-label="Check the project on github"
href="https://github.com/Valerioageno/tuono"
href="https://github.com/tuono-labs/tuono"
target="_blank"
component="a"
>
@@ -1,32 +1,31 @@
import type { JSX } from 'react'
import { AppShell, Burger, Button, Flex } from '@mantine/core'
import { Link, useRouter } from 'tuono'
import { AppShell, Box, Burger, Button, Flex } from '@mantine/core'
import { Link } from 'tuono'
import Actions from './actions'
interface NavbarProps {
opened: boolean
toggle: () => void
}
export default function Navbar({ opened, toggle }: NavbarProps): JSX.Element {
const { pathname } = useRouter()
export default function Navbar({ toggle }: NavbarProps): JSX.Element {
return (
<AppShell.Header p="sm">
<Flex justify="space-between">
<Button component={Link} href="/" variant="transparent" p={0} fz={28}>
<Button
component={Link}
href="/"
variant="transparent"
p={0}
fz={28}
hiddenFrom="sm"
>
Tuono
</Button>
<Box />
<Flex align="center" gap={8}>
<Actions />
{pathname.startsWith('/documentation') && (
<Burger
opened={opened}
onClick={toggle}
hiddenFrom="sm"
size="sm"
/>
)}
<Burger onClick={toggle} hiddenFrom="sm" size="sm" />
</Flex>
</Flex>
</AppShell.Header>
@@ -0,0 +1,265 @@
import type { ReactNode } from 'react'
interface SidebarElementBase<T extends string> {
type: T
}
type SidebarDivider = SidebarElementBase<'divider'>
interface SidebarLink extends SidebarElementBase<'element'> {
label: string
href: string
children?: Array<SidebarLink>
leftIcon?: ReactNode
}
interface SidebarTitle extends SidebarElementBase<'title'> {
label: string
}
type SidebarElement = SidebarDivider | SidebarLink | SidebarTitle
export const sidebarElements: Array<SidebarElement> = [
{
type: 'element',
label: 'Home',
href: '/',
},
{
type: 'element',
label: 'Getting started',
href: '#focus',
children: [
{
type: 'element',
label: 'Installation',
href: '/documentation/installation',
},
{
type: 'element',
label: 'How is tuono different?',
href: '/documentation/how-is-tuono-different',
},
],
},
{
type: 'element',
label: 'Tutorial',
href: '#focus',
children: [
{
type: 'element',
href: '/documentation/tutorial',
label: 'Intro',
},
{
type: 'element',
href: '/documentation/tutorial/development-setup',
label: 'Development setup',
},
{
type: 'element',
href: '/documentation/tutorial/api-fetching',
label: 'API fetching',
},
{
type: 'element',
href: '/documentation/tutorial/components',
label: 'Components',
},
{
type: 'element',
href: '/documentation/tutorial/dynamic-routes',
label: 'Dynamic routes',
},
{
type: 'element',
href: '/documentation/tutorial/error-handling',
label: 'Error handling',
},
{
type: 'element',
href: '/documentation/tutorial/seo',
label: 'SEO and meta tags',
},
{
type: 'element',
href: '/documentation/tutorial/redirections',
label: 'Redirections',
},
{
type: 'element',
href: '/documentation/tutorial/production',
label: 'Production build',
},
{
type: 'element',
href: '/documentation/tutorial/conclusion',
label: 'Conclusion',
},
],
},
{ type: 'divider' },
{ type: 'title', label: 'Overview' },
{
type: 'element',
label: 'Routing',
href: '#focus',
children: [
{
type: 'element',
label: 'Defining routes',
href: '/documentation/routing/defining-routes',
},
{
type: 'element',
label: 'Dynamic routes',
href: '/documentation/routing/dynamic-routes',
},
{
type: 'element',
label: 'Link and navigation',
href: '/documentation/routing/link-and-navigation',
},
{
type: 'element',
label: 'Pages',
href: '/documentation/routing/pages',
},
{
type: 'element',
label: 'Loading state',
href: '/documentation/routing/loading-state',
},
{
type: 'element',
label: 'Layouts',
href: '/documentation/routing/layouts',
},
{
type: 'element',
label: 'Redirecting',
href: '/documentation/routing/redirecting',
},
],
},
{
type: 'element',
label: 'Rendering',
href: '#focus',
children: [
{
type: 'element',
label: 'Server side rendering',
href: '/documentation/rendering/server-side-rendering',
},
{
type: 'element',
label: 'Static site rendering',
href: '/documentation/rendering/static-site-rendering',
},
],
},
{
type: 'element',
label: 'Styles',
href: '#focus',
children: [
{
type: 'element',
label: 'CSS modules',
href: '/documentation/styles/css-modules',
},
],
},
{
type: 'element',
label: 'Integrations',
href: '#focus',
children: [
{
type: 'element',
label: 'MDX',
href: '/documentation/integrations/mdx',
},
],
},
{
type: 'element',
label: 'Core concepts',
href: '#focus',
children: [
{
type: 'element',
label: 'Multithreading',
href: '/documentation/core-concepts/multithreading',
},
],
},
{ type: 'divider' },
{ type: 'title', label: 'API reference' },
{
type: 'element',
label: 'Components',
href: '#focus',
children: [
{
type: 'element',
label: 'Head',
href: '/documentation/components/head',
},
{
type: 'element',
label: 'Link',
href: '/documentation/components/link',
},
],
},
{
type: 'element',
label: 'Hooks',
href: '#focus',
children: [
{
type: 'element',
label: 'useRouter',
href: '/documentation/hooks/use-router',
},
],
},
{
type: 'element',
label: 'CLI',
href: '/documentation/cli',
},
{
type: 'element',
label: 'Application state',
href: '/documentation/application-state',
},
{
type: 'element',
label: 'tuono.config.ts',
href: '/documentation/configuration',
},
{ type: 'divider' },
{
type: 'element',
label: 'Contributing',
href: '#focus',
leftIcon: '✨',
children: [
{
type: 'element',
label: 'Guildelines',
href: '/documentation/contributing',
},
{
type: 'element',
label: 'Local development',
href: '/documentation/contributing/local-development',
},
],
},
]
@@ -1,6 +1,17 @@
.link {
border-radius: 8px;
margin-top: 0.25rem;
line-height: 1.25rem;
line-height: 1rem;
font-weight: 500;
transition: 0.3s;
}
.link span {
font-size: 14px !important;
color: var(--mantine-color-sidebar-gray);
}
.link:hover span,
.active span {
color: var(--mantine-color-sidebar-text-hover);
}
@@ -1,6 +1,6 @@
import type { JSX, ReactNode } from 'react'
import { useState } from 'react'
import { NavLink, type NavLinkProps } from '@mantine/core'
import clsx from 'clsx'
import { Link, useRouter } from 'tuono'
import { IconChevronRight } from '@tabler/icons-react'
@@ -17,24 +17,19 @@ export default function SidebarLink(
props: SidebarLinkProps & NavLinkProps,
): JSX.Element {
const { pathname } = useRouter()
const [isOpen, setIsOpen] = useState<boolean>(!!props.defaultOpened)
const isActive = pathname === props.href
const internalProps = {
active: pathname === props.href,
className: styles.link,
active: isActive,
className: clsx(styles.link, isActive && styles.active),
rightSection: props.children && (
<IconChevronRight
size="1.2rem"
stroke={1.5}
className="mantine-rotate-rtl"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setIsOpen((state) => !state)
}}
/>
),
opened: isOpen,
autoContrast: true,
...props,
}
@@ -1,108 +1,111 @@
import type { JSX } from 'react'
import { AppShell } from '@mantine/core'
import {
AppShell,
Badge,
Flex,
Divider,
Title,
Button,
ScrollArea,
Text,
} from '@mantine/core'
import { IconX } from '@tabler/icons-react'
import { useMediaQuery } from '@mantine/hooks'
import { sidebarElements } from './config'
import SidebarLink from './sidebar-link'
interface SidebarProps {
close: () => void
}
function SidebarHeader({ close }: SidebarProps): JSX.Element {
const isSm = useMediaQuery('(min-width: 48em)')
return (
<AppShell.Section>
<Flex mb={20} w="100%" justify="space-between">
<Flex
gap={10}
align="center"
w={isSm ? '100%' : 'auto'}
justify="center"
>
<Title order={3}>Tuono</Title>
<Badge mt={4} size="xs" variant="outline">
Docs
</Badge>
</Flex>
<Button onClick={close} hiddenFrom="sm" variant="transparent" p="0">
<IconX />
</Button>
</Flex>
</AppShell.Section>
)
}
function SidebarElements({ close }: SidebarProps): JSX.Element {
return (
<AppShell.Section component={ScrollArea}>
{sidebarElements.map((el, i) => {
if (el.type === 'divider') {
return <Divider key={`${el.type}-${i}`} my="md" mx={10} />
}
if (el.type === 'title') {
return (
<Text
key={`${el.type}-${i}`}
size="xs"
fw={700}
fz={12}
pl={12}
py={5}
>
{el.label}
</Text>
)
}
if (el.children?.length) {
return (
<SidebarLink
key={`${el.type}-${i}`}
href={el.href}
label={el.label}
leftSection={el.leftIcon}
>
{el.children.map((child, index) => (
<SidebarLink
href={child.href}
label={child.label}
key={index}
onClick={close}
/>
))}
</SidebarLink>
)
}
return (
<SidebarLink
key={`${el.type}-${i}`}
href={el.href}
label={el.label}
onClick={close}
/>
)
})}
</AppShell.Section>
)
}
export default function Sidebar({ close }: SidebarProps): JSX.Element {
return (
<AppShell.Navbar p="md">
<SidebarLink
href="/documentation"
label="Getting started"
onClick={close}
/>
<SidebarLink
href="/documentation/installation"
label="Installation"
onClick={close}
/>
<SidebarLink
href="/documentation/tutorial"
label="Tutorial"
defaultOpened
onClick={close}
>
<SidebarLink
href="/documentation/tutorial/development-setup"
label="Development setup"
onClick={close}
/>
<SidebarLink
href="/documentation/tutorial/api-fetching"
label="API fetching"
onClick={close}
/>
<SidebarLink
href="/documentation/tutorial/components"
label="Components"
onClick={close}
/>
<SidebarLink
href="/documentation/tutorial/dynamic-routes"
label="Dynamic routes"
onClick={close}
/>
<SidebarLink
href="/documentation/tutorial/error-handling"
label="Error handling"
onClick={close}
/>
<SidebarLink
href="/documentation/tutorial/seo"
label="SEO and meta tags"
onClick={close}
/>
<SidebarLink
href="/documentation/tutorial/redirections"
label="Server redirection"
onClick={close}
/>
<SidebarLink
href="/documentation/tutorial/production"
label="Production build"
onClick={close}
/>
<SidebarLink
href="/documentation/tutorial/conclusion"
label="Conclusion"
onClick={close}
/>
</SidebarLink>
<SidebarLink href="/documentation/cli" label="CLI" onClick={close} />
<SidebarLink
label="Application state"
href="/documentation/application-state"
onClick={close}
/>
<SidebarLink
label="Routing"
href="/documentation/routing"
onClick={close}
>
<SidebarLink
href="/documentation/routing/intro"
label="Project structure"
onClick={close}
/>
</SidebarLink>
<SidebarLink
label="Contributing"
href="/documentation/contributing"
leftSection="✨"
onClick={close}
>
<SidebarLink
href="/documentation/contributing/local-development"
label="Local development"
onClick={close}
/>
</SidebarLink>
<SidebarHeader close={close} />
<SidebarElements close={close} />
</AppShell.Navbar>
)
}
@@ -8,8 +8,8 @@ export interface Heading {
getNode: () => HTMLHeadingElement
}
function getHeadingsData(headings: HTMLHeadingElement[]): Heading[] {
const result: Heading[] = []
function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
const result: Array<Heading> = []
for (const heading of headings) {
if (heading.id) {
@@ -26,7 +26,7 @@ function getHeadingsData(headings: HTMLHeadingElement[]): Heading[] {
return result
}
export function getHeadings(): Heading[] {
export function getHeadings(): Array<Heading> {
const root = document.getElementById('mdx-root')
console.log(root)
@@ -6,6 +6,7 @@ import { useEffect, useRef, useState } from 'react'
import { useRouter, Link } from 'tuono'
import { IconList } from '@tabler/icons-react'
import { Box, rem, ScrollArea, Text } from '@mantine/core'
import { getHeadings, type Heading } from './get-headings'
import classes from './table-of-content.module.css'
@@ -13,7 +14,7 @@ interface TableOfContentsProps {
withTabs: boolean
}
function getActiveElement(rects: DOMRect[]): number {
function getActiveElement(rects: Array<DOMRect>): number {
if (rects.length === 0) {
return -1
}
@@ -39,8 +40,8 @@ export function TableOfContents({
withTabs,
}: TableOfContentsProps): JSX.Element | null {
const [active, setActive] = useState(0)
const [headings, setHeadings] = useState<Heading[]>([])
const headingsRef = useRef<Heading[]>([])
const [headings, setHeadings] = useState<Array<Heading>>([])
const headingsRef = useRef<Array<Heading>>([])
const router = useRouter()
const filteredHeadings = headings.filter((heading) => heading.depth > 1)
@@ -63,7 +64,9 @@ export function TableOfContents({
),
)
window.addEventListener('scroll', handleScroll)
return (): void => window.removeEventListener('scroll', handleScroll)
return (): void => {
window.removeEventListener('scroll', handleScroll)
}
}, [router.pathname])
if (filteredHeadings.length === 0) {
@@ -17,9 +17,9 @@ export default function ThemeBtn(): JSX.Element {
return (
<ActionIcon
onClick={() =>
onClick={() => {
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
}
}}
variant="default"
size="lg"
aria-label="Toggle color scheme"
+4 -1
View File
@@ -1,2 +1,5 @@
// declaration.d.ts
declare module '*.css'
declare module '*.css' {
const CSSModule: Record<string, string>
export default CSSModule
}
+40 -11
View File
@@ -1,19 +1,24 @@
import type { ReactNode, JSX } from 'react'
import {
ColorSchemeScript,
createTheme,
MantineProvider,
AppShell,
Container,
} from '@mantine/core'
import type { CSSVariablesResolver } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { Head, useRouter } from 'tuono'
import Navbar from '../components/navbar'
import { Head } from 'tuono'
import EditPage from '@/components/edit-page'
import MdxProvider from '@/components/mdx-provider'
import Navbar from '@/components/navbar'
import Sidebar from '@/components/sidebar'
import '@mantine/core/styles.css'
import '@mantine/code-highlight/styles.css'
import Sidebar from '../components/sidebar'
interface RootRouteProps {
children: ReactNode
}
@@ -21,7 +26,7 @@ interface RootRouteProps {
const theme = createTheme({
primaryColor: 'violet',
primaryShade: { light: 6, dark: 9 },
fontFamily: 'Roboto',
fontFamily: 'Inter',
respectReducedMotion: true,
radius: {
xs: '8px',
@@ -56,13 +61,31 @@ const theme = createTheme({
},
},
},
other: {
sidebarGrayLight: '#495057',
sidebarGrayDark: '#adb5bd',
sidebarTextHoverLight: '#212529',
sidebarTextHoverDark: '#f8f9fa',
},
})
const resolver: CSSVariablesResolver = (th) => ({
variables: {},
light: {
'--mantine-color-sidebar-gray': th.other.sidebarGrayLight as string,
'--mantine-color-sidebar-text-hover': th.other
.sidebarTextHoverLight as string,
},
dark: {
'--mantine-color-sidebar-gray': th.other.sidebarGrayDark as string,
'--mantine-color-sidebar-text-hover': th.other
.sidebarTextHoverDark as string,
},
})
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
const [opened, { toggle }] = useDisclosure()
const { pathname } = useRouter()
return (
<>
<Head>
@@ -88,8 +111,9 @@ export default function RootRoute({ children }: RootRouteProps): JSX.Element {
<link rel="manifest" href="/site.webmanifest" />
</Head>
<ColorSchemeScript />
<MantineProvider theme={theme}>
<MantineProvider theme={theme} cssVariablesResolver={resolver}>
<AppShell
layout="alt"
header={{ height: 60 }}
navbar={{
width: 300,
@@ -97,9 +121,14 @@ export default function RootRoute({ children }: RootRouteProps): JSX.Element {
collapsed: { mobile: !opened },
}}
>
<Navbar opened={opened} toggle={toggle} />
{pathname.startsWith('/documentation') && <Sidebar close={toggle} />}
{children}
<Navbar toggle={toggle} />
<Sidebar close={toggle} />
<AppShell.Main>
<Container id="mdx-root" component="article" size="md" p={20}>
<MdxProvider>{children}</MdxProvider>
<EditPage />
</Container>
</AppShell.Main>
</AppShell>
</MantineProvider>
</>
@@ -1,20 +0,0 @@
import type { ReactNode, JSX } from 'react'
import { AppShell, Container } from '@mantine/core'
import MdxProvider from '../../components/mdx-provider'
import EditPage from '../../components/edit-page'
interface RootRouteProps {
children: ReactNode
}
export default function RootRoute({ children }: RootRouteProps): JSX.Element {
return (
<AppShell.Main>
<Container id="mdx-root" component="article" size="md" p={20}>
<MdxProvider>{children}</MdxProvider>
<EditPage />
</Container>
</AppShell.Main>
)
}
@@ -1,4 +1,4 @@
import MetaTags from '../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Application state"
@@ -6,7 +6,7 @@ import MetaTags from '../../components/meta-tags'
description="Learn how to add features to your Tuono application"
/>
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Application state' }]} />
@@ -1,4 +1,4 @@
import MetaTags from '../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - CLI"
@@ -6,7 +6,7 @@ import MetaTags from '../../components/meta-tags'
description="Tuono is the CLI that provides all the needed commands to handle the full-stack project."
/>
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'CLI' }]} />
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Head"
canonical="https://tuono.dev/documentation/components/head"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Components' }]} />
# Head
TODO
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Link"
canonical="https://tuono.dev/documentation/components/link"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Components' }]} />
# Link
TODO
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - tuono.config.ts"
canonical="https://tuono.dev/documentation/configuration"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Configuration' }]} />
# Configuration
TODO
@@ -1,4 +1,4 @@
import MetaTags from '../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Contributing"
@@ -6,7 +6,7 @@ import MetaTags from '../../components/meta-tags'
description="The project is massive - if you like it, do consider contributing!"
/>
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Contributing"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="The project is massive - if you like it, do consider contributing!"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
@@ -28,11 +28,10 @@ The `tuono` project can mostly be split into the following subdomains:
To check what the knowledge requirements are for each domain, check the
[requirements](#requirements) section below.
Currently, I'm keeping a private dashboard to prioritize new features and bug fixes, but if you
want to propose something, please open a new issue on GitHub or reach out to me using
my email address [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it). I'm also available
on Twitter (X) DMs `@valerioageno`, [Linkedin](https://www.linkedin.com/in/valerioageno)
and discord `@__v__v__`.
We are currently managing the tasks' prioritization in the [Road to V1](https://github.com/orgs/tuono-labs/projects/2) project.
You can pick any task that you see in the `ready` column that is not already assigned.
If you want to contribute on something that is in the `backlog` column, or it hasn't been scoped yet, please drop a message in the [#general](https://discord.com/invite/khQzPa654B) discord channel so we can all have visibility of it.
## Requirements
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Contributing - Local development"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="Contribute to Tuono. Learn here how to setup the repository for local development"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Multithreading"
canonical="https://tuono.dev/documentation/core-concepts/multithreading"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Core concepts' }]} />
# Multithreading
TODO
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - useRouter"
canonical="https://tuono.dev/documentation/hooks/use-router"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Hooks' }]} />
# useRouter
TODO
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - tuono.config.ts"
canonical="https://tuono.dev/documentation/how-is-tuono-different"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'How is tuono different?' }]} />
# How is tuono different?
TODO
@@ -1,6 +1,6 @@
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
import MetaTags from '../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Installation"
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - MDX"
canonical="https://tuono.dev/documentation/integrations/mdx"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Integrations' }]} />
# MDX
TODO
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Server side rendering"
canonical="https://tuono.dev/documentation/rendering/server-side-rendering"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Rendering' }]} />
# SSR
TODO
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Static site rendering"
canonical="https://tuono.dev/documentation/rendering/static-site-rendering"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Rendering' }]} />
# Static Site Rendering
TODO
@@ -0,0 +1,101 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/defining-routes"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Defining routes' }]} />
# Defining routes
Tuono has a file-system based router built on the concept of routes.
When a file is added to the `src/routes` directory, it's automatically available as a route.
In Tuono, a page is a [React Component](https://react.dev/learn/your-first-component) exported from a `.tsx` file in the `src/routes` directory.
Each page is associated with a route based on its file name.
Every page is rendered server side by default.
Example:
If you create `src/routes/about.tsx` that exports a React component like:
```
export default function About() {
return <div>About</div>
}
```
The `About` component will be rendered when loading `/about` route.
## Index routes
The router will automatically route files named `index` to the root of the directory.
- `src/routes/index.tsx` → `/`
- `src/routes/blog/index.tsx`→ `/blog`
## Nested routes
You can also create a nested folder structure, and files will still be routed in the same way.
- `src/routes/blog/first-post.tsx` → `/blog/first-post`
- `src/routes/dashboard/settings/username.tsx` → `/dashboard/settings/username`
## Pages with Dynamic Routes
Tuono supports pages with dynamic routes segments.
For example, if you create a file called `src/routes/posts/[id].tsx`,
then it will be accessible at `posts/1`, `posts/2`, etc.
## The Root route (Layout components)
Tuono allows you to have a layout component to wrap all the routes included within the same folder.
To define such component you will have to create a `__root.tsx` file (is allowed only a single `__root` file per folder).
This file won't generate any route.
### Layout component for all routes
A file created in this location will wrap all project route.
```tsx
// src/routes/__root.tsx
export default function RootLayout({ children }) {
return <main>{children}</main>
}
```
### Layout component for specific routes
A file created in this location will wrap only the routes defined within the `src/routes/posts` folder.
```tsx
// src/routes/posts/__root.tsx
export default function PostRootLayout({ children }) {
return <article>{children}</article>
}
```
Referring to the two examples above consider that:
- `/posts/examples-post` → will be wrapped by:
```tsx
<RootLayout>
<PostRootLayout>
<ExamplePost />
</PostRootLayout>
</RootLayout>
```
- `/about` → will be wrapped by:
```tsx
<RootLayout>
<About />
</RootLayout>
```
@@ -0,0 +1,61 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/dynamic-routes"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Dynamic routes' }]} />
# Dynamic routes
> 🚧 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.
@@ -1,11 +1,11 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/intro"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Routing' }]} />
@@ -1,19 +0,0 @@
import MetaTags from '../../../components/meta-tags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
{ label: 'Routing', href: '/documentation/routing' },
{ label: 'Project structure' },
]}
/>
# Project structure
Todo
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/layouts"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Layouts' }]} />
# Layouts
Todo
@@ -0,0 +1,75 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/link-and-navigation"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Link and navigation' }]} />
# Link and navigation
The Tuono router facilitates client-side route transitions between pages, enhancing the user experience by:
- Enabling smooth navigation in single-page applications (SPAs)
- Preventing page reloads during site navigation
How? Using the `Link` component provided from Tuono exports:
```tsx
import { Link } from 'tuono'
export default function HomePage() {
return (
<ul>
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/about">About Us</Link>
</li>
<li>
<Link href="/blog/hello-world">Blog Post</Link>
</li>
</ul>
)
}
```
The example above uses multiple links. Each one maps a path (`href`) to a known route:
- `/` → `src/routes/index.tsx`
- `/about` → `src/routes/about.tsx`
- `/blog/hello-world` → `src/routes/blog/[slug].tsx`
> Additional considerations:
>
> - Any `<Link />` in the viewport is prefetched by default when it appears within the viewport.
> - The corresponding data for server-rendered routes is fetched only when the `<Link />` is clicked.
## The `useRouter` hook
The Link component is not suitable for programmatic navigation.
To handle this, the library provides the `useRouter` hook.
For example, after a user submits a form and the API request succeeds, they can be redirected to another page.
```tsx
import { useRouter } from 'tuono'
export default function GoToAboutPage() {
const router = useRouter()
return (
<ProfileForm
onUpdateSuccess={() => {
router.push('/profile')
}}
/>
)
}
```
> For more information about this hook visit the [dedicated API reference page](/documentation/hooks/use-router).
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/loading-state"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Loading state' }]} />
# Loading state
Todo
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/pages"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Pages' }]} />
# Pages
Todo
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Routing"
canonical="https://tuono.dev/documentation/routing/redirecting"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Redirecting' }]} />
# Redirecting
Todo
@@ -0,0 +1,14 @@
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - CSS Modules"
canonical="https://tuono.dev/documentation/styles/css-modules"
/>
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Styles' }]} />
# CSS Modules
TODO
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - API fetching"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="Learn how to fetch remote APIs on the backend"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Components"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="Learn how to manage the components in a Tuono codebase"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Conclusion"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="You just created a multi thread full stack application with Rust and React"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Development setup"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="Learn how to setup a new Tuono project"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Dynamic routes"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="Learn how to catch more endpoint with the same route loading dynamic data"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Error handling"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="Learn how to handle the server side error on your Tuono project"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Tutorial"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="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 the latest version"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs breadcrumbs={[{ label: 'Tutorial' }]} />
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Building for production"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="Learn how to build the production project and run the optimize version"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Redirection"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="Learn how to redirect to different route from the server"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -1,4 +1,4 @@
import MetaTags from '../../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - SEO and meta tags"
@@ -6,7 +6,7 @@ import MetaTags from '../../../components/meta-tags'
description="Learn how to handle meaningful meta tags for your Tuono project"
/>
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
<Breadcrumbs
breadcrumbs={[
@@ -1,12 +1,12 @@
import MetaTags from '../../components/meta-tags'
import MetaTags from '@/components/meta-tags'
<MetaTags
title="Tuono - Documentation"
canonical="https://tuono.dev/documentation"
canonical="https://tuono.dev"
description="Tuono is a full-stack framework for building React applications using Rust as the backend."
/>
import Breadcrumbs from '../../components/breadcrumbs'
import Breadcrumbs from '@/components/breadcrumbs'
<Breadcrumbs />
@@ -36,7 +36,7 @@ Tuono addresses the following two limits:
#### NodeJs/Deno/Bun are the only runtimes that allow a React app to be server-side rendered (SSR)? (no)
Tuono is the first full-stack React framework with the server side written in Rust (check the [ssr-rs](https://github.com/Valerioageno/ssr-rs) crate).
Tuono is the first full-stack React framework with the server side written in Rust (check the [ssr-rs](https://github.com/tuono-labs/ssr-rs) crate).
Because of this, Tuono is extremely fast, and the requests are handled by a multithreaded Rust server.
React is still React - it is just superpowered.
@@ -51,4 +51,4 @@ The routing is handled by Tuono based on the files defined within the `./src/rou
The Tuono API tries to stick as much as possible to the Next.js one (or at least takes a huge inspiration
from it). The major difference is the backend system. While Next.js relies entirely on Node/Deno/Bun,
Tuono runs the server without any intermediary runtime. This enables impressive performance improvements
(check the benchmarks [here](https://github.com/Valerioageno/tuono/tree/main/benches)).
(check the benchmarks [here](https://github.com/tuono-labs/tuono/tree/main/benches)).
-17
View File
@@ -1,17 +0,0 @@
import type { JSX } from 'react'
import Hero from '../components/hero'
import MetaTags from '../components/meta-tags'
export default function IndexPage(): JSX.Element {
return (
<>
<MetaTags
title="Tuono - The React/Rust full-stack framework"
canonical="https://tuono.dev"
description="The technologies we love seamlessly working together to unleash the highest web performance ever met on React"
/>
<Hero />
</>
)
}
+20 -10
View File
@@ -1,8 +1,7 @@
use tuono_lib::reqwest::StatusCode;
use tuono_lib::{Request, Response};
use tuono_lib::axum::http::{header, HeaderMap};
use glob::glob;
use time::OffsetDateTime;
use tuono_lib::axum::http::{header, HeaderMap, StatusCode};
use tuono_lib::{Request, Response};
const FILE_TO_EXCLUDE: [&str; 2] = ["sitemap.xml", "__root"];
@@ -14,11 +13,17 @@ const SITEMAP: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
fn load_routes() -> Vec<String> {
let mut paths: Vec<String> = vec![];
for entry in glob("./src/routes/**/*").expect("Failed to glob src/routes folder").flatten() {
for entry in glob("./src/routes/**/*")
.expect("Failed to glob src/routes folder")
.flatten()
{
if !entry.is_dir() {
let path = clean_path(format!("/{}", entry.to_string_lossy()));
if !FILE_TO_EXCLUDE.iter().any(|exclude| path.ends_with(exclude)) {
if !FILE_TO_EXCLUDE
.iter()
.any(|exclude| path.ends_with(exclude))
{
paths.push(path)
}
}
@@ -51,11 +56,16 @@ async fn generate_sitemap(_req: Request) -> Response {
url.pop();
}
sitemaps.push_str(
&format!(r#"<url><loc>{}</loc><lastmod>{}</lastmod></url>"#,url, OffsetDateTime::now_utc().date())
)
sitemaps.push_str(&format!(
r#"<url><loc>{}</loc><lastmod>{}</lastmod></url>"#,
url,
OffsetDateTime::now_utc().date()
))
}
Response::Custom((StatusCode::OK, headers, SITEMAP.replace("[PLACEHOLDER]", &sitemaps)))
Response::Custom((
StatusCode::OK,
headers,
SITEMAP.replace("[PLACEHOLDER]", &sitemaps),
))
}
+1 -1
View File
@@ -1 +1 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap');
+29 -18
View File
@@ -1,25 +1,36 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2021", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
// Typechecking
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
// Modules
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"paths": {
"@/*": ["./src/*"]
},
// Emit
"noEmit": true,
// JavaScript Support
"allowJs": true,
// Interop Constraints
"isolatedModules": true,
// Language and Environment
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2021", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
// Completeness
"skipLibCheck": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": ["src", "tuono.config.ts", "postcss.config.js"]
}
-11
View File
@@ -1,11 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}
+16
View File
@@ -0,0 +1,16 @@
import type { TuonoConfig } from 'tuono/config'
import mdx from '@mdx-js/rollup'
const config: TuonoConfig = {
vite: {
alias: {
'@': 'src',
'@tabler/icons-react': '@tabler/icons-react/dist/esm/icons/index.mjs',
},
plugins: [
{ enforce: 'pre', ...mdx({ providerImportSource: '@mdx-js/react' }) },
],
},
}
export default config
+7 -2
View File
@@ -1,12 +1,12 @@
[package]
name = "tuono"
version = "0.14.4"
version = "0.16.8"
edition = "2021"
authors = ["V. Ageno <valerioageno@yahoo.it>"]
description = "Superfast React fullstack framework"
homepage = "https://tuono.dev"
keywords = [ "react", "typescript", "fullstack", "web", "ssr"]
repository = "https://github.com/Valerioageno/tuono"
repository = "https://github.com/tuono-labs/tuono"
readme = "../../README.md"
license-file = "../../LICENSE.md"
categories = ["web-programming"]
@@ -33,3 +33,8 @@ reqwest = {version = "0.12.4", features =["blocking", "json"]}
serde_json = "1.0"
fs_extra = "1.3.0"
http = "1.1.0"
[dev-dependencies]
tempfile = "3.14.0"
assert_cmd = "2.0.16"
serial_test = "0.4.0"
+13
View File
@@ -21,11 +21,17 @@ const ROUTES_FOLDER_PATH: &str = "\\src\\routes";
#[cfg(target_os = "windows")]
const BUILD_JS_SCRIPT: &str = ".\\node_modules\\.bin\\tuono-build-prod.cmd";
#[cfg(target_os = "windows")]
const BUILD_TUONO_CONFIG: &str = ".\\node_modules\\.bin\\tuono-build-config.cmd";
#[cfg(not(target_os = "windows"))]
const ROUTES_FOLDER_PATH: &str = "/src/routes";
#[cfg(not(target_os = "windows"))]
const BUILD_JS_SCRIPT: &str = "./node_modules/.bin/tuono-build-prod";
#[cfg(not(target_os = "windows"))]
const BUILD_TUONO_CONFIG: &str = "./node_modules/.bin/tuono-build-config";
#[derive(Debug)]
pub struct App {
pub route_map: HashMap<String, Route>,
@@ -147,6 +153,13 @@ impl App {
.expect("Failed to run the rust server")
}
pub fn build_tuono_config(&self) -> Result<std::process::Output, std::io::Error> {
Command::new(BUILD_TUONO_CONFIG)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
}
pub fn get_used_http_methods(&self) -> HashSet<Method> {
let mut acc = HashSet::new();
+24 -8
View File
@@ -23,6 +23,10 @@ enum Actions {
#[arg(short, long = "static")]
/// Statically generate the website HTML
ssg: bool,
#[arg(short, long)]
/// Prevent to export the js assets
no_js_emit: bool,
},
/// Scaffold a new project
New {
@@ -42,12 +46,12 @@ struct Args {
action: Actions,
}
fn init_tuono_folder(mode: Mode) -> std::io::Result<()> {
fn init_tuono_folder(mode: Mode) -> std::io::Result<App> {
check_tuono_folder()?;
bundle_axum_source(mode)?;
let app = bundle_axum_source(mode)?;
create_client_entry_files()?;
Ok(())
Ok(app)
}
fn check_ports(mode: Mode) {
@@ -83,13 +87,19 @@ pub fn app() -> std::io::Result<()> {
Actions::Dev => {
check_ports(Mode::Dev);
init_tuono_folder(Mode::Dev)?;
let app = init_tuono_folder(Mode::Dev)?;
app.build_tuono_config()
.expect("Failed to build tuono.config.ts");
watch::watch().unwrap();
}
Actions::Build { ssg } => {
init_tuono_folder(Mode::Prod)?;
let app = App::new();
Actions::Build { ssg, no_js_emit } => {
let app = init_tuono_folder(Mode::Prod)?;
if no_js_emit {
println!("Rust build successfully finished");
return Ok(());
}
if ssg && app.has_dynamic_routes() {
// TODO: allow dynamic routes static generation
@@ -97,6 +107,9 @@ pub fn app() -> std::io::Result<()> {
return Ok(());
}
app.build_tuono_config()
.expect("Failed to build tuono.config.ts");
app.build_react_prod();
if ssg {
@@ -120,6 +133,8 @@ pub fn app() -> std::io::Result<()> {
)
.expect("Failed to clone assets into static output folder");
// the process is killed below so we don't really need to use wait() function
#[allow(clippy::zombie_processes)]
let mut rust_server = app.run_rust_server();
let reqwest = reqwest::blocking::Client::builder()
@@ -131,7 +146,8 @@ pub fn app() -> std::io::Result<()> {
let mut is_server_ready = false;
while !is_server_ready {
if reqwest.get("http://localhost:3000").send().is_ok() {
let server_url = format!("http://localhost:{}", TUONO_PORT);
if reqwest.get(server_url).send().is_ok() {
is_server_ready = true
}
// TODO: add maximum tries
+5
View File
@@ -1,3 +1,8 @@
//! ## Tuono
//! Tuono is a full-stack web framework for building React applications using Rust as the backend with a strong focus on usability and performance.
//!
//! You can find the full documentation at [tuono.dev](https://tuono.dev/)
mod app;
pub mod cli;
mod mode;
+15 -12
View File
@@ -22,12 +22,12 @@ pub struct AxumInfo {
}
impl AxumInfo {
pub fn new(path: String) -> Self {
pub fn new(route: &Route) -> Self {
// Remove first slash
let mut module = path.chars();
let mut module = route.path.chars();
module.next();
let axum_route = path.replace("/index", "");
let axum_route = route.path.replace("/index", "");
let module_import = module
.as_str()
@@ -44,7 +44,7 @@ impl AxumInfo {
};
}
if has_dynamic_path(&path) {
if route.is_dynamic {
return AxumInfo {
module_import: module
.as_str()
@@ -52,8 +52,12 @@ impl AxumInfo {
.replace('/', "_")
.replace('-', "_hyphen_")
.replace('[', "dyn_")
.replace("...", "catch_all_")
.replace(']', ""),
axum_route: axum_route
.replace("[...", "*")
.replace('[', ":")
.replace(']', ""),
axum_route: axum_route.replace('[', ":").replace(']', ""),
};
}
@@ -134,7 +138,7 @@ impl Route {
}
pub fn update_axum_info(&mut self) {
self.axum_info = Some(AxumInfo::new(self.path.clone()))
self.axum_info = Some(AxumInfo::new(self))
}
pub fn save_ssg_file(&self, reqwest: &Client) {
@@ -159,8 +163,7 @@ impl Route {
// Saving also the server response
if self.axum_info.is_some() {
let data_file_path =
PathBuf::from(&format!("out/static/__tuono/data{}/data.json", path));
let data_file_path = PathBuf::from(&format!("out/static/__tuono/data{path}"));
let data_parent_dir = data_file_path.parent().unwrap();
@@ -169,11 +172,11 @@ impl Route {
.expect("Failed to create data parent directories");
}
let base = Url::parse("http://localhost:3000/__tuono/data/data.json").unwrap();
let base = Url::parse("http://localhost:3000/__tuono/data").unwrap();
let path = if path == "/" { "" } else { path };
let pathname = &format!("/__tuono/data{path}/data.json");
let pathname = &format!("/__tuono/data{path}");
let url = base
.join(pathname)
@@ -229,12 +232,12 @@ mod tests {
#[test]
fn should_correctly_create_the_axum_infos() {
let info = AxumInfo::new("/index".to_string());
let info = AxumInfo::new(&Route::new("/index".to_string()));
assert_eq!(info.axum_route, "/");
assert_eq!(info.module_import, "index");
let dyn_info = AxumInfo::new("/[posts]".to_string());
let dyn_info = AxumInfo::new(&Route::new("/[posts]".to_string()));
assert_eq!(dyn_info.axum_route, "/:posts");
assert_eq!(dyn_info.module_import, "dyn_posts");
+3 -3
View File
@@ -7,9 +7,9 @@ use std::io::{self, prelude::*};
use std::path::{Path, PathBuf};
const GITHUB_TUONO_REPO_URL: &str =
"https://api.github.com/repos/Valerioageno/tuono/git/trees/main?recursive=1";
"https://api.github.com/repos/tuono-labs/tuono/git/trees/main?recursive=1";
const GITHUB_RAW_CONTENT_URL: &str = "https://raw.githubusercontent.com/Valerioageno/tuono/main/";
const GITHUB_RAW_CONTENT_URL: &str = "https://raw.githubusercontent.com/tuono-labs/tuono/main/";
#[derive(Deserialize, Debug)]
enum GithubFileType {
@@ -64,7 +64,7 @@ pub fn create_new_project(folder_name: Option<String>, template: Option<String>)
if new_project_files.is_empty() {
eprintln!("Error: Template '{template}' not found");
println!("Hint: you can view the available templates at https://github.com/Valerioageno/tuono/tree/main/examples");
println!("Hint: you can view the available templates at https://github.com/tuono-labs/tuono/tree/main/examples");
std::process::exit(1);
}
+4 -5
View File
@@ -84,12 +84,11 @@ fn create_routes_declaration(routes: &HashMap<String, Route>) -> String {
if !route.is_api() {
route_declarations.push_str(&format!(
r#".route("{axum_route}", get({module_import}::route))"#
r#".route("{axum_route}", get({module_import}::tuono__internal__route))"#
));
let slash = if axum_route.ends_with('/') { "" } else { "/" };
route_declarations.push_str(&format!(
r#".route("/__tuono/data{axum_route}{slash}data.json", get({module_import}::api))"#
r#".route("/__tuono/data{axum_route}", get({module_import}::tuono__internal__api))"#
));
} else {
for method in route.api_data.as_ref().unwrap().methods.clone() {
@@ -123,7 +122,7 @@ fn create_modules_declaration(routes: &HashMap<String, Route>) -> String {
route_declarations
}
pub fn bundle_axum_source(mode: Mode) -> io::Result<()> {
pub fn bundle_axum_source(mode: Mode) -> io::Result<App> {
let base_path = std::env::current_dir().unwrap();
let app = App::new();
@@ -132,7 +131,7 @@ pub fn bundle_axum_source(mode: Mode) -> io::Result<()> {
create_main_file(&base_path, &bundled_file);
Ok(())
Ok(app)
}
fn generate_axum_source(app: &App, mode: Mode) -> String {
+142
View File
@@ -0,0 +1,142 @@
mod utils;
use assert_cmd::Command;
use serial_test::serial;
use std::fs;
use utils::TempTuonoProject;
const POST_API_FILE: &str = r"#[tuono_lib::api(POST)]";
const GET_API_FILE: &str = r"#[tuono_lib::api(GET)]";
#[test]
#[serial]
fn it_successfully_create_the_index_route() {
let temp_tuono_project = TempTuonoProject::new();
temp_tuono_project.add_route("./src/routes/index.rs");
let mut test_tuono_build = Command::cargo_bin("tuono").unwrap();
test_tuono_build
.arg("build")
.arg("--no-js-emit")
.assert()
.success();
let temp_main_rs_path = temp_tuono_project.path().join(".tuono/main.rs");
let temp_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/index.rs"]"#));
assert!(temp_main_rs_content.contains("mod index;"));
assert!(temp_main_rs_content
.contains(r#".route("/", get(index::tuono__internal__route)).route("/__tuono/data/", get(index::tuono__internal__api))"#));
}
#[test]
#[serial]
fn it_successfully_create_an_api_route() {
let temp_tuono_project = TempTuonoProject::new();
temp_tuono_project.add_api("./src/routes/api/health_check.rs", POST_API_FILE);
let mut test_tuono_build = Command::cargo_bin("tuono").unwrap();
test_tuono_build
.arg("build")
.arg("--no-js-emit")
.assert()
.success();
let temp_main_rs_path = temp_tuono_project.path().join(".tuono/main.rs");
let temp_main_rs_content =
fs::read_to_string(&temp_main_rs_path).expect("Failed to read '.tuono/main.rs' content.");
dbg!(&temp_main_rs_content);
assert!(temp_main_rs_content.contains(r#"#[path="../src/routes/api/health_check.rs"]"#));
assert!(temp_main_rs_content.contains("mod api_health_check;"));
assert!(temp_main_rs_content.contains(
r#".route("/api/health_check", post(api_health_check::post__tuono_internal_api))"#
));
}
#[test]
#[serial]
fn it_successfully_create_multiple_api_for_the_same_file() {
let temp_tuono_project = TempTuonoProject::new();
temp_tuono_project.add_api(
"./src/routes/api/health_check.rs",
&format!("{POST_API_FILE}{GET_API_FILE}"),
);
let mut test_tuono_build = Command::cargo_bin("tuono").unwrap();
test_tuono_build
.arg("build")
.arg("--no-js-emit")
.assert()
.success();
let temp_main_rs_path = temp_tuono_project.path().join(".tuono/main.rs");
let temp_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/health_check.rs"]"#));
assert!(temp_main_rs_content.contains("mod api_health_check;"));
assert!(temp_main_rs_content.contains(
r#".route("/api/health_check", post(api_health_check::post__tuono_internal_api))"#
));
assert!(temp_main_rs_content.contains(
r#".route("/api/health_check", get(api_health_check::get__tuono_internal_api))"#
));
}
#[test]
#[serial]
fn it_successfully_create_catch_all_routes() {
let temp_tuono_project = TempTuonoProject::new();
temp_tuono_project.add_route("./src/routes/[...all_routes].rs");
temp_tuono_project.add_api(
"./src/routes/api/[...all_apis].rs",
&format!("{POST_API_FILE}"),
);
let mut test_tuono_build = Command::cargo_bin("tuono").unwrap();
test_tuono_build
.arg("build")
.arg("--no-js-emit")
.assert()
.success();
let temp_main_rs_path = temp_tuono_project.path().join(".tuono/main.rs");
let temp_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("mod api_dyn_catch_all_all_apis;"));
assert!(temp_main_rs_content.contains(r#"#[path="../src/routes/[...all_routes].rs"]"#));
assert!(temp_main_rs_content.contains("mod dyn_catch_all_all_routes;"));
assert!(temp_main_rs_content.contains(
r#".route("/api/*all_apis", post(api_dyn_catch_all_all_apis::post__tuono_internal_api))"#
));
assert!(temp_main_rs_content.contains(
r#".route("/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__route))"#
));
assert!(temp_main_rs_content.contains(
r#".route("/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__route))"#
));
assert!(temp_main_rs_content
.contains(r#".route("/__tuono/data/*all_routes", get(dyn_catch_all_all_routes::tuono__internal__api))"#));
}
+61
View File
@@ -0,0 +1,61 @@
use fs_extra::dir::create_all;
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use tempfile::{tempdir, TempDir};
#[derive(Debug)]
pub struct TempTuonoProject {
original_dir: PathBuf,
temp_dir: TempDir,
}
impl TempTuonoProject {
pub fn new() -> Self {
let original_dir = env::current_dir().expect("Failed to read current_dir");
let temp_dir = tempdir().expect("Failed to create temp_dir");
env::set_current_dir(temp_dir.path()).expect("Failed to change current dir into temp_dir");
TempTuonoProject {
original_dir,
temp_dir,
}
}
pub fn path(&self) -> &Path {
self.temp_dir.path()
}
pub fn add_route<'a>(&self, path: &'a str) {
let path = PathBuf::from(path);
create_all(
path.parent().expect("Route path does not have any parent"),
false,
)
.expect("Failed to create parent route directory");
File::create(path).expect("Failed to create the route file");
}
pub fn add_api<'a>(&self, path: &'a str, content: &'a str) {
let path = PathBuf::from(path);
create_all(
path.parent().expect("Route path does not have any parent"),
false,
)
.expect("Failed to create parent route directory");
let mut file = File::create(path).expect("Failed to create the route file");
file.write_all(content.as_bytes())
.expect("Failed to write into API file");
}
}
impl Drop for TempTuonoProject {
fn drop(&mut self) {
// Set back the current dir in the previous state
env::set_current_dir(self.original_dir.to_owned())
.expect("Failed to restore the original directory.");
}
}
+4 -3
View File
@@ -1,12 +1,12 @@
[package]
name = "tuono_lib"
version = "0.14.4"
version = "0.16.8"
edition = "2021"
authors = ["V. Ageno <valerioageno@yahoo.it>"]
description = "Superfast React fullstack framework"
homepage = "https://tuono.dev"
keywords = [ "react", "typescript", "fullstack", "web", "ssr"]
repository = "https://github.com/Valerioageno/tuono"
repository = "https://github.com/tuono-labs/tuono"
readme = "../../README.md"
license-file = "../../LICENSE.md"
categories = ["web-programming"]
@@ -19,6 +19,7 @@ include = [
[dependencies]
ssr_rs = "0.7.0"
axum = {version = "0.7.5", features = ["json", "ws"]}
axum-extra = {version = "0.9.6", features = ["cookie"]}
tokio = { version = "1.37.0", features = ["full"] }
serde = { version = "1.0.202", features = ["derive"] }
erased-serde = "0.4.5"
@@ -31,7 +32,7 @@ either = "1.13.0"
tower-http = {version = "0.6.0", features = ["fs"]}
colored = "2.1.0"
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.14.4"}
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.16.8"}
# Match the same version used by axum
tokio-tungstenite = "0.24.0"
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
+6
View File
@@ -1,3 +1,8 @@
//! ## Tuono
//! Tuono is a full-stack web framework for building React applications using Rust as the backend with a strong focus on usability and performance.
//!
//! You can find the full documentation at [tuono.dev](https://tuono.dev/)
mod catch_all;
mod logger;
mod manifest;
@@ -19,4 +24,5 @@ pub use tuono_lib_macros::{api, handler};
// Re-exports
pub use axum;
pub use axum_extra::extract::cookie;
pub use tokio;

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