Compare commits
358 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 946df5d221 | |||
| 32d7d83d79 | |||
| 0a2ee1ab02 | |||
| 77c9e1f7c8 | |||
| 183f1c498d | |||
| 3e07d57956 | |||
| d5c0dd4365 | |||
| 4d187e506f | |||
| 71d3440d23 | |||
| 4856af992b | |||
| 6ccc91c4a8 | |||
| 468bc48a0c | |||
| 1ef6a9dc90 | |||
| 27c546d8e3 | |||
| fbaf411992 | |||
| 403d548a07 | |||
| be48dbda09 | |||
| 038d9e4a46 | |||
| a5dd7cb537 | |||
| 8ac2132a2e | |||
| bc49ec3193 | |||
| 022b548af1 | |||
| a1f5811eda | |||
| 5990cf3d27 | |||
| c0719c6edb | |||
| 9404946269 | |||
| eab8d67203 | |||
| a9a76f8b09 | |||
| f7ba80c5c4 | |||
| a4667c4c2c | |||
| 998a3191b4 | |||
| 89ad91a055 | |||
| d810047ddd | |||
| 19c73f7747 | |||
| 2f2d772882 | |||
| 32e300f730 | |||
| 2f31d14194 | |||
| 980aa0d07f | |||
| 07351587f3 | |||
| 3027b828a8 | |||
| eef2cd26e0 | |||
| 8190915e3a | |||
| dd157d6ef9 | |||
| 58f4ecb260 | |||
| ab441d1fa7 | |||
| 7538031261 | |||
| 18f66ff79b | |||
| b48f7244b7 | |||
| 22844a37fa | |||
| bc444929ed | |||
| 0813c14e15 | |||
| 5331cc0c8c | |||
| ba89a7e388 | |||
| 90a4d20436 | |||
| d938e82998 | |||
| fc15c01c90 | |||
| ffdf0f91ac | |||
| fa6b2f4c89 | |||
| 245b5c535b | |||
| 2bb0bdb2b0 | |||
| e368cc5e23 | |||
| eb4966e88e | |||
| 64c4c1174c | |||
| 315225184d | |||
| 39fcc33600 | |||
| 1adf380798 | |||
| ac20a83e7e | |||
| b17521063a | |||
| f449c4537e | |||
| 3c413c38bd | |||
| 750e4b6f22 | |||
| e6e455e966 | |||
| 54de22d8c4 | |||
| 40005c4e02 | |||
| 32a76a2560 | |||
| f3cf9b05ae | |||
| c946c43462 | |||
| 2ecfb0d5c0 | |||
| 6b02c40ad4 | |||
| 3334bddeb2 | |||
| 0335c8c1ad | |||
| 0fa83fb1a2 | |||
| b24466fb11 | |||
| c0b4fa8717 | |||
| 16b8125206 | |||
| 8ce1292960 | |||
| dbc205a09c | |||
| 0e5fcd4b57 | |||
| 42b7a22a29 | |||
| 13983a5362 | |||
| 557f412e27 | |||
| f8f21b69b6 | |||
| 7b656ee961 | |||
| c044ab45a7 | |||
| eda328f861 | |||
| f75fce87cf | |||
| 7c0999a5fe | |||
| a7b5e92c46 | |||
| 4d91bc0a72 | |||
| 3b3f601ff1 | |||
| abb12e7b3d | |||
| 66c3d91dfa | |||
| 53ec3ae280 | |||
| f293a1dee8 | |||
| a786f11d28 | |||
| 604581c64a | |||
| ae6ece7415 | |||
| b0a7557d83 | |||
| fde01421dc | |||
| 497532e6fe | |||
| 724ac83429 | |||
| c7ca9ca8b4 | |||
| 7934a7cbab | |||
| 35b1de5d22 | |||
| 2cfad3de12 | |||
| 78a742c4c8 | |||
| 57f0c9fa20 | |||
| 8dde08892c | |||
| da5fac0af2 | |||
| 12aa12c94d | |||
| 6dbdafa888 | |||
| 97082840f8 | |||
| d562aa87a3 | |||
| 2301083edc | |||
| 1c45b29ed1 | |||
| bfd0041c6a | |||
| 9f14689ca0 | |||
| b32b192f70 | |||
| 486a254a1e | |||
| 67de777fd1 | |||
| 8fcebae4a9 | |||
| 77c902a7a5 | |||
| da194524ee | |||
| 81e7af6f5e | |||
| fd2357cedc | |||
| 9db6f02701 | |||
| 9b6f8098b6 | |||
| a322ce2fad | |||
| 73b7a8f056 | |||
| 0e6546d4d0 | |||
| bf77ed1794 | |||
| e34c10bc34 | |||
| 310579cf76 | |||
| 1ba94238ed | |||
| 5f6bad637c | |||
| 3063763667 | |||
| caf1a58a0b | |||
| 059f3d6476 | |||
| 303a177344 | |||
| d26918fc2f | |||
| 31f0a167e9 | |||
| d71185c9cb | |||
| 798f8f1bf8 | |||
| 06612f07b0 | |||
| 067535c2f6 | |||
| d679982949 | |||
| de5ac53cc3 | |||
| ca4a7b3466 | |||
| 0a4d1e2995 | |||
| d6fc18e2f6 | |||
| 8e04abbe42 | |||
| 2905cfae3d | |||
| b632cf43ba | |||
| 06bc3700b7 | |||
| d4648afe01 | |||
| 0653349c08 | |||
| 12e0937174 | |||
| d37b655add | |||
| e3b1c0e013 | |||
| be77068c73 | |||
| 907c92bedc | |||
| 6794ab20d3 | |||
| e00dad9200 | |||
| 9aa6aedee0 | |||
| 0114867fe6 | |||
| 4f58d42582 | |||
| 40add60fd8 | |||
| 9a4bad95ed | |||
| 3e28aeea27 | |||
| 58e48147b8 | |||
| 68eda84053 | |||
| 253f35dbbb | |||
| 0f72506f95 | |||
| ebe1abe422 | |||
| f5a7480f69 | |||
| c0139fddc8 | |||
| 9e08a949bb | |||
| d86c97979b | |||
| 4e266522b6 | |||
| a58a1a4da9 | |||
| 22c1dd04c2 | |||
| 74e34dbbf9 | |||
| 4f38de3dea | |||
| 9d97f2d345 | |||
| 938cf3d8c6 | |||
| e1443274e3 | |||
| b9f8c0312f | |||
| 674a817131 | |||
| 2cbe53f7b7 | |||
| f3033ef0d2 | |||
| 0a0fb8c7f8 | |||
| f72a1a9e5c | |||
| fb29a26246 | |||
| 79d4aff758 | |||
| 7ea8414d6c | |||
| 1a3c9a7fbe | |||
| cecf2ba8cc | |||
| eaaf2513d7 | |||
| fa4d435320 | |||
| 2aff72942c | |||
| 11666fc422 | |||
| aef456cbf2 | |||
| 97b9d71caa | |||
| 5fa38b9506 | |||
| 98e50da575 | |||
| c8cf0771d4 | |||
| 1c6e78ad57 | |||
| 2b6f8c16ce | |||
| 2403ef3d64 | |||
| 003c0766cf | |||
| 9d7e59d793 | |||
| 43f03adedf | |||
| 211c6401e9 | |||
| 01b90b327e | |||
| ad0b09c3df | |||
| 362e1be58d | |||
| eb9039daa3 | |||
| ff7433f434 | |||
| a4e207688a | |||
| fdc87ed8f1 | |||
| 0ab9c297a5 | |||
| 04d6fcc1e1 | |||
| bf18ac0ab0 | |||
| bbe9605f6f | |||
| bd34c194cb | |||
| c0aa707315 | |||
| b6fa448202 | |||
| 2e141be0fc | |||
| 93b5e3e68f | |||
| 39dac5065f | |||
| a16a313f71 | |||
| 82560ddc20 | |||
| 83f3cae6f0 | |||
| 99df4a5575 | |||
| d33c211a01 | |||
| 21ee89ada4 | |||
| dcedb1c6c0 | |||
| d5cc65983e | |||
| 3cb150b090 | |||
| 305ae59086 | |||
| 8fe8930def | |||
| 8730bdbfb9 | |||
| 3ee47e9481 | |||
| 4e649acd60 | |||
| 4768d70a8a | |||
| c7bff7ed09 | |||
| 3035d2a645 | |||
| 0750037385 | |||
| 8c4d61ee65 | |||
| 1a3ccb112e | |||
| 118172d27f | |||
| 69f1d85360 | |||
| f0d042c85b | |||
| 550830c232 | |||
| 6be12b4e58 | |||
| 33aa37ed7d | |||
| 43910fd75d | |||
| 5efcc86a63 | |||
| 17b61a5a41 | |||
| 5dae5c31b7 | |||
| 96deea7f31 | |||
| 7632e6a464 | |||
| 23e2a2e3ff | |||
| b19512dffc | |||
| 11c6fc820b | |||
| 1a8231851d | |||
| 1dfb83b1aa | |||
| 93cb1ae8ba | |||
| 631b168e05 | |||
| af8a43c93a | |||
| 875d12f514 | |||
| a9a2ef67ea | |||
| 5caf3ed6e7 | |||
| 580ec704f2 | |||
| a9918ae555 | |||
| 4b3f4bfab8 | |||
| fed3b1581b | |||
| 9b2f0aab32 | |||
| 818a6bd2ff | |||
| 94a43347a1 | |||
| 16d793c588 | |||
| 0322e5bd8d | |||
| cfe76a9ab3 | |||
| ff6014317b | |||
| 64220f2e27 | |||
| e75414abfc | |||
| d6fae42dc3 | |||
| 0db1245a6b | |||
| 91c0a9ec31 | |||
| 7670c37685 | |||
| ae41c5629c | |||
| 3e7fa05fce | |||
| 6833640c47 | |||
| 18541c45de | |||
| 45bb0dd0d2 | |||
| db0aad2c37 | |||
| d55ca12125 | |||
| ad2cada94b | |||
| f9545f513b | |||
| c75d83dc7c | |||
| 91236e5533 | |||
| 1356be248e | |||
| 7e8be92cd5 | |||
| c3fda4a3a5 | |||
| dfc66cfd50 | |||
| 0ef1252e1f | |||
| cf56b3a53e | |||
| 592f402ffd | |||
| 1dc77fc473 | |||
| 09cd772d74 | |||
| 1c69e10e6a | |||
| 1af18e4c91 | |||
| a3709609c5 | |||
| 70a79faee0 | |||
| 07cb7dae9d | |||
| 9a1fb9c4a1 | |||
| a3f7ed7f61 | |||
| 5302fd2779 | |||
| ccd4e02b6a | |||
| f6593097b6 | |||
| 230cd22bff | |||
| f4367f1755 | |||
| 7b7447f9a0 | |||
| d12d8404ec | |||
| d1b12173d5 | |||
| f3bf13da59 | |||
| f236b75f55 | |||
| 2434231f9b | |||
| b8b39e69a1 | |||
| f8158dfd8e | |||
| f1ab7df5ce | |||
| 986de49052 | |||
| d09ca5548a | |||
| 77e22c40b6 | |||
| de74647c43 | |||
| 498601c21a | |||
| e99f06ddf5 | |||
| f06af59168 | |||
| d50e3a7fc0 | |||
| 3e4e7ff1ab | |||
| 21ff3c8655 | |||
| 166e61bab4 | |||
| 93cd8bd8da | |||
| f5c1641af2 | |||
| 62ffed0624 | |||
| 8d0a4a7417 | |||
| b1b274ea12 | |||
| fc38865965 |
@@ -0,0 +1,10 @@
|
|||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
insert_final_newline = true
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
max_line_length = 80
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
indent_size = 4
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
description: File a bug report
|
description: File a bug report
|
||||||
title: '[Bug]: '
|
title: '[Bug]: '
|
||||||
|
type: Bug
|
||||||
labels: ['bug']
|
labels: ['bug']
|
||||||
|
|
||||||
body:
|
body:
|
||||||
@@ -32,55 +33,24 @@ body:
|
|||||||
description: If applicable, add screenshots to help explain your problem.
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: input
|
- type: textarea
|
||||||
id: tuono-version
|
id: system-info
|
||||||
attributes:
|
attributes:
|
||||||
label: Tuono version
|
label: System Info
|
||||||
placeholder: '[e.g. 0.4.0]'
|
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
|
||||||
|
render: shell
|
||||||
|
placeholder: System, Binaries, Browsers
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: input
|
- type: textarea
|
||||||
id: os-version
|
id: system-info-rust
|
||||||
attributes:
|
attributes:
|
||||||
label: OS
|
label: System info (Rust)
|
||||||
placeholder: '[e.g. MacOS, Windows]'
|
description: Output of `rustc --version && cargo --version && tuono --version`
|
||||||
|
render: shell
|
||||||
|
placeholder: 'rustc, cargo and tuono binary version'
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: input
|
|
||||||
id: browser-version
|
|
||||||
attributes:
|
|
||||||
label: Browser
|
|
||||||
placeholder: '[e.g. chrome, safari]'
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: node-version
|
|
||||||
attributes:
|
|
||||||
label: Node version
|
|
||||||
placeholder: '[e.g. 20.0.0]'
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: rust-version
|
|
||||||
attributes:
|
|
||||||
label: Rust version
|
|
||||||
placeholder: '[e.g. 1.79.0]'
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: create-version
|
|
||||||
attributes:
|
|
||||||
label: Crate version
|
|
||||||
placeholder: '[e.g. 1.78.0]'
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: node-package-manager-version
|
|
||||||
attributes:
|
|
||||||
label: Node Package Manger version
|
|
||||||
placeholder: '[e.g. pnpm: 9.5.0] '
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: additional-context
|
id: additional-context
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Ask a question / Get support
|
||||||
|
url: https://discord.com/invite/khQzPa654B
|
||||||
|
about: Ask questions and discuss with other community members
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
name: Feature request
|
name: Feature request
|
||||||
description: Suggest an idea for this project
|
description: Suggest an idea for this project
|
||||||
title: '[Feature request]: '
|
title: '[Feature request]: '
|
||||||
|
type: Feature
|
||||||
labels: ['enhancement']
|
labels: ['enhancement']
|
||||||
|
|
||||||
body:
|
body:
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# @see https://github.com/actions/labeler
|
||||||
|
|
||||||
|
rust:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['crates/**', 'Cargo.toml']
|
||||||
|
|
||||||
|
typescript:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
[
|
||||||
|
'devtools/**',
|
||||||
|
'packages/**',
|
||||||
|
'package.json',
|
||||||
|
'pnpm-*.yaml',
|
||||||
|
'eslint.config.js',
|
||||||
|
'tsconfig.json',
|
||||||
|
]
|
||||||
|
|
||||||
|
'CI/CD':
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['.github/**']
|
||||||
|
|
||||||
|
'repo maintenance':
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
[
|
||||||
|
'devtools/**',
|
||||||
|
'.npmrc',
|
||||||
|
'.nvmrc',
|
||||||
|
'.prettierrc',
|
||||||
|
'.prettierignore',
|
||||||
|
'renovate.json',
|
||||||
|
'turbo.json',
|
||||||
|
]
|
||||||
@@ -1,13 +1,24 @@
|
|||||||
## Context & Description
|
<!--
|
||||||
|
👋 Thank you for your Pull Request 🙏
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
- [ ] I have read [Contributing > Pull requests](https://tuono.dev/documentation/contributing/pull-requests)
|
||||||
|
|
||||||
|
### Related issue
|
||||||
|
|
||||||
|
Fixes #0
|
||||||
|
|
||||||
|
<!-- Replace the content with "None" if you haven't an issue to link -->
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Thank you for your Pull Request.
|
|
||||||
|
|
||||||
Explain the context and why you're making that change. What is the problem
|
Explain the context and why you're making that change.
|
||||||
you're trying to solve? If a new feature is being added, describe the intended
|
What is the problem you're trying to solve?
|
||||||
use case that feature fulfills.
|
If a new feature is being added,
|
||||||
|
describe the intended use case that feature fulfills.
|
||||||
|
|
||||||
Bug fixes and new features should include tests.
|
|
||||||
|
|
||||||
Contributors guide: https://github.com/Valerioageno/tuono/blob/main/CONTRIBUTING.md
|
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
name: Documentation Website CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '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:
|
|
||||||
build:
|
|
||||||
name: Check build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install NodeJS Dependencies
|
|
||||||
uses: ./.github/actions/install-node-dependencies
|
|
||||||
|
|
||||||
- name: Setup rust
|
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
||||||
|
|
||||||
- name: Install tuono
|
|
||||||
run: cargo install tuono@0.15.0
|
|
||||||
|
|
||||||
- name: Build project
|
|
||||||
working-directory: ./apps/documentation
|
|
||||||
run: tuono build --static
|
|
||||||
|
|
||||||
code_check:
|
|
||||||
name: Check format, lint and types
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install NodeJS Dependencies
|
|
||||||
uses: ./.github/actions/install-node-dependencies
|
|
||||||
|
|
||||||
- name: Check formatting
|
|
||||||
run: pnpm docs:format:check
|
|
||||||
|
|
||||||
- name: Lint
|
|
||||||
run: pnpm docs:lint
|
|
||||||
|
|
||||||
- name: Types
|
|
||||||
run: pnpm docs:types
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
name: Deploy documentation website on AWS S3
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'apps/documentation/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy-documentation:
|
|
||||||
name: Deploy
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
env:
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install NodeJS Dependencies
|
|
||||||
uses: ./.github/actions/install-node-dependencies
|
|
||||||
|
|
||||||
- name: Setup rust
|
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
||||||
|
|
||||||
- name: Install tuono
|
|
||||||
run: cargo install tuono@0.16.0
|
|
||||||
|
|
||||||
- name: Build project
|
|
||||||
working-directory: ./apps/documentation
|
|
||||||
run: tuono build --static
|
|
||||||
|
|
||||||
- name: Deploy
|
|
||||||
uses: reggionick/s3-deploy@v4
|
|
||||||
with:
|
|
||||||
folder: apps/documentation/out/static
|
|
||||||
bucket: tuono-documentation
|
|
||||||
bucket-region: eu-west-3
|
|
||||||
dist-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}
|
|
||||||
invalidation: /
|
|
||||||
delete-removed: true
|
|
||||||
no-cache: true
|
|
||||||
private: true
|
|
||||||
files-to-include: '{.*/**,**}'
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
name: Docker CI
|
||||||
|
|
||||||
|
# If we need support more OS refer to
|
||||||
|
# https://github.com/tuono-labs/tuono/pull/332#issuecomment-2614439692
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
name: Build and Test Docker Image on ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build and Run Docker Compose
|
||||||
|
run: |
|
||||||
|
docker compose -f docker/compose.yml up --build -d
|
||||||
|
|
||||||
|
- name: Check if the container is running
|
||||||
|
run: |
|
||||||
|
docker ps --filter "name=tuono-source-container" --format "table {{.Names}}\t{{.Status}}"
|
||||||
|
if ! docker ps --filter "name=tuono-source-container" --format "{{.Status}}" | grep -q "Up"; then
|
||||||
|
echo "Error: Container 'tuono-source-container' is not running."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check commands inside container
|
||||||
|
run: |
|
||||||
|
docker exec tuono-source-container pnpm --version || { echo "Error: pnpm is not available."; exit 1; }
|
||||||
|
docker exec tuono-source-container tuono --version || { echo "Error: tuono is not available."; exit 1; }
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
run: docker exec tuono-source-container cargo test
|
||||||
|
|
||||||
|
- name: Install node dependencies in each tuono's sub-projets
|
||||||
|
run: docker exec tuono-source-container bash -c 'for dir in /tuono/packages/*; do [ -d "$dir" ] && (cd "$dir" && yes | pnpm install --frozen-lockfile); done'
|
||||||
|
|
||||||
|
- name: Run pnpm test
|
||||||
|
run: docker exec tuono-source-container pnpm test
|
||||||
|
|
||||||
|
- name: Tear Down Docker Compose
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
docker compose -f docker/compose.yml down --volumes
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
name: E2E CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e:
|
||||||
|
name: E2E Test (${{ matrix.os }})
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macos-latest'
|
||||||
|
- 'windows-latest'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install NodeJS Dependencies
|
||||||
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- name: Install Playwright Browsers
|
||||||
|
run: pnpm exec playwright install
|
||||||
|
|
||||||
|
- name: Run Playwright E2E tests
|
||||||
|
run: pnpm test:e2e
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: E2E CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- e2e
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
name: Examples CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
example_list:
|
||||||
|
name: Get examples names
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
names: ${{ steps.find_folder.outputs.names }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- id: find_folder
|
||||||
|
working-directory: examples
|
||||||
|
# produces the list of folder inside examples without leading `./` and trailing `/`:
|
||||||
|
# ```text
|
||||||
|
# tuono-tutorial
|
||||||
|
# with-tailwind
|
||||||
|
# tuono-app
|
||||||
|
# with-mdx
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# after, using `jq` the output is converted into a JSON array:
|
||||||
|
# ```json
|
||||||
|
# ["tuono-tutorial","with-tailwind","tuono-app","with-mdx"]
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# and added to `$GITHUB_OUTPUT` env variable:
|
||||||
|
run: |
|
||||||
|
NAMES=$(find . -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||||||
|
echo "names=$(echo "$NAMES" | jq -R -s -c 'split("\n")[:-1]')" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
check_example:
|
||||||
|
needs:
|
||||||
|
- example_list
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
example_name: ${{ fromJson(needs.example_list.outputs.names) }}
|
||||||
|
os:
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macos-latest'
|
||||||
|
# to be added
|
||||||
|
# - 'windows-latest'
|
||||||
|
|
||||||
|
# Only on this repo some examples build including plugins fails
|
||||||
|
# However, if the project is downloaded via `tuono new`` it will work
|
||||||
|
# @see https://github.com/tuono-labs/tuono/pull/605#pullrequestreview-2656262230
|
||||||
|
# needs further investigations
|
||||||
|
exclude:
|
||||||
|
- example_name: with-tailwind
|
||||||
|
|
||||||
|
name: 'Check `${{ matrix.example_name }}` build on ${{ matrix.os }}'
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install NodeJS Dependencies
|
||||||
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- name: Build Rust
|
||||||
|
run: cargo build
|
||||||
|
|
||||||
|
- name: Build typescript
|
||||||
|
run: pnpm run build
|
||||||
|
|
||||||
|
- name: Check "build"
|
||||||
|
working-directory: ./examples/${{ matrix.example_name }}
|
||||||
|
run: ../../target/debug/tuono build
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: pnpm run format
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: pnpm run typecheck
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Examples CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- example_list
|
||||||
|
- check_example
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: 'PR Labeler'
|
||||||
|
|
||||||
|
# @see https://github.com/actions/labeler
|
||||||
|
# @see .github/labeler.yml
|
||||||
|
|
||||||
|
on:
|
||||||
|
- pull_request_target
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
labeler:
|
||||||
|
name: 'Manage labels'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Add checkout step to load labeler.yml locally,
|
||||||
|
# rather than from an API request
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/labeler@v5
|
||||||
|
with:
|
||||||
|
sync-labels: true
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
name: 'PR Title Checker'
|
||||||
|
|
||||||
|
# This workflow ensures that PR titles adhere to a pattern
|
||||||
|
# similar to conventional commits standard
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
|
||||||
|
env:
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_pr_title:
|
||||||
|
name: 'PR Title Checker'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check type
|
||||||
|
id: type
|
||||||
|
# @warning Keep in sync with https://github.com/tuono-labs/tuono-documentation/blob/main/src/routes/documentation/contributing/pull-requests.mdx
|
||||||
|
run: |
|
||||||
|
VALID_COMMIT_TYPES="chore|ci|docs|feat|fix|refactor|test"
|
||||||
|
REGEX="^(${VALID_COMMIT_TYPES})(\(.*\))?!?: .*"
|
||||||
|
|
||||||
|
if ! [[ $PR_TITLE =~ $REGEX ]]; then
|
||||||
|
echo "::error title=Type::The title has an incorrect type. Valid types are ${VALID_COMMIT_TYPES}"
|
||||||
|
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::notice title=Type::The title is using a valid type"
|
||||||
|
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check length
|
||||||
|
id: length
|
||||||
|
run: |
|
||||||
|
MAX_LENGTH=100
|
||||||
|
LENGTH=${#PR_TITLE}
|
||||||
|
|
||||||
|
if [ $LENGTH -gt $MAX_LENGTH ]; then
|
||||||
|
echo "::error title=Length::The title is longer than $MAX_LENGTH characters"
|
||||||
|
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::notice title=Length::The title length is within the maximum value of $MAX_LENGTH characters"
|
||||||
|
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check result
|
||||||
|
env:
|
||||||
|
FAILURE: ${{ contains(join(steps.*.outputs.result, ','), 'fail') }}
|
||||||
|
run: |
|
||||||
|
echo "Failure: $FAILURE"
|
||||||
|
if [ "$FAILURE" = "false" ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -8,16 +8,14 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- '*'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '.github/**'
|
concurrency:
|
||||||
- '*'
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
format:
|
||||||
name: Check format
|
name: Check format
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
@@ -30,4 +28,16 @@ jobs:
|
|||||||
uses: ./.github/actions/install-node-dependencies
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
- name: Test project
|
- name: Test project
|
||||||
run: pnpm repo:root:format:check
|
run: pnpm repo:root:format
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Repo root CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- format
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
|
|||||||
@@ -4,15 +4,11 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'Cargo.toml'
|
|
||||||
- 'crates/**'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '.github/**'
|
concurrency:
|
||||||
- 'Cargo.toml'
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
- 'crates/**'
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
@@ -40,6 +36,7 @@ jobs:
|
|||||||
os: 'ubuntu-latest'
|
os: 'ubuntu-latest'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -56,6 +53,7 @@ jobs:
|
|||||||
lint_and_fmt:
|
lint_and_fmt:
|
||||||
name: Check format and lint rust crates
|
name: Check format and lint rust crates
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -66,3 +64,16 @@ jobs:
|
|||||||
|
|
||||||
- run: cargo fmt --all -- --check
|
- run: cargo fmt --all -- --check
|
||||||
- run: cargo clippy -- -D warnings
|
- run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Rust CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- build_and_test
|
||||||
|
- lint_and_fmt
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
@@ -7,6 +7,10 @@ on:
|
|||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
spell_checking:
|
spell_checking:
|
||||||
name: LanguageTool
|
name: LanguageTool
|
||||||
|
|||||||
@@ -4,26 +4,15 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
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:
|
pull_request:
|
||||||
paths:
|
|
||||||
# ⬇ Keep paths in sync with `ci-documentation.yml`
|
concurrency:
|
||||||
- '.github/**'
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
- '*'
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
- '!Cargo.toml'
|
|
||||||
# ⬆ Keep paths in sync with `ci-documentation.yml`
|
|
||||||
- 'packages/**'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_test:
|
build_and_test:
|
||||||
name: Check build and test
|
name: Check build and test
|
||||||
timeout-minutes: 15
|
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
@@ -34,6 +23,7 @@ jobs:
|
|||||||
- 'windows-latest'
|
- 'windows-latest'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -61,10 +51,23 @@ jobs:
|
|||||||
uses: ./.github/actions/install-node-dependencies
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
run: pnpm format:check
|
run: pnpm run format
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: pnpm lint
|
run: pnpm run lint
|
||||||
|
|
||||||
- name: Types
|
- name: Typecheck
|
||||||
run: pnpm types
|
run: pnpm run typecheck
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Typescript CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs:
|
||||||
|
- build_and_test
|
||||||
|
- lint_and_fmt
|
||||||
|
steps:
|
||||||
|
- name: Exit with error if some jobs are not successful
|
||||||
|
run: exit 1
|
||||||
|
if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')) }}
|
||||||
@@ -8,6 +8,8 @@ dist/
|
|||||||
|
|
||||||
examples/*/pnpm-lock.yaml
|
examples/*/pnpm-lock.yaml
|
||||||
|
|
||||||
|
vite.config.ts.timestamp*
|
||||||
|
|
||||||
## Rust related ignores
|
## Rust related ignores
|
||||||
|
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
@@ -27,3 +29,4 @@ examples/*/pnpm-lock.yaml
|
|||||||
|
|
||||||
# Files, generated by the code editors
|
# Files, generated by the code editors
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,10 @@ pnpm-lock.yaml
|
|||||||
|
|
||||||
dist
|
dist
|
||||||
.tuono
|
.tuono
|
||||||
|
out
|
||||||
|
|
||||||
packages/lazy-fn-vite-plugin/tests/sources/*
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
packages/tuono-lazy-fn-vite-plugin/tests/sources/*
|
||||||
|
|
||||||
|
e2e/fixtures/*/test-results
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
* @Valerioageno
|
||||||
|
|
||||||
@@ -23,6 +23,4 @@ Consider [opening a new issue](https://github.com/tuono-labs/tuono/issues/new/ch
|
|||||||
|
|
||||||
**Did you write a patch that fixes a bug?**
|
**Did you write a patch that fixes a bug?**
|
||||||
|
|
||||||
- Open a new GitHub pull request with the patch.
|
[Visit the documentation to learn how to open a pull request](https://tuono.dev/documentation/contributing/pull-requests)
|
||||||
- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number, if applicable.
|
|
||||||
- The pull requests must pass all the CI pipelines
|
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/tuono",
|
"crates/tuono",
|
||||||
|
"crates/tuono_internal",
|
||||||
"crates/tuono_lib",
|
"crates/tuono_lib",
|
||||||
"crates/tuono_lib_macros",
|
"crates/tuono_lib_macros"
|
||||||
]
|
]
|
||||||
exclude = [
|
exclude = [
|
||||||
"apps/documentation",
|
|
||||||
"examples/with-mdx",
|
"examples/with-mdx",
|
||||||
"examples/tuono-app",
|
"examples/tuono-app",
|
||||||
"examples/tuono-tutorial",
|
"examples/tuono-tutorial",
|
||||||
"benches/tuono"
|
"examples/with-tailwind",
|
||||||
|
"e2e/fixtures/base",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -24,51 +24,34 @@ Some of its features are:
|
|||||||
- 🧬 Server Side Rendering
|
- 🧬 Server Side Rendering
|
||||||
- 🔥 Hot Module Reload
|
- 🔥 Hot Module Reload
|
||||||
|
|
||||||
## 📖 Documentation
|
## Documentation
|
||||||
|
|
||||||
The [documentation](https://tuono.dev/documentation) is available on
|
The [documentation](https://tuono.dev/) is available on
|
||||||
[tuono.dev](https://tuono.dev/).
|
[tuono.dev](https://tuono.dev/).
|
||||||
|
|
||||||
## Introduction
|
## Installation
|
||||||
|
|
||||||
**NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)**
|
Available on MacOS, Linux and Windows.
|
||||||
|
|
||||||
Tuono is a full-stack React framework with the server side written in Rust.
|
```sh
|
||||||
Because of this, Tuono is extremely fast, and the requests are handled by a multithreaded Rust server.
|
cargo install tuono
|
||||||
React is still React - it is just superpowered.
|
```
|
||||||
|
|
||||||
**Rust is a hard language, then writing server-side code is hard as well, right? (no again)**
|
|
||||||
|
|
||||||
Tuono provides a collection of utilities to handle the server side code seamlessly with the React code.
|
|
||||||
Each server-side route is managed in a separate file alongside the React route. The routing is handled
|
|
||||||
by Tuono based on the files defined within the `./src/routes` directory.
|
|
||||||
|
|
||||||
**How is Tuono different from Next.js?**
|
|
||||||
|
|
||||||
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/tuono-labs/tuono/tree/main/benches)).
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
|
Create a new project with:
|
||||||
To download it is required [cargo](https://doc.rust-lang.org/cargo/), which is the [rust](https://www.rust-lang.org/)
|
|
||||||
package manager.
|
|
||||||
|
|
||||||
Then run `cargo install tuono`.
|
```sh
|
||||||
|
tuono new [PROJECT_NAME]
|
||||||
|
```
|
||||||
|
|
||||||
To list all the available commands, run `tuono -h`
|
> You can optionally pass the --template (or -t) flag to directly start from a [template](https://github.com/tuono-labs/tuono/tree/main/examples)
|
||||||
|
|
||||||
To create a new project, run `tuono new [NAME]` (optionally, you can pass the `--template` (or `-t`) flag - check the
|
Now just install the dependencies with you favourite JS package manager (i.e. `npm install`) and start the development environment
|
||||||
[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`
|
```sh
|
||||||
|
tuono dev
|
||||||
Finally, when the project is ready to be deployed, just run `tuono build` to create the final React assets
|
```
|
||||||
and to set the server project in `production` mode.
|
|
||||||
|
|
||||||
Now to execute it just run `cargo run --release`.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
# Stadalone apps folder
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "tuono"
|
|
||||||
version = "0.0.1"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tuono"
|
|
||||||
path = ".tuono/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tuono_lib = "0.16.2"
|
|
||||||
glob = "0.3.1"
|
|
||||||
time = { version = "0.3", features = ["macros"] }
|
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Tuono starter
|
|
||||||
|
|
||||||
This is the starter tuono project. To download it run in your terminal:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ tuono new [NAME]
|
|
||||||
```
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "documentation",
|
|
||||||
"description": "The react/rust fullstack framework documentation",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"lint": "eslint .",
|
|
||||||
"format": "prettier --write --ignore-unknown .",
|
|
||||||
"format:check": "prettier --check --ignore-unknown .",
|
|
||||||
"types": "tsc --noEmit"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@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.16.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@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,14 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
'postcss-preset-mantine': {},
|
|
||||||
'postcss-simple-vars': {
|
|
||||||
variables: {
|
|
||||||
'mantine-breakpoint-xs': '36em',
|
|
||||||
'mantine-breakpoint-sm': '48em',
|
|
||||||
'mantine-breakpoint-md': '62em',
|
|
||||||
'mantine-breakpoint-lg': '75em',
|
|
||||||
'mantine-breakpoint-xl': '88em',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"polyfills": [
|
|
||||||
{
|
|
||||||
"identifier": "HTMLElement",
|
|
||||||
"import": {
|
|
||||||
"package": "@lit-labs/ssr-dom-shim",
|
|
||||||
"name": "HTMLElement"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identifier": "customElements",
|
|
||||||
"import": {
|
|
||||||
"package": "@lit-labs/ssr-dom-shim",
|
|
||||||
"name": "customElements"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { Breadcrumbs, Button } from '@mantine/core'
|
|
||||||
import { Link, Head } from 'tuono'
|
|
||||||
|
|
||||||
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
interface Breadcrumb {
|
|
||||||
href?: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
interface BreadcrumbsProps {
|
|
||||||
breadcrumbs: Array<Breadcrumb>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TuonoBreadcrumbs({
|
|
||||||
breadcrumbs = [],
|
|
||||||
}: BreadcrumbsProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{`{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "BreadcrumbList",
|
|
||||||
"itemListElement": [
|
|
||||||
{
|
|
||||||
"@type": "ListItem",
|
|
||||||
"position": 1,
|
|
||||||
"name": "Tuono - The React/Rust fullstack framework",
|
|
||||||
"item": "https://tuono.dev"
|
|
||||||
}${breadcrumbs.length > 0 ? ',' : ''}
|
|
||||||
${breadcrumbs
|
|
||||||
.map((br, i) =>
|
|
||||||
JSON.stringify({
|
|
||||||
'@type': 'ListItem',
|
|
||||||
position: i + 3,
|
|
||||||
name: br.label,
|
|
||||||
item: br.href ? `https://tuono.dev${br.href}` : undefined,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.join(',')}]
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</script>
|
|
||||||
</Head>
|
|
||||||
<Breadcrumbs
|
|
||||||
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
|
|
||||||
mb="md"
|
|
||||||
mt="md"
|
|
||||||
>
|
|
||||||
<Button href="/" component={Link} variant="subtle" radius="xl" p={5}>
|
|
||||||
<IconBolt />
|
|
||||||
</Button>
|
|
||||||
{breadcrumbs.map((br) => (
|
|
||||||
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
|
|
||||||
))}
|
|
||||||
</Breadcrumbs>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BreadcrumbElementProps {
|
|
||||||
href?: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BreadcrumbElement({
|
|
||||||
href,
|
|
||||||
label,
|
|
||||||
}: BreadcrumbElementProps): JSX.Element {
|
|
||||||
if (href) {
|
|
||||||
return (
|
|
||||||
<Button component={Link} href={href} variant="subtle" radius="xl" px={10}>
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button component="span" variant="light" radius="xl">
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import Breadcrumbs, { BreadcrumbElement } from './breadcrumbs'
|
|
||||||
|
|
||||||
export default Breadcrumbs
|
|
||||||
|
|
||||||
export { BreadcrumbElement as Element }
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { Button } from '@mantine/core'
|
|
||||||
import { IconEdit } from '@tabler/icons-react'
|
|
||||||
import { useRouter } from 'tuono'
|
|
||||||
|
|
||||||
const GITHUB_URL =
|
|
||||||
'https://github.com/tuono-labs/tuono/tree/main/apps/documentation/src/routes'
|
|
||||||
|
|
||||||
export default function EditPage(): JSX.Element {
|
|
||||||
const { pathname } = useRouter()
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
p={0}
|
|
||||||
mt={60}
|
|
||||||
component="a"
|
|
||||||
variant="transparent"
|
|
||||||
leftSection={<IconEdit />}
|
|
||||||
target="_blank"
|
|
||||||
href={GITHUB_URL.concat(pathname).concat('.mdx')}
|
|
||||||
>
|
|
||||||
Edit page
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import EditPage from './edit-page'
|
|
||||||
|
|
||||||
export default EditPage
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Center,
|
|
||||||
Container,
|
|
||||||
CopyButton,
|
|
||||||
Group,
|
|
||||||
rem,
|
|
||||||
Text,
|
|
||||||
Title,
|
|
||||||
} from '@mantine/core'
|
|
||||||
import { IconCopy, IconCheck } from '@tabler/icons-react'
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
|
|
||||||
export default function Hero(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Container size={1100} my={100}>
|
|
||||||
<Center>
|
|
||||||
<Title order={1}>Superfast fullstack react framework</Title>
|
|
||||||
</Center>
|
|
||||||
<Center mt={50}>
|
|
||||||
<Text fz="18px">
|
|
||||||
The technologies we love seamlessly working together to unleash the
|
|
||||||
<strong> highest web performance</strong> ever met on React
|
|
||||||
</Text>
|
|
||||||
</Center>
|
|
||||||
<Center mt={50}>
|
|
||||||
<Group>
|
|
||||||
<Button component={Link} href="/documentation" size="lg">
|
|
||||||
Get Started
|
|
||||||
</Button>
|
|
||||||
<CopyButton value="cargo install tuono">
|
|
||||||
{({ copied, copy }) => (
|
|
||||||
<Button
|
|
||||||
onClick={copy}
|
|
||||||
size="lg"
|
|
||||||
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
|
|
||||||
color="gray"
|
|
||||||
leftSection="$ cargo install tuono"
|
|
||||||
rightSection={
|
|
||||||
copied ? (
|
|
||||||
<IconCheck style={{ width: rem(20) }} />
|
|
||||||
) : (
|
|
||||||
<IconCopy style={{ width: rem(20) }} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</CopyButton>
|
|
||||||
</Group>
|
|
||||||
</Center>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Hero from './hero'
|
|
||||||
|
|
||||||
export default Hero
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxProvider from './mdx-provider'
|
|
||||||
|
|
||||||
export default MdxProvider
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { Text, type TextProps } from '@mantine/core'
|
|
||||||
|
|
||||||
export default function MdxBold(props: TextProps): JSX.Element {
|
|
||||||
return <Text fw={700} {...props} />
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxCode from './mdx-code'
|
|
||||||
|
|
||||||
export default MdxCode
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import type { JSX, HTMLAttributes } from 'react'
|
|
||||||
import { Code } from '@mantine/core'
|
|
||||||
|
|
||||||
export default function MdxCode(
|
|
||||||
props: HTMLAttributes<HTMLPreElement>,
|
|
||||||
): JSX.Element {
|
|
||||||
return <Code {...props} style={{ fontSize: 'inherit' }} />
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxLink from './mdx-link'
|
|
||||||
|
|
||||||
export default MdxLink
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import type { JSX, AnchorHTMLAttributes } from 'react'
|
|
||||||
import { Button } from '@mantine/core'
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
import { IconExternalLink } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
export default function MdxLink(
|
|
||||||
props: AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
||||||
): JSX.Element {
|
|
||||||
if (props.href?.startsWith('http') || props.href?.startsWith('mailto')) {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
component="a"
|
|
||||||
{...props}
|
|
||||||
target="_blank"
|
|
||||||
rightSection={
|
|
||||||
<IconExternalLink size="16px" style={{ marginLeft: -5 }} />
|
|
||||||
}
|
|
||||||
variant="transparent"
|
|
||||||
style={{ height: '20px' }}
|
|
||||||
mt={-2}
|
|
||||||
p={0}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
{...props}
|
|
||||||
target="_blank"
|
|
||||||
variant="transparent"
|
|
||||||
style={{ height: '20px' }}
|
|
||||||
mt={-2}
|
|
||||||
p={0}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxPre from './mdx-pre'
|
|
||||||
|
|
||||||
export default MdxPre
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.pre {
|
|
||||||
code {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { CodeHighlight } from '@mantine/code-highlight'
|
|
||||||
|
|
||||||
import styles from './mdx-pre.module.css'
|
|
||||||
|
|
||||||
interface PreProps {
|
|
||||||
children: {
|
|
||||||
props: {
|
|
||||||
children: string
|
|
||||||
className?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MdxPre({ children }: PreProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<CodeHighlight
|
|
||||||
className={styles.pre}
|
|
||||||
code={children.props.children || ''}
|
|
||||||
style={{ borderRadius: 8, fontWeight: 100 }}
|
|
||||||
language={children.props.className?.replace('language-', '')}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import type { JSX, ReactNode } from 'react'
|
|
||||||
import { MDXProvider } from '@mdx-js/react'
|
|
||||||
|
|
||||||
import MdxLink from './mdx-link'
|
|
||||||
import MdxPre from './mdx-pre'
|
|
||||||
import MdxQuote from './mdx-quote'
|
|
||||||
import MdxCode from './mdx-code'
|
|
||||||
import { h } from './mdx-title'
|
|
||||||
import MdxBold from './mdx-bold/mdx-bold'
|
|
||||||
|
|
||||||
interface MdxProviderProps {
|
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MdxProvider({
|
|
||||||
children,
|
|
||||||
}: MdxProviderProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<MDXProvider
|
|
||||||
components={{
|
|
||||||
a: MdxLink,
|
|
||||||
// @ts-expect-error: useless finding the correct props types
|
|
||||||
pre: MdxPre,
|
|
||||||
blockquote: MdxQuote,
|
|
||||||
code: MdxCode,
|
|
||||||
h1: h(1),
|
|
||||||
h2: h(2),
|
|
||||||
h3: h(3),
|
|
||||||
h4: h(4),
|
|
||||||
h5: h(5),
|
|
||||||
h6: h(6),
|
|
||||||
strong: MdxBold,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</MDXProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxQuote from './mdx-quote'
|
|
||||||
|
|
||||||
export default MdxQuote
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import type { JSX, HTMLAttributes } from 'react'
|
|
||||||
import { Blockquote, Space } from '@mantine/core'
|
|
||||||
|
|
||||||
export default function MdxQuote(
|
|
||||||
props: HTMLAttributes<HTMLQuoteElement>,
|
|
||||||
): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Blockquote
|
|
||||||
color="violet"
|
|
||||||
py={1}
|
|
||||||
px={20}
|
|
||||||
mt={30}
|
|
||||||
iconSize={30}
|
|
||||||
{...props}
|
|
||||||
style={{ borderRadius: 8 }}
|
|
||||||
/>
|
|
||||||
<Space h="md" />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import MdxTitle, { h } from './mdx-title'
|
|
||||||
|
|
||||||
export default MdxTitle
|
|
||||||
|
|
||||||
export { h }
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { Title, type TitleProps } from '@mantine/core'
|
|
||||||
|
|
||||||
export default function MdxTitle(props: TitleProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Title
|
|
||||||
data-heading={props.children}
|
|
||||||
data-order={props.order}
|
|
||||||
mt={20}
|
|
||||||
{...props}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
||||||
id={String(props.children ?? '')
|
|
||||||
.toLowerCase()
|
|
||||||
.replaceAll(' ', '-')}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const h =
|
|
||||||
(order: 1 | 2 | 3 | 4 | 5 | 6) =>
|
|
||||||
(props: TitleProps): JSX.Element => <MdxTitle order={order} {...props} />
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MetaTags from './meta-tags'
|
|
||||||
|
|
||||||
export default MetaTags
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import type { ReactNode } from 'react'
|
|
||||||
import { Head } from 'tuono'
|
|
||||||
|
|
||||||
interface MetaTagsProps {
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
type?: 'website' | 'article' | 'book' | 'video.movie' | 'music.song'
|
|
||||||
canonical: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MetaTags({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
type = 'article',
|
|
||||||
canonical,
|
|
||||||
}: MetaTagsProps): ReactNode {
|
|
||||||
return (
|
|
||||||
<Head>
|
|
||||||
<title>{title}</title>
|
|
||||||
<link rel="canonical" href={canonical} />
|
|
||||||
<meta
|
|
||||||
name="description"
|
|
||||||
content={
|
|
||||||
description ??
|
|
||||||
'The technologies we love seamlessly working together to unleash the highest web performance ever met on React'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="keywords"
|
|
||||||
content="ReactJs, Typescript, Rust, JavaScript, Fullstack framework, Web development"
|
|
||||||
/>
|
|
||||||
<meta property="og:title" content={title} />
|
|
||||||
<meta property="og:type" content={type} />
|
|
||||||
<meta property="og:image" content="https://tuono.dev/og-cover.png" />
|
|
||||||
<meta property="og:url" content={canonical} />
|
|
||||||
<meta property="og:description" content={description} />
|
|
||||||
<meta property="og:site_name" content="Tuono" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta name="twitter:site" content="@tuono_dev" />
|
|
||||||
<meta name="twitter:title" content={title} />
|
|
||||||
<meta name="twitter:description" content={description} />
|
|
||||||
<meta name="twitter:image" content="https://tuono.dev/og-cover.png" />
|
|
||||||
</Head>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { Flex, ActionIcon, Group } from '@mantine/core'
|
|
||||||
import { IconBrandGithub, IconBrandDiscord } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
import ThemeBtn from '../theme-btn'
|
|
||||||
|
|
||||||
export default function Actions(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Flex gap={8}>
|
|
||||||
<Group gap={8}>
|
|
||||||
<ActionIcon
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
aria-label="Check the project on github"
|
|
||||||
href="https://github.com/tuono-labs/tuono"
|
|
||||||
target="_blank"
|
|
||||||
component="a"
|
|
||||||
>
|
|
||||||
<IconBrandGithub />
|
|
||||||
</ActionIcon>
|
|
||||||
<ActionIcon
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
aria-label="Join the Tuono's community on Discord!"
|
|
||||||
href="https://discord.com/invite/khQzPa654B"
|
|
||||||
target="_blank"
|
|
||||||
component="a"
|
|
||||||
>
|
|
||||||
<IconBrandDiscord />
|
|
||||||
</ActionIcon>
|
|
||||||
</Group>
|
|
||||||
<ThemeBtn />
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Navbar from './navbar'
|
|
||||||
|
|
||||||
export default Navbar
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { AppShell, Box, Burger, Button, Flex } from '@mantine/core'
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
|
|
||||||
import Actions from './actions'
|
|
||||||
|
|
||||||
interface NavbarProps {
|
|
||||||
toggle: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
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}
|
|
||||||
hiddenFrom="sm"
|
|
||||||
>
|
|
||||||
Tuono
|
|
||||||
</Button>
|
|
||||||
<Box />
|
|
||||||
<Flex align="center" gap={8}>
|
|
||||||
<Actions />
|
|
||||||
<Burger onClick={toggle} hiddenFrom="sm" size="sm" />
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</AppShell.Header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import NavigationButtons from './navigation-buttons'
|
|
||||||
|
|
||||||
export default NavigationButtons
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { Box, Button, Text, Title, Flex } from '@mantine/core'
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
interface NavigationButton {
|
|
||||||
href: string
|
|
||||||
title: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NavigationButtonsProps {
|
|
||||||
prev?: NavigationButton
|
|
||||||
next?: NavigationButton
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NavigationButtons({
|
|
||||||
prev,
|
|
||||||
next,
|
|
||||||
}: NavigationButtonsProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Flex mt={50} gap={10}>
|
|
||||||
{prev && <NavigationBtn type="prev" {...prev} />}
|
|
||||||
{next && <NavigationBtn type="next" {...next} />}
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NavigationButtonProps extends NavigationButton {
|
|
||||||
type: 'next' | 'prev'
|
|
||||||
}
|
|
||||||
|
|
||||||
const NavigationBtn = ({
|
|
||||||
type,
|
|
||||||
title,
|
|
||||||
href,
|
|
||||||
}: NavigationButtonProps): JSX.Element => {
|
|
||||||
const heading = type === 'next' ? 'Next' : 'Previous'
|
|
||||||
const textAlign = type === 'next' ? 'left' : 'right'
|
|
||||||
const variant = type === 'next' ? 'filled' : 'outline'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
fullWidth
|
|
||||||
variant={variant}
|
|
||||||
href={href}
|
|
||||||
justify="space-between"
|
|
||||||
h="auto"
|
|
||||||
rightSection={type === 'next' && <IconArrowRight />}
|
|
||||||
leftSection={type === 'prev' && <IconArrowLeft />}
|
|
||||||
p="20"
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<Title component="span" display="block" order={4} style={{ textAlign }}>
|
|
||||||
{heading}
|
|
||||||
</Title>
|
|
||||||
<Text component="span" display="block" style={{ textAlign }}>
|
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
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,3 +0,0 @@
|
|||||||
import Sidebar from './sidebar'
|
|
||||||
|
|
||||||
export default Sidebar
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
.link {
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 0.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,42 +0,0 @@
|
|||||||
import type { JSX, ReactNode } from 'react'
|
|
||||||
import { NavLink, type NavLinkProps } from '@mantine/core'
|
|
||||||
import clsx from 'clsx'
|
|
||||||
import { Link, useRouter } from 'tuono'
|
|
||||||
import { IconChevronRight } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
import styles from './sidebar-link.module.css'
|
|
||||||
|
|
||||||
interface SidebarLinkProps {
|
|
||||||
label: string
|
|
||||||
href: string
|
|
||||||
onClick?: () => void
|
|
||||||
children?: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SidebarLink(
|
|
||||||
props: SidebarLinkProps & NavLinkProps,
|
|
||||||
): JSX.Element {
|
|
||||||
const { pathname } = useRouter()
|
|
||||||
|
|
||||||
const isActive = pathname === props.href
|
|
||||||
|
|
||||||
const internalProps = {
|
|
||||||
active: isActive,
|
|
||||||
className: clsx(styles.link, isActive && styles.active),
|
|
||||||
rightSection: props.children && (
|
|
||||||
<IconChevronRight
|
|
||||||
size="1.2rem"
|
|
||||||
stroke={1.5}
|
|
||||||
className="mantine-rotate-rtl"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
autoContrast: true,
|
|
||||||
...props,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.href.startsWith('#')) {
|
|
||||||
return <NavLink component="button" {...internalProps} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return <NavLink component={Link} {...internalProps} />
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
|
|
||||||
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 my="md" mx={10} />
|
|
||||||
}
|
|
||||||
if (el.type === 'title') {
|
|
||||||
return (
|
|
||||||
<Text
|
|
||||||
size="xs"
|
|
||||||
fw={700}
|
|
||||||
fz={12}
|
|
||||||
pl={12}
|
|
||||||
py={5}
|
|
||||||
children={el.label}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (el.children?.length) {
|
|
||||||
return (
|
|
||||||
<SidebarLink
|
|
||||||
href={el.href}
|
|
||||||
label={el.label}
|
|
||||||
key={i}
|
|
||||||
leftSection={el.leftIcon}
|
|
||||||
>
|
|
||||||
{el.children.map((child, index) => (
|
|
||||||
<SidebarLink
|
|
||||||
href={child.href}
|
|
||||||
label={child.label}
|
|
||||||
key={index}
|
|
||||||
onClick={close}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</SidebarLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<SidebarLink
|
|
||||||
href={el.href}
|
|
||||||
label={el.label}
|
|
||||||
onClick={close}
|
|
||||||
key={i}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</AppShell.Section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Sidebar({ close }: SidebarProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<AppShell.Navbar p="md">
|
|
||||||
<SidebarHeader close={close} />
|
|
||||||
<SidebarElements close={close} />
|
|
||||||
</AppShell.Navbar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
export interface Heading {
|
|
||||||
depth: number
|
|
||||||
content: string
|
|
||||||
id: string
|
|
||||||
getNode: () => HTMLHeadingElement
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
|
|
||||||
const result: Array<Heading> = []
|
|
||||||
|
|
||||||
for (const heading of headings) {
|
|
||||||
if (heading.id) {
|
|
||||||
result.push({
|
|
||||||
depth: parseInt(heading.getAttribute('data-order') || '1', 10),
|
|
||||||
content: heading.getAttribute('data-heading') || '',
|
|
||||||
id: heading.id,
|
|
||||||
getNode: () =>
|
|
||||||
document.getElementById(heading.id) as HTMLHeadingElement,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHeadings(): Array<Heading> {
|
|
||||||
const root = document.getElementById('mdx-root')
|
|
||||||
console.log(root)
|
|
||||||
|
|
||||||
if (!root) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return getHeadingsData(Array.from(root.querySelectorAll('[data-heading]')))
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
/*
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
import { TableOfContents } from './table-of-content'
|
|
||||||
|
|
||||||
export default TableOfContents
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
/*
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
.wrapper {
|
|
||||||
padding-left: var(--mantine-spacing-md);
|
|
||||||
position: sticky;
|
|
||||||
top: var(--mantine-spacing-xl);
|
|
||||||
right: 0;
|
|
||||||
padding-top: 55px;
|
|
||||||
flex: 0 0 calc(var(--docs-table-of-contents-width) - 20px);
|
|
||||||
width: 200px;
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: var(--mantine-spacing-md);
|
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-with-tabs] {
|
|
||||||
padding-top: 0;
|
|
||||||
top: calc(var(--mantine-spacing-xl) + 60px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inner {
|
|
||||||
padding-bottom: var(--mantine-spacing-xl);
|
|
||||||
padding-left: var(--mantine-spacing-md);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
display: block;
|
|
||||||
border-left: 1px solid transparent;
|
|
||||||
padding: 8px var(--mantine-spacing-md);
|
|
||||||
margin-left: -1px;
|
|
||||||
padding-left: calc(var(--toc-link-offset) * var(--mantine-spacing-lg));
|
|
||||||
border-top-right-radius: var(--mantine-radius-sm);
|
|
||||||
border-bottom-right-radius: var(--mantine-radius-sm);
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
color: var(--mantine-color-gray-7);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
color: var(--mantine-color-dark-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 1px solid transparent;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: -1px;
|
|
||||||
border-top-left-radius: var(--mantine-radius-sm);
|
|
||||||
border-bottom-left-radius: var(--mantine-radius-sm);
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-active] {
|
|
||||||
border-color: var(--mantine-color-violet-5);
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
color: var(--mantine-color-violet-8);
|
|
||||||
background-color: var(--mantine-color-violet-0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
color: var(--mantine-color-violet-1);
|
|
||||||
background-color: #534a6d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
margin-left: var(--mantine-spacing-md);
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.items {
|
|
||||||
border-left: 1px solid;
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
border-color: var(--mantine-color-gray-3) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
border-color: var(--mantine-color-dark-4) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 1px solid;
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
border-color: var(--mantine-color-gray-3) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
border-color: var(--mantine-color-dark-4) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
import type { JSX } from 'react'
|
|
||||||
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'
|
|
||||||
|
|
||||||
interface TableOfContentsProps {
|
|
||||||
withTabs: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveElement(rects: Array<DOMRect>): number {
|
|
||||||
if (rects.length === 0) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
const closest = rects.reduce(
|
|
||||||
(acc, item, index) => {
|
|
||||||
if (Math.abs(acc.position) < Math.abs(item.y)) {
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
index,
|
|
||||||
position: item.y,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ index: 0, position: rects[0].y },
|
|
||||||
)
|
|
||||||
|
|
||||||
return closest.index
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TableOfContents({
|
|
||||||
withTabs,
|
|
||||||
}: TableOfContentsProps): JSX.Element | null {
|
|
||||||
const [active, setActive] = useState(0)
|
|
||||||
const [headings, setHeadings] = useState<Array<Heading>>([])
|
|
||||||
const headingsRef = useRef<Array<Heading>>([])
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const filteredHeadings = headings.filter((heading) => heading.depth > 1)
|
|
||||||
|
|
||||||
const handleScroll = (): void => {
|
|
||||||
setActive(
|
|
||||||
getActiveElement(
|
|
||||||
headingsRef.current.map((d) => d.getNode().getBoundingClientRect()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const _headings = getHeadings()
|
|
||||||
headingsRef.current = _headings
|
|
||||||
setHeadings(_headings)
|
|
||||||
setActive(
|
|
||||||
getActiveElement(
|
|
||||||
_headings.map((d) => d.getNode().getBoundingClientRect()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return (): void => window.removeEventListener('scroll', handleScroll)
|
|
||||||
}, [router.pathname])
|
|
||||||
|
|
||||||
if (filteredHeadings.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = filteredHeadings.map((heading, index) => (
|
|
||||||
<Text
|
|
||||||
key={heading.id}
|
|
||||||
component={Link}
|
|
||||||
fz="sm"
|
|
||||||
p={10}
|
|
||||||
className={classes.link}
|
|
||||||
mod={{ active: active === index }}
|
|
||||||
href={`#${heading.id}`}
|
|
||||||
__vars={{ '--toc-link-offset': `${heading.depth - 1}` }}
|
|
||||||
>
|
|
||||||
{heading.content}
|
|
||||||
</Text>
|
|
||||||
))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
component="nav"
|
|
||||||
mod={{ 'with-tabs': withTabs }}
|
|
||||||
className={classes.wrapper}
|
|
||||||
>
|
|
||||||
<div className={classes.inner}>
|
|
||||||
<div>
|
|
||||||
<div className={classes.header}>
|
|
||||||
<IconList
|
|
||||||
style={{ width: rem(20), height: rem(20) }}
|
|
||||||
stroke={1.5}
|
|
||||||
/>
|
|
||||||
<Text className={classes.title}>Table of contents</Text>
|
|
||||||
</div>
|
|
||||||
<ScrollArea.Autosize
|
|
||||||
mah={`calc(100vh - ${rem(140)})`}
|
|
||||||
type="never"
|
|
||||||
offsetScrollbars
|
|
||||||
>
|
|
||||||
<div className={classes.items}>{items}</div>
|
|
||||||
</ScrollArea.Autosize>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import ThemeBtn from './theme-btn'
|
|
||||||
|
|
||||||
export default ThemeBtn
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
.icon {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
@mixin dark {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin light {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.light {
|
|
||||||
@mixin light {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import {
|
|
||||||
ActionIcon,
|
|
||||||
useMantineColorScheme,
|
|
||||||
useComputedColorScheme,
|
|
||||||
} from '@mantine/core'
|
|
||||||
import { IconSun, IconMoon } from '@tabler/icons-react'
|
|
||||||
import cx from 'clsx'
|
|
||||||
|
|
||||||
import classes from './theme-btn.module.css'
|
|
||||||
|
|
||||||
export default function ThemeBtn(): JSX.Element {
|
|
||||||
const { setColorScheme } = useMantineColorScheme()
|
|
||||||
const computedColorScheme = useComputedColorScheme('light', {
|
|
||||||
getInitialValueInEffect: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ActionIcon
|
|
||||||
onClick={() =>
|
|
||||||
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
|
||||||
}
|
|
||||||
variant="default"
|
|
||||||
size="lg"
|
|
||||||
aria-label="Toggle color scheme"
|
|
||||||
>
|
|
||||||
<IconSun className={cx(classes.icon, classes.light)} stroke={1.5} />
|
|
||||||
<IconMoon className={cx(classes.icon, classes.dark)} stroke={1.5} />
|
|
||||||
</ActionIcon>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// declaration.d.ts
|
|
||||||
declare module '*.css' {
|
|
||||||
const CSSModule: Record<string, string>
|
|
||||||
export default CSSModule
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
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 } 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'
|
|
||||||
|
|
||||||
interface RootRouteProps {
|
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const theme = createTheme({
|
|
||||||
primaryColor: 'violet',
|
|
||||||
primaryShade: { light: 6, dark: 9 },
|
|
||||||
fontFamily: 'Inter',
|
|
||||||
respectReducedMotion: true,
|
|
||||||
radius: {
|
|
||||||
xs: '8px',
|
|
||||||
lg: '8px',
|
|
||||||
xl: '8px',
|
|
||||||
md: '8px',
|
|
||||||
sm: '8px',
|
|
||||||
},
|
|
||||||
fontSizes: {
|
|
||||||
// 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
||||||
xs: '16px',
|
|
||||||
sm: '16px',
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
dark: [
|
|
||||||
'#d5d7e0',
|
|
||||||
'#acaebf',
|
|
||||||
'#8c8fa3',
|
|
||||||
'#666980',
|
|
||||||
'#4d4f66',
|
|
||||||
'#34354a',
|
|
||||||
'#2b2c3d',
|
|
||||||
'#1d1e30',
|
|
||||||
'#0c0d21',
|
|
||||||
'#01010a',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
headings: {
|
|
||||||
sizes: {
|
|
||||||
h1: {
|
|
||||||
fontSize: '48px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<meta charSet="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<link
|
|
||||||
rel="apple-touch-icon"
|
|
||||||
sizes="180x180"
|
|
||||||
href="/apple-touch-icon.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="32x32"
|
|
||||||
href="/favicon-32x32.png"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="16x16"
|
|
||||||
href="/favicon-16x16.png"
|
|
||||||
/>
|
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
|
||||||
</Head>
|
|
||||||
<ColorSchemeScript />
|
|
||||||
<MantineProvider theme={theme} cssVariablesResolver={resolver}>
|
|
||||||
<AppShell
|
|
||||||
layout="alt"
|
|
||||||
header={{ height: 60 }}
|
|
||||||
navbar={{
|
|
||||||
width: 300,
|
|
||||||
breakpoint: 'sm',
|
|
||||||
collapsed: { mobile: !opened },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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,55 +0,0 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Application state"
|
|
||||||
canonical="https://tuono.dev/documentation/application-state"
|
|
||||||
description="Learn how to add features to your Tuono application"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Application state' }]} />
|
|
||||||
|
|
||||||
# Application state
|
|
||||||
|
|
||||||
The main reason Tuono is fast is that it loads just the features that are needed for the project.
|
|
||||||
|
|
||||||
To define them, you need to fill the `ApplicationState` struct in the `./src/app.rs` file, and
|
|
||||||
they will be automatically available in all the handlers you will define across the application.
|
|
||||||
|
|
||||||
```rs
|
|
||||||
// src/app.rs
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ApplicationState {
|
|
||||||
pub website_name: String,
|
|
||||||
pub base_path: String
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() -> ApplicationState {
|
|
||||||
let website_name = "tuono".to_string();
|
|
||||||
let base_path = "http://localhost:3000".to_string();
|
|
||||||
|
|
||||||
ApplicationState {
|
|
||||||
website_name,
|
|
||||||
base_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> For the sake of simplicity here we just enable a `String` but you can add Database connections or
|
|
||||||
> HTTP clients (take a look [here](/documentation/tutorial/api-fetching) for an example with an HTTP client).
|
|
||||||
|
|
||||||
Now the `ApplicationState` is available on all the handlers as following:
|
|
||||||
|
|
||||||
```rs
|
|
||||||
// src/routes/index.rs
|
|
||||||
|
|
||||||
#[tuono_lib::handler]
|
|
||||||
// The first argument always is the Request
|
|
||||||
// The ApplicationState arguments are optional. You can use just the ones you need
|
|
||||||
// to use in the handler (with no specific order).
|
|
||||||
async fn my_handler(req: Request, website_name: String) -> Response {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - CLI"
|
|
||||||
canonical="https://tuono.dev/documentation/cli"
|
|
||||||
description="Tuono is the CLI that provides all the needed commands to handle the full-stack project."
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'CLI' }]} />
|
|
||||||
|
|
||||||
# CLI
|
|
||||||
|
|
||||||
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
|
|
||||||
|
|
||||||
> ☝️ Check the [installation](/documentation/installation) page if you haven't installed the
|
|
||||||
> `tuono` CLI yet.
|
|
||||||
|
|
||||||
To list all the available commands, run `tuono -h`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
The React/Rust full-stack framework
|
|
||||||
|
|
||||||
Usage: tuono <COMMAND>
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
dev Start the development environment
|
|
||||||
build Build the production assets
|
|
||||||
new Scaffold a new project
|
|
||||||
help Print this message or the help of the given subcommand(s)
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help Print help
|
|
||||||
-V, --version Print version
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a new project, run `tuono new [NAME]` (optionally you can pass the --template (or -t) flag - check the examples' folder).
|
|
||||||
|
|
||||||
Then to run the local development environment run inside the project folder `tuono dev`
|
|
||||||
|
|
||||||
Finally, when the project will be ready to be deployed just run `tuono build` to create the final React assets and to set the server project in the production mode.
|
|
||||||
|
|
||||||
Now to execute it, just run `cargo run --release`.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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,48 +0,0 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Contributing"
|
|
||||||
canonical="https://tuono.dev/documentation/contributing"
|
|
||||||
description="The project is massive - if you like it, do consider contributing!"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
|
||||||
|
|
||||||
# Contributing
|
|
||||||
|
|
||||||
## TL;DR
|
|
||||||
|
|
||||||
The project is massive - if you like it, do consider contributing!
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
The `tuono` project can mostly be split into the following subdomains:
|
|
||||||
|
|
||||||
- The CLI
|
|
||||||
- The Rust backend
|
|
||||||
- The React frontend
|
|
||||||
- The documentation website (which is written with tuono 🚀)
|
|
||||||
|
|
||||||
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__`.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
It's not strictly required to know both React (& typescript) and Rust (even though it
|
|
||||||
would be a great nice to have).
|
|
||||||
|
|
||||||
Without taking into account specific cases, we can mostly split the domain requirements by:
|
|
||||||
|
|
||||||
- The `CLI` needs Rust knowledge (even though a couple of scenarios might also need Typescript)
|
|
||||||
- The Backend needs just `Rust`
|
|
||||||
- The Frontend needs just `React` & `Typescript`
|
|
||||||
- The documentation website needs just `React` & `Typescript` (or even less, since most of the
|
|
||||||
code is markdown).
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Contributing"
|
|
||||||
canonical="https://tuono.dev/documentation/contributing"
|
|
||||||
description="The project is massive - if you like it, do consider contributing!"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
|
||||||
|
|
||||||
# Contributing
|
|
||||||
|
|
||||||
## TL;DR
|
|
||||||
|
|
||||||
The project is massive - if you like it, do consider contributing!
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
The `tuono` project can mostly be split into the following subdomains:
|
|
||||||
|
|
||||||
- The CLI
|
|
||||||
- The Rust backend
|
|
||||||
- The React frontend
|
|
||||||
- The documentation website (which is written with tuono 🚀)
|
|
||||||
|
|
||||||
To check what the knowledge requirements are for each domain, check the
|
|
||||||
[requirements](#requirements) section below.
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
It's not strictly required to know both React (& typescript) and Rust (even though it
|
|
||||||
would be a great nice to have).
|
|
||||||
|
|
||||||
Without taking into account specific cases, we can mostly split the domain requirements by:
|
|
||||||
|
|
||||||
- The `CLI` needs Rust knowledge (even though a couple of scenarios might also need Typescript)
|
|
||||||
- The Backend needs just `Rust`
|
|
||||||
- The Frontend needs just `React` & `Typescript`
|
|
||||||
- The documentation website needs just `React` & `Typescript` (or even less, since most of the
|
|
||||||
code is markdown).
|
|
||||||
|
|
||||||
import NavigationButtons from '../../../components/navigation-buttons'
|
|
||||||
|
|
||||||
<NavigationButtons
|
|
||||||
next={{
|
|
||||||
title: 'Local development',
|
|
||||||
href: '/documentation/contributing/local-development',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Contributing - Local development"
|
|
||||||
canonical="https://tuono.dev/documentation/contributing/local-development"
|
|
||||||
description="Contribute to Tuono. Learn here how to setup the repository for local development"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs
|
|
||||||
breadcrumbs={[
|
|
||||||
{ label: '✨ Contributing', href: '/documentation/contributing' },
|
|
||||||
{ label: 'Local development' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
# Local development
|
|
||||||
|
|
||||||
Thanks for your interest! In this page, you find the instructions to set up `tuono` on your local environment!
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
### Fork and clone repository
|
|
||||||
|
|
||||||
After [forking the repo on GitHub](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git clone https://github.com/<your-name-here>/tuono
|
|
||||||
cd tuono
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rust tool chain
|
|
||||||
|
|
||||||
Install the Rust programming language tool chain (`rust` and `cargo`).
|
|
||||||
Follows instructions in the official [docs](https://rustup.rs/)
|
|
||||||
|
|
||||||
### Node.js — runtime
|
|
||||||
|
|
||||||
Install `Node.js`.
|
|
||||||
You can follow the instructions from the [Node official site](https://nodejs.org/en/download/package-manager)
|
|
||||||
|
|
||||||
> 💡 This project has a `.nvmrc` file to specify the node version used in development.
|
|
||||||
>
|
|
||||||
> Consider to use [nvm](https://github.com/nvm-sh/nvm) so you can run
|
|
||||||
>
|
|
||||||
> ```sh
|
|
||||||
> nvm use
|
|
||||||
> ```
|
|
||||||
>
|
|
||||||
> to simply pick up the correct version!
|
|
||||||
|
|
||||||
### Node.js — package manager
|
|
||||||
|
|
||||||
We use [`pnpm`](https://pnpm.io) as Node.js package manager.
|
|
||||||
|
|
||||||
You can see which version of yarn we use by checking the `packageManager` field in the root `package.json`.
|
|
||||||
|
|
||||||
### Pre-flight checks
|
|
||||||
|
|
||||||
To check that everything is working properly, run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
turbo run check-all
|
|
||||||
cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tuono development
|
|
||||||
|
|
||||||
1. Start tuono frontend build using
|
|
||||||
|
|
||||||
```sh
|
|
||||||
turbo run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
2. In another terminal run
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
To automatically rebuild crates on code change, consider using `cargo-watch` crate
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo watch -x build -w crates/
|
|
||||||
```
|
|
||||||
|
|
||||||
3. You can now use the binary inside `/target/debug/tuono` in another folder on your local machine
|
|
||||||
|
|
||||||
> Consider adding an alias to your shell setup file
|
|
||||||
>
|
|
||||||
> ```sh
|
|
||||||
> alias t="/path-to-repo/target/debug/tuono"
|
|
||||||
> ```
|
|
||||||
|
|
||||||
## Documentation development
|
|
||||||
|
|
||||||
1. Change the current working directory to the documentation folder:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd apps/documentation
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Run
|
|
||||||
|
|
||||||
```sh
|
|
||||||
tuono dev
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Open the localhost URL.
|
|
||||||
|
|
||||||
> On the documentation remember that `tuono` `npm` package is installed from the registry and
|
|
||||||
> it is not linked to the repository.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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,57 +0,0 @@
|
|||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
|
||||||
|
|
||||||
import MetaTags from '@/components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Installation"
|
|
||||||
canonical="https://tuono.dev/documentation/installation"
|
|
||||||
description="Tuono is a development environment built in rust and typescript that outputs a website written in both the languages"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Installation' }]} />
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
`Tuono` is a development environment built in rust and typescript that outputs a website written in both the
|
|
||||||
languages. Since then, you need the following tools installed on your computer to work with it:
|
|
||||||
|
|
||||||
- `Rust` - The Rust programming language tool chain (Go [here](https://rustup.rs/) for installing both `rust` and `cargo`)
|
|
||||||
- `Cargo` - The rust package manager
|
|
||||||
- `NodeJs` - The JavaScript runtime (Go [here](https://nodejs.org/en/download/package-manager) for installing both `nodejs` and `npm`)
|
|
||||||
- `npm` or `yarn` or `pnpm` - A javaScript package manager
|
|
||||||
|
|
||||||
> Node.js is needed just for the development environment. The final output will run on a Rust server.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
The tuono `CLI` is hosted on [crates.io](https://crates.io/crates/tuono) to download and install it, just run it on a terminal:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install tuono
|
|
||||||
```
|
|
||||||
|
|
||||||
To check that it is correctly installed, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tuono --version
|
|
||||||
```
|
|
||||||
|
|
||||||
Run `tuono -h` to see all the available commands.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
The React/Rust full-stack framework
|
|
||||||
|
|
||||||
Usage: tuono <COMMAND>
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
dev Start the development environment
|
|
||||||
build Build the production assets
|
|
||||||
new Scaffold a new project
|
|
||||||
help Print this message or the help of the given subcommand(s)
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help Print help
|
|
||||||
-V, --version Print version
|
|
||||||
```
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
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>
|
|
||||||
```
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
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,14 +0,0 @@
|
|||||||
import MetaTags from '@/components/meta-tags'
|
|
||||||
|
|
||||||
<MetaTags
|
|
||||||
title="Tuono - Routing"
|
|
||||||
canonical="https://tuono.dev/documentation/routing/intro"
|
|
||||||
/>
|
|
||||||
|
|
||||||
import Breadcrumbs, { Element } from '@/components/breadcrumbs'
|
|
||||||
|
|
||||||
<Breadcrumbs breadcrumbs={[{ label: 'Routing' }]} />
|
|
||||||
|
|
||||||
# Routing index
|
|
||||||
|
|
||||||
TODO
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
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).
|
|
||||||