mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-27 13:52:47 -07:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aaae7db24d | |||
| 48c3934b6e | |||
| 258b4af0b1 | |||
| edfbd03f13 | |||
| 9cb0f62da7 | |||
| 9591960e31 | |||
| 132d8ebe8f | |||
| 4020073aca | |||
| 5025692220 | |||
| d04a4c4103 | |||
| 7e6dea68f4 | |||
| a046833414 | |||
| cfa59ca8bf | |||
| d470bd58f2 |
@@ -18,32 +18,32 @@ jobs:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./apps/documentation
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Setup rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install tuono
|
||||
run: cargo install tuono@0.10.3
|
||||
- name: Setup rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm i -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --ignore-workspace
|
||||
- name: Install tuono
|
||||
run: cargo install tuono@0.10.4
|
||||
|
||||
- name: Build project
|
||||
run: tuono build --static
|
||||
- name: Install pnpm
|
||||
run: npm i -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --ignore-workspace
|
||||
|
||||
- name: Build project
|
||||
run: tuono build --static
|
||||
|
||||
fmt-lint-and-types:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
@@ -53,23 +53,23 @@ jobs:
|
||||
run:
|
||||
working-directory: ./apps/documentation
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm i -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --ignore-workspace
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Check formatting
|
||||
run: pnpm format:check
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
- name: Types
|
||||
run: pnpm types
|
||||
- name: Install pnpm
|
||||
run: npm i -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --ignore-workspace
|
||||
|
||||
- name: Check formatting
|
||||
run: pnpm format:check
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
- name: Types
|
||||
run: pnpm types
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
steps:
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -28,14 +28,14 @@ jobs:
|
||||
node-version: 20
|
||||
|
||||
- name: Setup rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
- name: Install tuono
|
||||
run: cargo install tuono@0.10.3
|
||||
run: cargo install tuono@0.10.4
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm i -g pnpm
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --ignore-workspace
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ name = "tuono"
|
||||
path = ".tuono/main.rs"
|
||||
|
||||
[dependencies]
|
||||
tuono_lib = "0.10.3"
|
||||
tuono_lib = "0.11.1"
|
||||
glob = "0.3.1"
|
||||
time = { version = "0.3", features = ["macros"] }
|
||||
serde = { version = "1.0.202", features = ["derive"] }
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"tuono": "0.10.3"
|
||||
"tuono": "0.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mdx": "^2.0.13",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 314 KiB |
@@ -1,31 +1,67 @@
|
||||
import { Breadcrumbs, Button } from '@mantine/core'
|
||||
import { Link } from 'tuono'
|
||||
import { Link, Head } from 'tuono'
|
||||
|
||||
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
interface Breadcrumb {
|
||||
href?: string
|
||||
label: string
|
||||
}
|
||||
interface BreadcrumbsProps {
|
||||
breadcrumbs: Breadcrumb[]
|
||||
}
|
||||
|
||||
export default function TuonoBreadcrumbs({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode
|
||||
}): JSX.Element {
|
||||
breadcrumbs = [],
|
||||
}: BreadcrumbsProps): JSX.Element {
|
||||
return (
|
||||
<Breadcrumbs
|
||||
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
|
||||
mb="md"
|
||||
mt="md"
|
||||
>
|
||||
<Button
|
||||
href="/documentation"
|
||||
component={Link}
|
||||
variant="subtle"
|
||||
radius="xl"
|
||||
p={5}
|
||||
<>
|
||||
<Head>
|
||||
<script type="application/ld+json">
|
||||
{`[
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
'@type': "ListItem",
|
||||
"position": 1,
|
||||
"name": "Tuono - Documentation",
|
||||
"item": "https://tuono.dev/documentation",
|
||||
}${breadcrumbs.length > 0 ? ',' : ''}
|
||||
${breadcrumbs
|
||||
.map((br, i) =>
|
||||
JSON.stringify({
|
||||
'@type': 'ListItem',
|
||||
position: i + 2,
|
||||
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"
|
||||
>
|
||||
<IconBolt />
|
||||
</Button>
|
||||
{children}
|
||||
</Breadcrumbs>
|
||||
<Button
|
||||
href="/documentation"
|
||||
component={Link}
|
||||
variant="subtle"
|
||||
radius="xl"
|
||||
p={5}
|
||||
>
|
||||
<IconBolt />
|
||||
</Button>
|
||||
{breadcrumbs.map((br) => (
|
||||
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
|
||||
))}
|
||||
</Breadcrumbs>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import MetaTags from './meta-tags'
|
||||
|
||||
export default MetaTags
|
||||
@@ -0,0 +1,41 @@
|
||||
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" />
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import NavigationButtons from './navigation-buttons'
|
||||
|
||||
export default NavigationButtons
|
||||
@@ -0,0 +1,62 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -86,8 +86,8 @@ export default function RootRoute({ children }: RootRouteProps): JSX.Element {
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<ColorSchemeScript />
|
||||
</Head>
|
||||
<ColorSchemeScript />
|
||||
<MantineProvider theme={theme}>
|
||||
<AppShell
|
||||
header={{ height: 60 }}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tuono - CLI</title>
|
||||
</Head>
|
||||
<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>
|
||||
<Element label="CLI" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'CLI' }]} />
|
||||
|
||||
# CLI
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tuono - Contributing</title>
|
||||
</Head>
|
||||
<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>
|
||||
<Element label="✨ Contributing" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs breadcrumbs={[{ label: '✨ Contributing' }]} />
|
||||
|
||||
# Contributing
|
||||
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tuono - Documentation</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Documentation"
|
||||
canonical="https://tuono.dev/documentation"
|
||||
description="Tuono is a full-stack framework for building React applications using Rust as the backend."
|
||||
/>
|
||||
|
||||
import Breadcrumbs from '../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs></Breadcrumbs>
|
||||
<Breadcrumbs />
|
||||
|
||||
# Tuono
|
||||
|
||||
**Tuono is a full-stack framework for building React applications using Rust as the
|
||||
backend.**
|
||||
Tuono is a full-stack framework for building React applications using Rust as the
|
||||
backend.
|
||||
|
||||
Tuono (Italian word for "thunder", pronounced /2 Oh No/). Why Tuono? Just a badass name.
|
||||
|
||||
## Introduction
|
||||
|
||||
**NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)**
|
||||
NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)
|
||||
|
||||
Tuono is a full-stack React framework with the server side written in Rust.
|
||||
Because of this, Tuono is extremely fast and, the requests are handled by a multithreaded Rust server.
|
||||
React is still React - it is just superpowered.
|
||||
|
||||
**Rust is a hard language, then writing server side code is hard as well, right? (no again)**
|
||||
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.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Head } from 'tuono'
|
||||
import Breadcrumbs, { Element } from '../../components/breadcrumbs'
|
||||
|
||||
<Head>
|
||||
<title>Tuono - Installation</title>
|
||||
</Head>
|
||||
import MetaTags from '../../components/meta-tags'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Installation" />
|
||||
</Breadcrumbs>
|
||||
<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
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tuono - Routing</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Routing"
|
||||
canonical="https://tuono.dev/documentation/routing/intro"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Routing" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'Routing' }]} />
|
||||
|
||||
# Routing index
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tuono - Routing</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Routing"
|
||||
canonical="https://tuono.dev/documentation/routing"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Routing" href="/documentation/routing" />
|
||||
<Element label="Project structure" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Routing', href: '/documentation/routing' },
|
||||
{ label: 'Project structure' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Project structure
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - API fetching</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - API fetching"
|
||||
canonical="https://tuono.dev/documentation/tutorial/api-fetching"
|
||||
description="Learn how to fetch remote APIs on the backend"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="API fetching" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
||||
{ label: 'API fetching' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# API fetching
|
||||
|
||||
@@ -103,3 +107,13 @@ export default function IndexPage({
|
||||
```
|
||||
|
||||
Refresh the browser now! A bit ugly, but all the Pokémon are finally printed on screen!
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{
|
||||
title: 'Development setup',
|
||||
href: '/documentation/tutorial/development-setup',
|
||||
}}
|
||||
next={{ title: 'Components', href: '/documentation/tutorial/components' }}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - Components</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Components"
|
||||
canonical="https://tuono.dev/documentation/tutorial/components"
|
||||
description="Learn how to manage the components in a Tuono codebase"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="Components" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
||||
{ label: 'Components' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Components
|
||||
|
||||
@@ -127,3 +131,13 @@ export default function PokemonLink({
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{ title: 'API fetching', href: '/documentation/tutorial/api-fetching' }}
|
||||
next={{
|
||||
title: 'Dynamic routes',
|
||||
href: '/documentation/tutorial/dynamic-routes',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - Conclusion</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Conclusion"
|
||||
canonical="https://tuono.dev/documentation/tutorial/conclusion"
|
||||
description="You just created a multi thread full stack application with Rust and React"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="Conclusion" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
||||
{ label: 'Conclusion' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Conclusion
|
||||
|
||||
@@ -22,3 +26,12 @@ As I mentioned in the introduction, I'd love to hear what you thought about the
|
||||
at [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it) or in Twitter (X) DMs [@valerioageno](https://twitter.com/valerioageno).
|
||||
|
||||
Ciao
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{
|
||||
title: 'Production build',
|
||||
href: '/documentation/tutorial/production',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - Development setup</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Development setup"
|
||||
canonical="https://tuono.dev/documentation/tutorial/development-setup"
|
||||
description="Learn how to setup a new Tuono project"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="Development setup" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
||||
{ label: 'Development setup' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Development setup
|
||||
|
||||
@@ -64,3 +68,10 @@ The first time might take a little bit because it will install all the Rust’s
|
||||
> In case you face any error delete the cache `.tuono` folder and run it again!
|
||||
|
||||
Then open [http://localhost:3000/](http://localhost:3000) on the browser.
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{ title: 'Tutorial', href: '/documentation/tutorial' }}
|
||||
next={{ title: 'API fetching', href: '/documentation/tutorial/api-fetching' }}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - Dynamic routes</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Dynamic routes"
|
||||
canonical="https://tuono.dev/documentation/tutorial/dynamic-routes"
|
||||
description="Learn how to catch more endpoint with the same route loading dynamic data"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="Dynamic routes" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
||||
{ label: 'Dynamic routes' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Dynamic routes
|
||||
|
||||
@@ -157,3 +161,13 @@ export default function PokemonView({
|
||||
font-weight: 700;
|
||||
}
|
||||
```
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{ title: 'Components', href: '/documentation/tutorial/components' }}
|
||||
next={{
|
||||
title: 'Error handling',
|
||||
href: '/documentation/tutorial/error-handling',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - Error handling</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Error handling"
|
||||
canonical="https://tuono.dev/documentation/tutorial/error-handling"
|
||||
description="Learn how to handle the server side error on your Tuono project"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="Error handling" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
||||
{ label: 'Error handling' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Error handling
|
||||
|
||||
@@ -100,3 +104,13 @@ async fn get_all_pokemons(_req: Request, fetch: Client) -> Response {
|
||||
|
||||
If you now try to load a not-existing Pokémon (`http://localhost:3000/pokemons/tuono-pokemon`) you will
|
||||
correctly receive a 404 status code in the console.
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{
|
||||
title: 'Dynamic routes',
|
||||
href: '/documentation/tutorial/dynamic-routes',
|
||||
}}
|
||||
next={{ title: 'SEO and meta tags', href: '/documentation/tutorial/seo' }}
|
||||
/>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tuono - Tutorial</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Tutorial"
|
||||
canonical="https://tuono.dev/documentation/tutorial"
|
||||
description="This tutorial is meant to give you a sneak peek at the framework and is intended to evolve during the development - be sure to have installed the latest version"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs breadcrumbs={[{ label: 'Tutorial' }]} />
|
||||
|
||||
# Tutorial
|
||||
|
||||
@@ -26,3 +26,12 @@ $ tuono new tutorial --template tutorial
|
||||
|
||||
> I'd love to hear your thoughts about the framework and the tutorial - feel free to reach me at [valerioageno@yahoo.it](mailto:valerioageno@ahoo.it)
|
||||
> or on Twitter (X) DM [@valerioageno](https://twitter.com/valerioageno)
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
next={{
|
||||
title: 'Development setup',
|
||||
href: '/documentation/tutorial/development-setup',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - Building for production</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Building for production"
|
||||
canonical="https://tuono.dev/documentation/tutorial/production"
|
||||
description="Learn how to build the production project and run the optimize version"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="Production build" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
||||
{ label: 'Production build' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Production build
|
||||
|
||||
@@ -31,3 +35,13 @@ Check again [`http://localhost:3000/`](http://localhost:3000/) This environment
|
||||
optimizations ready to unleash the power of a rust server that seamlessly renders a React application!🚀
|
||||
|
||||
> Note: The `out` directory is not standalone. You can't rely just on it to run the production server.
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{
|
||||
title: 'Server redirection',
|
||||
href: '/documentation/tutorial/redirections',
|
||||
}}
|
||||
next={{ title: 'Conclusion', href: '/documentation/tutorial/conclusion' }}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - Redirections</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - Redirection"
|
||||
canonical="https://tuono.dev/documentation/tutorial/redirection"
|
||||
description="Learn how to redirect to different route from the server"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="Redirections" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
||||
{ label: 'Redirections' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# Redirections
|
||||
|
||||
@@ -45,3 +49,13 @@ Now let's create the button in the home page to actually point to it!
|
||||
|
||||
Now at [http://localhost:3000/](http:/localhost:3000/) You will find a new link at the beginning of the list.
|
||||
Click on it and see the application automatically redirecting you to your favourite pokemon's route!
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{ title: 'SEO and meta tags', href: '/documentation/tutorial/seo' }}
|
||||
next={{
|
||||
title: 'Production build',
|
||||
href: '/documentation/tutorial/production',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Head } from 'tuono'
|
||||
import MetaTags from '../../../components/meta-tags'
|
||||
|
||||
<Head>
|
||||
<title>Tutorial - SEO and meta tags</title>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - SEO and meta tags"
|
||||
canonical="https://tuono.dev/documentation/tutorial/eeo"
|
||||
description="Learn how to handle meaningful meta tags for your Tuono project"
|
||||
/>
|
||||
|
||||
import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
|
||||
|
||||
<Breadcrumbs>
|
||||
<Element label="Tutorial" href="/documentation/tutorial" />
|
||||
<Element label="SEO and meta tags" />
|
||||
</Breadcrumbs>
|
||||
<Breadcrumbs
|
||||
breadcrumbs={[
|
||||
{ label: 'Tutorial', href: '/documentation/tutorial' },
|
||||
{ label: 'SEO and meta tags' },
|
||||
]}
|
||||
/>
|
||||
|
||||
# SEO and meta tags
|
||||
|
||||
@@ -94,3 +98,16 @@ export default function Pokemon({ data }: TuonoProps): JSX.Element {
|
||||
```
|
||||
|
||||
The `Head` component takes as children any valid HTML meta tag.
|
||||
|
||||
import NavigationButtons from '../../../components/navigation-buttons'
|
||||
|
||||
<NavigationButtons
|
||||
prev={{
|
||||
title: 'Error handling',
|
||||
href: '/documentation/tutorial/error-handling',
|
||||
}}
|
||||
next={{
|
||||
title: 'Server redirection',
|
||||
href: '/documentation/tutorial/redirections',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { Head } from 'tuono'
|
||||
import Hero from '../components/hero'
|
||||
import MetaTags from '../components/meta-tags'
|
||||
|
||||
export default function IndexPage(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Tuono - The React/Rust full-stack framework</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="The technologies we love seamlessly working together to unleash the highest web performance ever met on React"
|
||||
/>
|
||||
</Head>
|
||||
<MetaTags
|
||||
title="Tuono - The React/Rust full-stack framework"
|
||||
canonical="https://tuono.dev"
|
||||
description="The technologies we love seamlessly working together to unleash the highest web performance ever met on React"
|
||||
/>
|
||||
<Hero />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono"
|
||||
version = "0.10.4"
|
||||
version = "0.11.2"
|
||||
edition = "2021"
|
||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||
description = "The react/rust fullstack framework"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono_lib"
|
||||
version = "0.10.4"
|
||||
version = "0.11.2"
|
||||
edition = "2021"
|
||||
authors = ["V. Ageno <valerioageno@yahoo.it>"]
|
||||
description = "The react/rust fullstack framework"
|
||||
@@ -33,7 +33,7 @@ either = "1.13.0"
|
||||
tower-http = {version = "0.6.0", features = ["fs"]}
|
||||
colored = "2.1.0"
|
||||
|
||||
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.10.4"}
|
||||
tuono_lib_macros = {path = "../tuono_lib_macros", version = "0.11.2"}
|
||||
# Match the same version used by axum
|
||||
tokio-tungstenite = "0.24.0"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tuono_lib_macros"
|
||||
version = "0.10.4"
|
||||
version = "0.11.2"
|
||||
edition = "2021"
|
||||
description = "The react/rust fullstack framework"
|
||||
keywords = [ "react", "typescript", "fullstack", "web", "ssr"]
|
||||
|
||||
@@ -2,21 +2,21 @@ import { Head, type TuonoProps } from 'tuono'
|
||||
import PokemonView from '../../components/PokemonView'
|
||||
|
||||
interface Pokemon {
|
||||
name: string
|
||||
id: string
|
||||
weight: number
|
||||
height: number
|
||||
name: string
|
||||
id: string
|
||||
weight: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export default function PokemonPage({
|
||||
data,
|
||||
data,
|
||||
}: TuonoProps<Pokemon>): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Pokemon: {data?.name}</title>
|
||||
</Head>
|
||||
<PokemonView pokemon={data} />
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{`Pokemon: ${data?.name}`}</title>
|
||||
</Head>
|
||||
<PokemonView pokemon={data} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "workspace",
|
||||
"packageManager": "pnpm@9.1.1",
|
||||
"packageManager": "pnpm@9.12.3+sha256.24235772cc4ac82a62627cd47f834c72667a2ce87799a846ec4e8e555e2d4b8b",
|
||||
"scripts": {
|
||||
"dev": "turbo watch dev",
|
||||
"build": "turbo build --filter tuono",
|
||||
@@ -21,7 +21,7 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@tanstack/config": "^0.7.0",
|
||||
"@types/node": "^20.12.7",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^18.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-fs-router-vite-plugin",
|
||||
"version": "0.10.4",
|
||||
"version": "0.11.2",
|
||||
"description": "Plugin for the tuono's file system router. Tuono is the react/rust fullstack framework",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
|
||||
@@ -21,6 +21,7 @@ import { ROUTES_FOLDER, ROOT_PATH_ID, GENERATED_ROUTE_TREE } from './constants'
|
||||
|
||||
import { format } from 'prettier'
|
||||
import { sortRouteNodes } from './sort-route-nodes'
|
||||
import isDefaultExported from './utils/is-default-exported'
|
||||
|
||||
let latestTask = 0
|
||||
|
||||
@@ -50,6 +51,13 @@ async function getRouteNodes(
|
||||
if (dirent.isDirectory()) {
|
||||
await recurse(relativePath)
|
||||
} else if (fullPath.match(/\.(tsx|ts|jsx|js|mdx)$/)) {
|
||||
// Check that the route is correctly default exported
|
||||
if (
|
||||
fullPath.match(/\.(tsx|ts|jsx|js)$/) &&
|
||||
!isDefaultExported((await fsp.readFile(fullPath)).toString())
|
||||
) {
|
||||
return
|
||||
}
|
||||
const filePath = replaceBackslash(path.join(dir, dirent.name))
|
||||
const filePathNoExt = removeExt(filePath)
|
||||
let routePath =
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export default function isDefaultExported(source: string): boolean {
|
||||
const regex = new RegExp('export default')
|
||||
return regex.test(source)
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
/** */
|
||||
// export default
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
@@ -1 +1 @@
|
||||
/** */
|
||||
/** export default */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-lazy-fn-vite-plugin",
|
||||
"version": "0.10.4",
|
||||
"version": "0.11.2",
|
||||
"description": "Plugin for the tuono's lazy fn. Tuono is the react/rust fullstack framework",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono-router",
|
||||
"version": "0.10.4",
|
||||
"version": "0.11.2",
|
||||
"description": "React routing component for the framework tuono. Tuono is the react/rust fullstack framework",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
|
||||
@@ -5,13 +5,20 @@ import useRoute from '../hooks/useRoute'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
|
||||
interface TuonoLinkProps {
|
||||
/**
|
||||
* If "true" the route gets loaded when the link enters the viewport. Default "true"
|
||||
*/
|
||||
preload?: boolean
|
||||
/**
|
||||
* If "false" the scroll offset will be kept across page navigation. Default "true"
|
||||
*/
|
||||
scroll?: boolean
|
||||
}
|
||||
|
||||
export default function Link(
|
||||
componentProps: AnchorHTMLAttributes<HTMLAnchorElement> & TuonoLinkProps,
|
||||
): JSX.Element {
|
||||
const { preload = true, ...props } = componentProps
|
||||
const { preload = true, scroll = true, ...props } = componentProps
|
||||
const router = useRouter()
|
||||
const route = useRoute(props.href)
|
||||
const { ref } = useInView({
|
||||
@@ -30,7 +37,7 @@ export default function Link(
|
||||
return
|
||||
}
|
||||
|
||||
router.push(props.href || '')
|
||||
router.push(props.href || '', { scroll })
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import type { ReactElement, ComponentType } from 'react'
|
||||
import type { RouteComponent } from './types'
|
||||
|
||||
type ImportFn = () => Promise<{ default: React.ComponentType<any> }>
|
||||
@@ -40,7 +41,18 @@ export const dynamic = (importFn: ImportFn): JSX.Element => {
|
||||
}
|
||||
|
||||
export const lazyLoadComponent = (factory: ImportFn): RouteComponent => {
|
||||
const Component = React.lazy(factory) as unknown as RouteComponent
|
||||
Component.preload = factory
|
||||
let LoadedComponent: ComponentType<any> | undefined
|
||||
const LazyComponent = React.lazy(factory) as unknown as RouteComponent
|
||||
|
||||
const loadComponent = (): Promise<void> =>
|
||||
factory().then((module) => {
|
||||
LoadedComponent = module.default
|
||||
})
|
||||
|
||||
const Component = (props: any): ReactElement =>
|
||||
React.createElement(LoadedComponent || LazyComponent, props)
|
||||
|
||||
Component.preload = loadComponent
|
||||
|
||||
return Component
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { useRouterStore } from './useRouterStore'
|
||||
|
||||
interface PushOptions {
|
||||
/**
|
||||
* If "false" the scroll offset will be kept across page navigation. Default "true"
|
||||
*/
|
||||
scroll?: boolean
|
||||
}
|
||||
|
||||
interface UseRouterHook {
|
||||
/**
|
||||
* Redirects to the path passed as argument updating the browser history.
|
||||
*/
|
||||
push: (path: string) => void
|
||||
push: (path: string, opt?: PushOptions) => void
|
||||
|
||||
/**
|
||||
* This object contains all the query params of the current route
|
||||
@@ -23,7 +30,8 @@ export const useRouter = (): UseRouterHook => {
|
||||
st.updateLocation,
|
||||
])
|
||||
|
||||
const push = (path: string): void => {
|
||||
const push = (path: string, opt?: PushOptions): void => {
|
||||
const { scroll = true } = opt || {}
|
||||
const url = new URL(path, window.location.origin)
|
||||
|
||||
updateLocation({
|
||||
@@ -34,6 +42,10 @@ export const useRouter = (): UseRouterHook => {
|
||||
hash: url.hash,
|
||||
})
|
||||
history.pushState(path, '', path)
|
||||
|
||||
if (scroll) {
|
||||
window.scroll(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tuono",
|
||||
"version": "0.10.4",
|
||||
"version": "0.11.2",
|
||||
"description": "The react/rust fullstack framework",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch",
|
||||
@@ -84,10 +84,10 @@
|
||||
"@babel/types": "^7.24.0",
|
||||
"@mdx-js/rollup": "^3.0.1",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/node": "^20.12.7",
|
||||
"@types/node": "^22.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"fast-text-encoding": "^1.0.6",
|
||||
"react-meta-tags": "^1.0.1",
|
||||
"react-helmet-async": "^2.0.5",
|
||||
"tuono-fs-router-vite-plugin": "workspace:*",
|
||||
"tuono-lazy-fn-vite-plugin": "workspace:*",
|
||||
"tuono-router": "workspace:*",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { hydrateRoot } from 'react-dom/client'
|
||||
import { RouterProvider, createRouter } from 'tuono-router'
|
||||
import { HelmetProvider } from 'react-helmet-async'
|
||||
|
||||
type RouteTree = any
|
||||
|
||||
@@ -14,7 +15,9 @@ export function hydrate(routeTree: RouteTree): void {
|
||||
hydrateRoot(
|
||||
rootElement,
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
<HelmetProvider>
|
||||
<RouterProvider router={router} />
|
||||
</HelmetProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Head from 'react-meta-tags'
|
||||
export { Helmet as Head } from 'react-helmet-async'
|
||||
|
||||
export {
|
||||
createRoute,
|
||||
@@ -11,6 +11,4 @@ export {
|
||||
useRouter,
|
||||
} from 'tuono-router'
|
||||
|
||||
export { Head }
|
||||
|
||||
export type { TuonoProps } from './types'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'fast-text-encoding' // Mandatory for React18
|
||||
import * as React from 'react'
|
||||
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
|
||||
import MetaTagsServer from 'react-meta-tags/server'
|
||||
import { MetaTagsContext } from 'react-meta-tags'
|
||||
import type { HelmetServerState } from 'react-helmet-async'
|
||||
import { HelmetProvider } from 'react-helmet-async'
|
||||
import { RouterProvider, createRouter } from 'tuono-router'
|
||||
|
||||
type RouteTree = any
|
||||
@@ -44,23 +44,26 @@ export function serverSideRendering(routeTree: RouteTree) {
|
||||
const cssBundles = props.cssBundles as string[]
|
||||
const router = createRouter({ routeTree }) // Render the app
|
||||
|
||||
const metaTagsInstance = MetaTagsServer()
|
||||
|
||||
const helmetContext = {}
|
||||
const app = renderToString(
|
||||
<MetaTagsContext extract={metaTagsInstance.extract}>
|
||||
<HelmetProvider context={helmetContext}>
|
||||
<RouterProvider router={router} serverProps={props} />
|
||||
</MetaTagsContext>,
|
||||
</HelmetProvider>,
|
||||
)
|
||||
|
||||
const metaTags = metaTagsInstance.renderToString()
|
||||
const { helmet } = helmetContext as { helmet: HelmetServerState }
|
||||
|
||||
return `<!doctype html>
|
||||
<html>
|
||||
<html ${helmet.htmlAttributes.toString()}>
|
||||
<head>
|
||||
${metaTags}
|
||||
${helmet.title.toString()}
|
||||
${helmet.priority.toString()}
|
||||
${helmet.meta.toString()}
|
||||
${helmet.link.toString()}
|
||||
${helmet.script.toString()}
|
||||
${generateCssLinks(cssBundles, mode)}
|
||||
</head>
|
||||
<body>
|
||||
<body ${helmet.bodyAttributes.toString()}>
|
||||
<div id="__tuono">${app}</div>
|
||||
${renderToStaticMarkup(
|
||||
<script
|
||||
|
||||
Reference in New Issue
Block a user