Compare commits

..

1 Commits

Author SHA1 Message Date
benwis
a787f21a49 Add id to ActionForm and MultiActionForm 2024-04-16 14:52:10 -07:00
338 changed files with 2408 additions and 12531 deletions

View File

@@ -48,11 +48,14 @@ jobs:
- name: Install wasm-bindgen
run: cargo binstall wasm-bindgen-cli --no-confirm
- name: Install wasm-pack
run: cargo binstall wasm-pack --no-confirm
- name: Install cargo-leptos
run: cargo binstall cargo-leptos --no-confirm
- name: Install Trunk
uses: jetli/trunk-action@v0.5.0
uses: jetli/trunk-action@v0.4.0
with:
version: "latest"

View File

@@ -1,9 +1,6 @@
[workspace]
resolver = "2"
members = [
# utilities
"oco",
# core
"leptos",
"leptos_dom",
@@ -28,24 +25,23 @@ members = [
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.6.12"
version = "0.6.11"
rust-version = "1.75"
[workspace.dependencies]
oco_ref = { path = "./oco", version = "0.1.0" }
leptos = { path = "./leptos", version = "0.6.12" }
leptos_dom = { path = "./leptos_dom", version = "0.6.12" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.12" }
leptos_macro = { path = "./leptos_macro", version = "0.6.12" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.12" }
leptos_server = { path = "./leptos_server", version = "0.6.12" }
server_fn = { path = "./server_fn", version = "0.6.12" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.12" }
leptos = { path = "./leptos", version = "0.6.11" }
leptos_dom = { path = "./leptos_dom", version = "0.6.11" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.11" }
leptos_macro = { path = "./leptos_macro", version = "0.6.11" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.11" }
leptos_server = { path = "./leptos_server", version = "0.6.11" }
server_fn = { path = "./server_fn", version = "0.6.11" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.11" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.6" }
leptos_config = { path = "./leptos_config", version = "0.6.12" }
leptos_router = { path = "./router", version = "0.6.12" }
leptos_meta = { path = "./meta", version = "0.6.12" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.12" }
leptos_config = { path = "./leptos_config", version = "0.6.11" }
leptos_router = { path = "./router", version = "0.6.11" }
leptos_meta = { path = "./meta", version = "0.6.11" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.11" }
[profile.release]
codegen-units = 1

View File

@@ -1 +0,0 @@
book

View File

@@ -1,2 +0,0 @@
Перевод в процессе, книга скоро будет доступна
> Translation underway, book will be available soon

View File

@@ -1,10 +0,0 @@
[output.html]
additional-css = ["./mdbook-admonish.css"]
[output.html.playground]
runnable = false
[preprocessor]
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "3.0.1" # не редактировать: управляется `mdbook-admonish install`

View File

@@ -1,345 +0,0 @@
@charset "UTF-8";
:root {
--md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
--md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
--md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
--md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
--md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
--md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
--md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
--md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
--md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
--md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
--md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
--md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
--md-details-icon: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
}
:is(.admonition) {
display: flow-root;
margin: 1.5625em 0;
padding: 0 1.2rem;
color: var(--fg);
page-break-inside: avoid;
background-color: var(--bg);
border: 0 solid black;
border-inline-start-width: 0.4rem;
border-radius: 0.2rem;
box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1);
}
@media print {
:is(.admonition) {
box-shadow: none;
}
}
:is(.admonition) > * {
box-sizing: border-box;
}
:is(.admonition) :is(.admonition) {
margin-top: 1em;
margin-bottom: 1em;
}
:is(.admonition) > .tabbed-set:only-child {
margin-top: 0;
}
html :is(.admonition) > :last-child {
margin-bottom: 1.2rem;
}
a.admonition-anchor-link {
display: none;
position: absolute;
left: -1.2rem;
padding-right: 1rem;
}
a.admonition-anchor-link:link, a.admonition-anchor-link:visited {
color: var(--fg);
}
a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover {
text-decoration: none;
}
a.admonition-anchor-link::before {
content: "§";
}
:is(.admonition-title, summary.admonition-title) {
position: relative;
min-height: 4rem;
margin-block: 0;
margin-inline: -1.6rem -1.2rem;
padding-block: 0.8rem;
padding-inline: 4.4rem 1.2rem;
font-weight: 700;
background-color: rgba(68, 138, 255, 0.1);
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
display: flex;
}
:is(.admonition-title, summary.admonition-title) p {
margin: 0;
}
html :is(.admonition-title, summary.admonition-title):last-child {
margin-bottom: 0;
}
:is(.admonition-title, summary.admonition-title)::before {
position: absolute;
top: 0.625em;
inset-inline-start: 1.6rem;
width: 2rem;
height: 2rem;
background-color: #448aff;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
-webkit-mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-size: contain;
content: "";
}
:is(.admonition-title, summary.admonition-title):hover a.admonition-anchor-link {
display: initial;
}
details.admonition > summary.admonition-title::after {
position: absolute;
top: 0.625em;
inset-inline-end: 1.6rem;
height: 2rem;
width: 2rem;
background-color: currentcolor;
mask-image: var(--md-details-icon);
-webkit-mask-image: var(--md-details-icon);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-size: contain;
content: "";
transform: rotate(0deg);
transition: transform 0.25s;
}
details[open].admonition > summary.admonition-title::after {
transform: rotate(90deg);
}
:is(.admonition):is(.admonish-note) {
border-color: #448aff;
}
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(68, 138, 255, 0.1);
}
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #448aff;
mask-image: var(--md-admonition-icon--admonish-note);
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
border-color: #00b0ff;
}
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 176, 255, 0.1);
}
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b0ff;
mask-image: var(--md-admonition-icon--admonish-abstract);
-webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-info, .admonish-todo) {
border-color: #00b8d4;
}
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 184, 212, 0.1);
}
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00b8d4;
mask-image: var(--md-admonition-icon--admonish-info);
-webkit-mask-image: var(--md-admonition-icon--admonish-info);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
border-color: #00bfa5;
}
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 191, 165, 0.1);
}
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00bfa5;
mask-image: var(--md-admonition-icon--admonish-tip);
-webkit-mask-image: var(--md-admonition-icon--admonish-tip);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
border-color: #00c853;
}
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(0, 200, 83, 0.1);
}
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #00c853;
mask-image: var(--md-admonition-icon--admonish-success);
-webkit-mask-image: var(--md-admonition-icon--admonish-success);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
border-color: #64dd17;
}
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(100, 221, 23, 0.1);
}
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #64dd17;
mask-image: var(--md-admonition-icon--admonish-question);
-webkit-mask-image: var(--md-admonition-icon--admonish-question);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
border-color: #ff9100;
}
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 145, 0, 0.1);
}
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff9100;
mask-image: var(--md-admonition-icon--admonish-warning);
-webkit-mask-image: var(--md-admonition-icon--admonish-warning);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
border-color: #ff5252;
}
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 82, 82, 0.1);
}
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff5252;
mask-image: var(--md-admonition-icon--admonish-failure);
-webkit-mask-image: var(--md-admonition-icon--admonish-failure);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-danger, .admonish-error) {
border-color: #ff1744;
}
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(255, 23, 68, 0.1);
}
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #ff1744;
mask-image: var(--md-admonition-icon--admonish-danger);
-webkit-mask-image: var(--md-admonition-icon--admonish-danger);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-bug) {
border-color: #f50057;
}
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(245, 0, 87, 0.1);
}
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #f50057;
mask-image: var(--md-admonition-icon--admonish-bug);
-webkit-mask-image: var(--md-admonition-icon--admonish-bug);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-example) {
border-color: #7c4dff;
}
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(124, 77, 255, 0.1);
}
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #7c4dff;
mask-image: var(--md-admonition-icon--admonish-example);
-webkit-mask-image: var(--md-admonition-icon--admonish-example);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
:is(.admonition):is(.admonish-quote, .admonish-cite) {
border-color: #9e9e9e;
}
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
background-color: rgba(158, 158, 158, 0.1);
}
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
background-color: #9e9e9e;
mask-image: var(--md-admonition-icon--admonish-quote);
-webkit-mask-image: var(--md-admonition-icon--admonish-quote);
mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat;
mask-size: contain;
-webkit-mask-repeat: no-repeat;
}
.navy :is(.admonition) {
background-color: var(--sidebar-bg);
}
.ayu :is(.admonition),
.coal :is(.admonition) {
background-color: var(--theme-hover);
}
.rust :is(.admonition) {
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
}
.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited {
color: var(--sidebar-fg);
}

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/">
<link rel="canonical" href="https://book.leptos.dev/">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/15_global_state.html">
<link rel="canonical" href="https://book.leptos.dev/15_global_state.html">

View File

@@ -1,55 +0,0 @@
# Оглавление
- [Вступление](./01_introduction.md)
- [Начало работы](./getting_started/README.md)
- [Leptos DX](./getting_started/leptos_dx.md)
- [Сообщество Leptos и leptos-* Крейты](./getting_started/community_crates.md)
- [Часть 1: Построение UI](./view/README.md)
- [Простой компонент](./view/01_basic_component.md)
- [Динамические атрибуты](./view/02_dynamic_attributes.md)
- [Компоненты и свойства](./view/03_components.md)
- [Итерирование](./view/04_iteration.md)
- [Итерирование более сложных структур через `<For>`](./view/04b_iteration.md)
- [Формы и поля ввода](./view/05_forms.md)
- [Порядок выполнения](./view/06_control_flow.md)
- [Обработка ошибок](./view/07_errors.md)
- [Общение Родитель-Ребёнок в дереве компонентов](./view/08_parent_child.md)
- [Передача Детей другим компонентам](./view/09_component_children.md)
- [Без макросов: синтаксис билдера View](./view/builder.md)
- [Реактивность](./reactivity/README.md)
- [Работа с сигналами](./reactivity/working_with_signals.md)
- [Реагирование на изменения с помощью `create_effect`](./reactivity/14_create_effect.md)
- [Примечание: Реактивность и функции](./reactivity/interlude_functions.md)
- [Тестирование](./testing.md)
- [Асинхронность](./async/README.md)
- [Подгрузка данных с помощью ресурсов (Resource)](./async/10_resources.md)
- [Ожидания (Suspense)](./async/11_suspense.md)
- [Переходы (Transition)](./async/12_transition.md)
- [Действия (Action)](./async/13_actions.md)
- [Примечание: Пробрасывание дочерних элементов](./interlude_projecting_children.md)
- [Управление глобальным состоянием](./15_global_state.md)
- [Маршрутизатор URL](./router/README.md)
- [Определение `<Routes/>`](./router/16_routes.md)
- [Вложенная маршрутизация](./router/17_nested_routing.md)
- [Параметры в пути и в строке запроса](./router/18_params_and_queries.md)
- [`<A/>`](./router/19_a.md)
- [`<Form/>`](./router/20_form.md)
- [Примечание: Стили](./interlude_styling.md)
- [Метаданные](./metadata.md)
- [Рендеринг на стороне клиента (CSR): Заключение](./csr_wrapping_up.md)
- [Часть 2: Рендеринг на стороне сервера (SSR)](./ssr/README.md)
- [`cargo-leptos`](./ssr/21_cargo_leptos.md)
- [Жизненный цикл загрузки страницы](./ssr/22_life_cycle.md)
- [Асинхронный рендеринг и режимы SSR](./ssr/23_ssr_modes.md)
- [Баги возникающие при гидратации](./ssr/24_hydration_bugs.md)
- [Работа с сервером](./server/README.md)
- [Серверные функции](./server/25_server_functions.md)
- [Экстракторы](./server/26_extractors.md)
- [Ответы и перенаправления](./server/27_response.md)
- [Постепенное улучшение и Изящная деградация](./progressive_enhancement/README.md)
- [`<ActionForm/>`](./progressive_enhancement/action_form.md)
- [Развёртывание](./deployment/README.md)
- [Оптимизация размера бинарника WASM](./deployment/binary_size.md)
- [Руководство: Острова](./islands.md)
- [Приложение: Как работает реактивная система?](./appendix_reactive_graph.md)

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/appendix_reactive_graph.html">
<link rel="canonical" href="https://book.leptos.dev/appendix_reactive_graph.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/10_resources.html">
<link rel="canonical" href="https://book.leptos.dev/async/10_resources.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/11_suspense.html">
<link rel="canonical" href="https://book.leptos.dev/async/11_suspense.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/12_transition.html">
<link rel="canonical" href="https://book.leptos.dev/async/12_transition.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/13_action.html">
<link rel="canonical" href="https://book.leptos.dev/async/13_action.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/index.html">
<link rel="canonical" href="https://book.leptos.dev/async/index.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/csr_wrapping_up.html">
<link rel="canonical" href="https://book.leptos.dev/csr_wrapping_up.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/deployment/index.html">
<link rel="canonical" href="https://book.leptos.dev/deployment/index.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/deployment/binary_size.html">
<link rel="canonical" href="https://book.leptos.dev/deployment/binary_size.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/getting_started/index.html">
<link rel="canonical" href="https://book.leptos.dev/getting_started/index.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/getting_started/community_crates.html">
<link rel="canonical" href="https://book.leptos.dev/getting_started/community_crates.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/getting_started/leptos_dx.html">
<link rel="canonical" href="https://book.leptos.dev/getting_started/leptos_dx.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/interlude_projecting_children.html">
<link rel="canonical" href="https://book.leptos.dev/interlude_projecting_children.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/interlude_styling.html">
<link rel="canonical" href="https://book.leptos.dev/interlude_styling.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/islands.html">
<link rel="canonical" href="https://book.leptos.dev/islands.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/metadata.html">
<link rel="canonical" href="https://book.leptos.dev/metadata.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/progressive_enhancement/index.html">
<link rel="canonical" href="https://book.leptos.dev/progressive_enhancement/index.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/progressive_enhancement/action_form.html">
<link rel="canonical" href="https://book.leptos.dev/progressive_enhancement/action_form.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/reactivity/14_create_effect.html">
<link rel="canonical" href="https://book.leptos.dev/reactivity/14_create_effect.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/reactivity/index.html">
<link rel="canonical" href="https://book.leptos.dev/reactivity/index.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/reactivity/interlude_functions.html">
<link rel="canonical" href="https://book.leptos.dev/reactivity/interlude_functions.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/reactivity/working_with_signals.html">
<link rel="canonical" href="https://book.leptos.dev/reactivity/working_with_signals.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/16_routes.html">
<link rel="canonical" href="https://book.leptos.dev/router/16_routes.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/17_nested_routing.html">
<link rel="canonical" href="https://book.leptos.dev/router/17_nested_routing.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/18_params_and_queries.html">
<link rel="canonical" href="https://book.leptos.dev/router/18_params_and_queries.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/19_a.html">
<link rel="canonical" href="https://book.leptos.dev/router/19_a.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/20_form.html">
<link rel="canonical" href="https://book.leptos.dev/router/20_form.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/index.html">
<link rel="canonical" href="https://book.leptos.dev/router/index.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/server/25_server_functions.html">
<link rel="canonical" href="https://book.leptos.dev/server/25_server_functions.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/server/26_extractors.html">
<link rel="canonical" href="https://book.leptos.dev/server/26_extractors.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/server/27_response.html">
<link rel="canonical" href="https://book.leptos.dev/server/27_response.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/server/index.html">
<link rel="canonical" href="https://book.leptos.dev/server/index.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/21_cargo_leptos.html">
<link rel="canonical" href="https://book.leptos.dev/ssr/21_cargo_leptos.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/22_life_cycle.html">
<link rel="canonical" href="https://book.leptos.dev/ssr/22_life_cycle.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/23_ssr_modes.html">
<link rel="canonical" href="https://book.leptos.dev/ssr/23_ssr_modes.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/24_hydration_bugs.html">
<link rel="canonical" href="https://book.leptos.dev/ssr/24_hydration_bugs.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/index.html">
<link rel="canonical" href="https://book.leptos.dev/ssr/index.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/testing.html">
<link rel="canonical" href="https://book.leptos.dev/testing.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/01_basic_component.html">
<link rel="canonical" href="https://book.leptos.dev/view/01_basic_component.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/02_dynamic_attributes.html">
<link rel="canonical" href="https://book.leptos.dev/view/02_dynamic_attributes.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/03_components.html">
<link rel="canonical" href="https://book.leptos.dev/view/03_components.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/04_iteration.html">
<link rel="canonical" href="https://book.leptos.dev/view/04_iteration.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/04b_iteration.html">
<link rel="canonical" href="https://book.leptos.dev/view/04b_iteration.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/05_forms.html">
<link rel="canonical" href="https://book.leptos.dev/view/05_forms.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/06_control_flow.html">
<link rel="canonical" href="https://book.leptos.dev/view/06_control_flow.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/07_errors.html">
<link rel="canonical" href="https://book.leptos.dev/view/07_errors.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/08_parent_child.html">
<link rel="canonical" href="https://book.leptos.dev/view/08_parent_child.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/09_component_children.html">
<link rel="canonical" href="https://book.leptos.dev/view/09_component_children.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/index.html">
<link rel="canonical" href="https://book.leptos.dev/view/index.html">

View File

@@ -1,2 +0,0 @@
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/builder.html">
<link rel="canonical" href="https://book.leptos.dev/view/builder.html">

View File

@@ -47,11 +47,11 @@ CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
workspace = false
description = "Generate the list of workspace members"
script = '''
examples=$(ls |
grep -v .md |
grep -v Makefile.toml |
grep -v cargo-make |
grep -v gtk |
examples=$(ls |
grep -v .md |
grep -v Makefile.toml |
grep -v cargo-make |
grep -v gtk |
jq -R -s -c 'split("\n")[:-1]')
echo "CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = $examples"
'''

View File

@@ -1,44 +0,0 @@
extend = [
{ path = "./lint.toml" }
]
[tasks.make-target-site-dir]
command = "mkdir"
args = ["-p", "target/site"]
[tasks.install-cargo-leptos]
install_crate = { crate_name = "cargo-leptos", binary = "cargo-leptos", test_arg = "--help" }
[tasks.cargo-leptos-e2e]
command = "cargo"
args = ["leptos", "end-to-end"]
[tasks.build]
clear = true
command = "cargo"
dependencies = ["make-target-site-dir"]
args = ["leptos", "build", "--release", "-P"]
[tasks.check]
clear = true
dependencies = ["check-debug", "check-release"]
[tasks.check-debug]
toolchain = "stable"
command = "cargo"
args = ["check-all-features"]
install_crate = "cargo-all-features"
[tasks.check-release]
toolchain = "stable"
command = "cargo"
args = ["check-all-features", "--release"]
install_crate = "cargo-all-features"
[tasks.lint]
dependencies = ["make-target-site-dir", "check-style"]
[tasks.start-client]
dependencies = ["install-cargo-leptos"]
command = "cargo"
args = ["leptos", "watch", "--release", "-P"]

View File

@@ -1,11 +1,9 @@
[tasks.build]
install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "--help" }
clear = true
command = "deno"
args = ["task", "build"]
[tasks.start-client]
install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "--help" }
command = "deno"
args = ["task", "start"]

View File

@@ -31,7 +31,6 @@ axum = { version = "0.7", optional = true, features = ["http2"] }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = [
"fs",
"compression-gzip",
"compression-br",
], optional = true }
tokio = { version = "1", features = ["full"], optional = true }
@@ -39,8 +38,6 @@ http = { version = "1.0", optional = true }
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
wasm-bindgen = "0.2"
lazy_static = "1.4.0"
rust-embed = { version = "8", features = ["axum", "mime_guess", "tokio"], optional = true }
mime_guess = { version = "2.0.4", optional = true }
[features]
default = []
@@ -52,8 +49,6 @@ ssr = [
"dep:tower-http",
"dep:tokio",
"dep:http",
"dep:rust-embed",
"dep:mime_guess",
"leptos/ssr",
"leptos_axum",
"leptos_meta/ssr",
@@ -99,12 +94,6 @@ bin-features = ["ssr"]
# Optional. Defaults to false.
bin-default-features = false
# This feature will add a hash to the filename of assets.
# This is useful here because our files are precompressed and use a `Cache-Control` policy to reduce HTTP requests
#
# Optional. Defaults to false.
hash_file = true
# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features

View File

@@ -1,6 +1,6 @@
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/cargo-leptos-compress.toml" },
{ path = "../cargo-make/cargo-leptos.toml" },
]
[env]

View File

@@ -1,11 +1,6 @@
# Leptos Hacker News Example with Axum
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to:
- Create a client-side rendered app
- Create a server side rendered app with hydration
- Precompress static assets and bundle those in with the server binary
This repo differs from the main Hacker News example by using Axum as it's server, precompressing and embedding static assets into the binary, and dynamically compressing the generated HTML.
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository. This repo differs from the main Hacker News example by using Axum as it's server.
## Getting Started
@@ -13,4 +8,4 @@ See the [Examples README](../README.md) for setup and run instructions.
## Quick Start
Run `cargo leptos watch --release -P` to run this example.
Run `cargo leptos watch` to run this example.

View File

@@ -0,0 +1,8 @@
wasm-pack build --target=web --features=hydrate --release
cd pkg
rm *.br
cp hackernews.js hackernews.unmin.js
cat hackernews.unmin.js | esbuild > hackernews.js
brotli hackernews.js
brotli hackernews_bg.wasm
brotli style.css

View File

@@ -2,34 +2,20 @@ use crate::error_template::error_template;
use axum::{
body::Body,
extract::State,
http::{header, Request, Response, StatusCode, Uri},
http::{Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
use leptos::LeptosOptions;
use std::borrow::Cow;
#[cfg(not(debug_assertions))]
const DEV_MODE: bool = false;
#[cfg(debug_assertions)]
const DEV_MODE: bool = true;
#[derive(rust_embed::RustEmbed)]
#[folder = "target/site/"]
struct Assets;
use tower::ServiceExt;
use tower_http::services::ServeDir;
pub async fn file_and_error_handler(
uri: Uri,
State(options): State<LeptosOptions>,
req: Request<Body>,
) -> AxumResponse {
let accept_encoding = req
.headers()
.get("accept-encoding")
.map(|h| h.to_str().unwrap_or("none"))
.unwrap_or("none")
.to_string();
let res = get_static_file(uri.clone(), accept_encoding).await.unwrap();
let root = options.site_root.clone();
let res = get_static_file(uri.clone(), &root).await.unwrap();
if res.status() == StatusCode::OK {
res.into_response()
@@ -44,56 +30,19 @@ pub async fn file_and_error_handler(
async fn get_static_file(
uri: Uri,
accept_encoding: String,
root: &str,
) -> Result<Response<Body>, (StatusCode, String)> {
let (_, path) = uri.path().split_at(1); // split off the first `/`
let mime = mime_guess::from_path(path);
let (path, encoding) = if DEV_MODE {
// during DEV, don't care about the precompression -> faster workflow
(Cow::from(path), "none")
} else if accept_encoding.contains("br") {
(Cow::from(format!("{}.br", path)), "br")
} else if accept_encoding.contains("gzip") {
(Cow::from(format!("{}.gz", path)), "gzip")
} else {
(Cow::from(path), "none")
};
match Assets::get(path.as_ref()) {
Some(content) => {
let body = Body::from(content.data);
let res = match DEV_MODE {
true => Response::builder()
.header(
header::CONTENT_TYPE,
mime.first_or_octet_stream().as_ref(),
)
.header(header::CONTENT_ENCODING, encoding)
.body(body)
.unwrap(),
false => Response::builder()
.header(header::CACHE_CONTROL, "max-age=86400")
.header(
header::CONTENT_TYPE,
mime.first_or_octet_stream().as_ref(),
)
.header(header::CONTENT_ENCODING, encoding)
.body(body)
.unwrap(),
};
Ok(res.into_response())
}
None => {
eprintln!(">> Asset {} not found", path);
for a in Assets::iter() {
eprintln!("Available asset: {}", a);
}
Err((StatusCode::NOT_FOUND, "Not found".to_string()))
}
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
.unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// This path is relative to the cargo root
match ServeDir::new(root).oneshot(req).await {
Ok(res) => Ok(res.into_response()),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
)),
}
}

View File

@@ -6,33 +6,16 @@ async fn main() {
use hackernews_islands::*;
pub use leptos::get_configuration;
pub use leptos_axum::{generate_route_list, LeptosRoutes};
use tower_http::compression::{
predicate::{NotForContentType, SizeAbove},
CompressionLayer, CompressionLevel, Predicate,
};
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
let leptos_options = conf.leptos_options;
let addr = leptos_options.site_addr;
let routes = generate_route_list(App);
let predicate = SizeAbove::new(1500) // files smaller than 1501 bytes are not compressed, since the MTU (Maximum Transmission Unit) of a TCP packet is 1500 bytes
.and(NotForContentType::GRPC)
.and(NotForContentType::IMAGES)
// prevent compressing assets that are already statically compressed
.and(NotForContentType::const_new("application/javascript"))
.and(NotForContentType::const_new("application/wasm"))
.and(NotForContentType::const_new("text/css"));
// build our application with a route
let app = Router::new()
.route("/favicon.ico", get(file_and_error_handler))
.leptos_routes(&leptos_options, routes, App)
.layer(
CompressionLayer::new()
.quality(CompressionLevel::Fastest)
.compress_when(predicate),
)
.fallback(file_and_error_handler)
.with_state(leptos_options);

View File

@@ -14,17 +14,17 @@ lto = true
console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
cfg-if = "1.0.0"
leptos = { path = "../../leptos" }
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
leptos = { version = "0.5" }
leptos_axum = { version = "0.5", default-features = false, optional = true }
leptos_meta = { version = "0.5" }
leptos_router = { version = "0.5" }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }
tracing = "0.1"
gloo-net = { version = "0.4.0", features = ["http"] }
reqwest = { version = "0.11.13", features = ["json"] }
axum = { version = "0.7", default-features = false, optional = true }
axum = { version = "0.6", default-features = false, optional = true }
tower = { version = "0.4.13", optional = true }
http = { version = "0.2.11", optional = true }
web-sys = { version = "0.3", features = [
@@ -37,7 +37,7 @@ wasm-bindgen = "0.2"
wasm-bindgen-futures = { version = "0.4.37", features = [
"futures-core-03-stream",
], optional = true }
axum-js-fetch = { git = "https://github.com/seanaye/axum-js-fetch", optional = true }
axum-js-fetch = { version = "0.2.1", optional = true }
lazy_static = "1.4.0"
[features]

View File

@@ -1,2 +0,0 @@
[toolchain]
channel = "stable" # test change

View File

@@ -62,8 +62,8 @@ mod ssr_imports {
let routes = generate_route_list(App);
// build our application with a route
let app: axum::Router<()> = Router::new()
.leptos_routes(&leptos_options, routes, App)
let app: axum::Router<(), axum::body::Body> = Router::new()
.leptos_routes(&leptos_options, routes, || view! { <App/> })
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
.with_state(leptos_options);
@@ -73,7 +73,7 @@ mod ssr_imports {
}
pub async fn serve(&self, req: web_sys::Request) -> web_sys::Response {
self.0.oneshot(req).await
self.0.serve(req).await
}
}
}

View File

@@ -8,7 +8,7 @@ leptos = { path = "../../leptos", features = ["csr"] }
leptos_meta = { path = "../../meta", features = ["csr"] }
leptos_router = { path = "../../router", features = ["csr"] }
log = "0.4"
gloo-net = { version = "0.5", features = ["http"] }
gloo-net = { version = "0.2", features = ["http"] }
# dependencies for client (enable when csr or hydrate set)
wasm-bindgen = { version = "0.2" }

View File

@@ -1,4 +1,4 @@
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/trunk_server.toml" },
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/trunk_server.toml" },
]

View File

@@ -0,0 +1,4 @@
[[hooks]]
stage = "pre_build"
command = "sh"
command_arguments = ["-c", "npx tailwindcss -i input.css -o style/output.css"]

View File

@@ -1,12 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<head>
<meta charset="utf-8">
<link data-trunk rel="rust" data-wasm-opt="z" />
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico" />
<link data-trunk rel="tailwind-css" href="/style/tailwind.css" />
<link data-trunk rel="css" href="/style/output.css" />
<title>Leptos • Counter with Tailwind</title>
</head>
</head>
<body></body>
<body></body>
</html>

View File

@@ -0,0 +1,604 @@
/*
! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
*/
html {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
display: none;
}
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.my-0 {
margin-top: 0px;
margin-bottom: 0px;
}
.max-w-3xl {
max-width: 48rem;
}
.rounded-lg {
border-radius: 0.5rem;
}
.bg-amber-600 {
--tw-bg-opacity: 1;
background-color: rgb(217 119 6 / var(--tw-bg-opacity));
}
.p-6 {
padding: 1.5rem;
}
.px-10 {
padding-left: 2.5rem;
padding-right: 2.5rem;
}
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.py-3 {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.pb-10 {
padding-bottom: 2.5rem;
}
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.hover\:bg-sky-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(3 105 161 / var(--tw-bg-opacity));
}

View File

@@ -27,6 +27,3 @@ tokio = { version = "1", features = ["rt", "fs"] }
[features]
nonce = ["leptos/nonce"]
experimental-islands = ["leptos_integration_utils/experimental-islands"]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -729,7 +729,10 @@ async fn stream_app(
build_stream_response(options, res_options, stream, runtime).await
}
#[cfg_attr(any(debug_assertions), instrument(level = "trace", skip_all,))]
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
async fn stream_app_in_order(
options: &LeptosOptions,
app: impl FnOnce() -> View + 'static,

View File

@@ -37,6 +37,3 @@ nonce = ["leptos/nonce"]
wasm = []
default = ["tokio/fs", "tokio/sync"]
experimental-islands = ["leptos_integration_utils/experimental-islands"]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -54,10 +54,7 @@ use leptos_meta::{generate_head_metadata_separated, MetaContext};
use leptos_router::*;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use server_fn::{
error::{NoCustomError, ServerFnErrorSerde},
redirect::REDIRECT_HEADER,
};
use server_fn::{error::NoCustomError, redirect::REDIRECT_HEADER};
use std::{fmt::Debug, io, pin::Pin, sync::Arc, thread::available_parallelism};
use tokio_util::task::LocalPoolHandle;
use tracing::Instrument;
@@ -360,10 +357,9 @@ async fn handle_server_fns_inner(
rx.await.unwrap_or_else(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ServerFnErrorSerde::ser(
&ServerFnError::<NoCustomError>::ServerError(e.to_string()),
)
.unwrap_or_default(),
ServerFnError::<NoCustomError>::ServerError(e.to_string())
.ser()
.unwrap_or_default(),
)
.into_response()
})

View File

@@ -18,6 +18,3 @@ tracing = "0.1.37"
[features]
experimental-islands = []
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -16,7 +16,7 @@ leptos_macro = { workspace = true }
leptos_reactive = { workspace = true }
leptos_server = { workspace = true }
leptos_config = { workspace = true }
leptos-spin-macro = { version = "0.2", optional = true }
leptos-spin-macro = { version = "0.1", optional = true }
tracing = "0.1"
typed-builder = "0.18"
typed-builder-macro = "0.18"
@@ -70,7 +70,7 @@ nightly = [
serde = ["leptos_reactive/serde"]
serde-lite = ["leptos_reactive/serde-lite"]
miniserde = ["leptos_reactive/miniserde"]
rkyv = ["leptos_reactive/rkyv", "server_fn/rkyv"]
rkyv = ["leptos_reactive/rkyv"]
tracing = ["leptos_macro/tracing"]
nonce = ["leptos_dom/nonce"]
spin = ["leptos_reactive/spin", "leptos-spin-macro"]
@@ -141,6 +141,3 @@ skip_feature_sets = [
"rustls",
],
]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -16,80 +16,8 @@ pub type ChildrenFnMut = Box<dyn FnMut() -> Fragment>;
// This is to still support components that accept `Box<dyn Fn() -> Fragment>` as a children.
type BoxedChildrenFn = Box<dyn Fn() -> Fragment>;
/// This trait can be used when constructing a component that takes children without needing
/// to know exactly what children type the component expects. This is used internally by the
/// `view!` macro implementation, and can also be used explicitly when using the builder syntax.
///
/// # Examples
///
/// ## Without ToChildren
///
/// Without [ToChildren], consumers need to explicitly provide children using the type expected
/// by the component. For example, [Provider][crate::Provider]'s children need to wrapped in
/// a [Box], while [Show][crate::Show]'s children need to be wrapped in an [Rc].
///
/// ```
/// # use leptos::{ProviderProps, ShowProps};
/// # use leptos_dom::html::p;
/// # use leptos_dom::IntoView;
/// # use leptos_macro::component;
/// # use std::rc::Rc;
/// #
/// #[component]
/// fn App() -> impl IntoView {
/// (
/// ProviderProps::builder()
/// .children(Box::new(|| p().child("Foo").into_view().into()))
/// // ...
/// # .value("Foo")
/// # .build(),
/// ShowProps::builder()
/// .children(Rc::new(|| p().child("Foo").into_view().into()))
/// // ...
/// # .when(|| true)
/// # .fallback(|| p().child("foo"))
/// # .build(),
/// )
/// }
/// ```
///
/// ## With ToChildren
///
/// With [ToChildren], consumers don't need to know exactly which type a component uses for
/// its children.
///
/// ```
/// # use leptos::{ProviderProps, ShowProps};
/// # use leptos_dom::html::p;
/// # use leptos_dom::IntoView;
/// # use leptos_macro::component;
/// # use std::rc::Rc;
/// # use leptos::ToChildren;
/// #
/// #[component]
/// fn App() -> impl IntoView {
/// (
/// ProviderProps::builder()
/// .children(ToChildren::to_children(|| {
/// p().child("Foo").into_view().into()
/// }))
/// // ...
/// # .value("Foo")
/// # .build(),
/// ShowProps::builder()
/// .children(ToChildren::to_children(|| {
/// p().child("Foo").into_view().into()
/// }))
/// // ...
/// # .when(|| true)
/// # .fallback(|| p().child("foo"))
/// # .build(),
/// )
/// }
#[doc(hidden)]
pub trait ToChildren<F> {
/// Convert the provided type to (generally a closure) to Self (generally a "children" type,
/// e.g., [Children]). See the implementations to see exactly which input types are supported
/// and which "children" type they are converted to.
fn to_children(f: F) -> Self;
}

View File

@@ -34,6 +34,18 @@ use std::hash::Hash;
/// }
/// }
/// />
/// <For
/// // a function that returns the items we're iterating over; a signal is fine
/// each=move || counters.get()
/// // a unique key for each item
/// key=|counter| counter.id
/// // renders each item to a view
/// children=move |counter: Counter| {
/// view! {
/// <button>"Value: " {move || counter.count.get()}</button>
/// }
/// }
/// />
/// </div>
/// }
/// }

View File

@@ -285,21 +285,6 @@ pub trait DynAttrs {
impl DynAttrs for () {}
#[doc(hidden)]
pub trait DynBindings {
fn dyn_bindings<B: Into<Binding>>(
self,
_args: impl IntoIterator<Item = B>,
) -> Self
where
Self: Sized,
{
self
}
}
impl DynBindings for () {}
#[doc(hidden)]
pub trait PropsOrNoPropsBuilder {
type Builder;

View File

@@ -20,6 +20,3 @@ typed-builder = "0.18"
tokio = { version = "1", features = ["rt", "macros"] }
tempfile = "3"
temp-env = { version = "0.3.6", features = ["async_closure"] }
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -178,6 +178,3 @@ trace-component-props = []
[package.metadata.cargo-all-features]
denylist = ["nightly", "trace-component-props"]
skip_feature_sets = [["web", "ssr"]]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -251,7 +251,7 @@ where
impl IntoView for TextProp {
fn into_view(self) -> View {
(move || self.get()).into_view()
self.get().into_view()
}
}

View File

@@ -227,7 +227,7 @@ impl IntoAttribute for Option<Box<dyn IntoAttribute>> {
impl IntoAttribute for TextProp {
fn into_attribute(self) -> Attribute {
(move || self.get()).into_attribute()
self.get().into_attribute()
}
impl_into_attr_boxed! {}

View File

@@ -46,16 +46,6 @@ pub trait IntoStyle {
fn into_style_boxed(self: Box<Self>) -> Style;
}
impl IntoStyle for Style {
fn into_style(self) -> Style {
self
}
fn into_style_boxed(self: Box<Self>) -> Style {
*self
}
}
impl IntoStyle for &'static str {
#[inline(always)]
fn into_style(self) -> Style {
@@ -186,7 +176,7 @@ impl Style {
/// Converts the style to its HTML value at that moment so it can be rendered on the server.
pub fn as_value_string(
&self,
style_name: &str,
style_name: &'static str,
) -> Option<Oco<'static, str>> {
match self {
Style::Value(value) => {

View File

@@ -31,12 +31,10 @@ use std::cell::Cell;
/// }
/// }
/// ```
#[cfg_attr(not(debug_assertions), repr(transparent))]
pub struct NodeRef<T: ElementDescriptor + 'static> {
element: RwSignal<Option<HtmlElement<T>>>,
#[cfg(debug_assertions)]
defined_at: &'static std::panic::Location<'static>,
}
#[repr(transparent)]
pub struct NodeRef<T: ElementDescriptor + 'static>(
RwSignal<Option<HtmlElement<T>>>,
);
/// Creates a shared reference to a DOM node created while using the `view`
/// macro to create your UI.
@@ -67,14 +65,9 @@ pub struct NodeRef<T: ElementDescriptor + 'static> {
/// }
/// }
/// ```
#[track_caller]
#[inline(always)]
pub fn create_node_ref<T: ElementDescriptor + 'static>() -> NodeRef<T> {
NodeRef {
#[cfg(debug_assertions)]
defined_at: std::panic::Location::caller(),
element: create_rw_signal(None),
}
NodeRef(create_rw_signal(None))
}
impl<T: ElementDescriptor + 'static> NodeRef<T> {
@@ -127,7 +120,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
where
T: Clone,
{
self.element.get()
self.0.get()
}
/// Gets the element that is currently stored in the reference.
@@ -139,7 +132,7 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
where
T: Clone,
{
self.element.get_untracked()
self.0.get_untracked()
}
#[doc(hidden)]
@@ -151,15 +144,13 @@ impl<T: ElementDescriptor + 'static> NodeRef<T> {
where
T: Clone,
{
self.element.update(|current| {
self.0.update(|current| {
if current.is_some() {
#[cfg(debug_assertions)]
crate::debug_warn!(
"You are setting the NodeRef defined at {}, which has \
already been filled Its possible this is intentional, \
but its also possible that youre accidentally using \
the same NodeRef for multiple _ref attributes.",
self.defined_at
"You are setting a NodeRef that has already been filled. \
Its possible this is intentional, but its also \
possible that youre accidentally using the same NodeRef \
for multiple _ref attributes."
);
}
*current = Some(node.clone());

View File

@@ -51,6 +51,3 @@ axum = ["server_fn_macro/axum"]
[package.metadata.cargo-all-features]
denylist = ["nightly", "tracing", "trace-component-props"]
skip_feature_sets = [["csr", "hydrate"], ["hydrate", "csr"], ["hydrate", "ssr"]]
[package.metadata.docs.rs]
rustdoc-args = ["--generate-link-to-definition"]

View File

@@ -8,11 +8,10 @@ use leptos_hot_reload::parsing::value_to_string;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
use syn::{
parse::Parse, parse_quote, spanned::Spanned, token::Colon,
visit_mut::VisitMut, AngleBracketedGenericArguments, Attribute, FnArg,
GenericArgument, GenericParam, Item, ItemFn, LitStr, Meta, Pat, PatIdent,
Path, PathArguments, ReturnType, Signature, Stmt, Type, TypeImplTrait,
TypeParam, TypePath, Visibility,
parse::Parse, parse_quote, spanned::Spanned,
AngleBracketedGenericArguments, Attribute, FnArg, GenericArgument, Item,
ItemFn, LitStr, Meta, Pat, PatIdent, Path, PathArguments, ReturnType,
Signature, Stmt, Type, TypePath, Visibility,
};
pub struct Model {
@@ -29,7 +28,6 @@ pub struct Model {
impl Parse for Model {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut item = ItemFn::parse(input)?;
convert_impl_trait_to_generic(&mut item.sig);
let docs = Docs::new(&item.attrs);
@@ -180,18 +178,6 @@ impl ToTokens for Model {
);
let component_fn_prop_docs = generate_component_fn_prop_docs(props);
let docs_and_prop_docs = if component_fn_prop_docs.is_empty() {
// Avoid generating an empty doc line in case the component has no doc and no props.
quote! {
#docs
}
} else {
quote! {
#docs
#[doc = ""]
#component_fn_prop_docs
}
};
let (
tracing_instrument_attr,
@@ -228,7 +214,7 @@ impl ToTokens for Model {
let component_id = name.to_string();
let hydrate_fn_name =
Ident::new(&format!("_island_{component_id}"), name.span());
Ident::new(&format!("_island_{}", component_id), name.span());
let island_serialize_props = if is_island_with_other_props {
quote! {
@@ -379,33 +365,6 @@ impl ToTokens for Model {
})
.collect::<TokenStream>();
let dyn_binding_props = props
.iter()
.filter(
|Prop {
prop_opts: PropOpt { attrs, .. },
..
}| *attrs,
)
.filter_map(
|Prop {
name,
prop_opts: PropOpt { attrs, .. },
..
}| {
let ident = &name.ident;
if *attrs {
Some(quote! {
::leptos::leptos_dom::html::Binding::Attribute { name, value } => self.#ident.push((name, value)),
})
} else {
None
}
},
)
.collect::<TokenStream>();
let body = quote! {
#destructure_props
#tracing_span_expr
@@ -516,7 +475,9 @@ impl ToTokens for Model {
let output = quote! {
#[doc = #builder_name_doc]
#[doc = ""]
#docs_and_prop_docs
#docs
#[doc = ""]
#component_fn_prop_docs
#[derive(::leptos::typed_builder_macro::TypedBuilder #props_derive_serialize)]
//#[builder(doc)]
#[builder(crate_module_path=::leptos::typed_builder)]
@@ -543,24 +504,11 @@ impl ToTokens for Model {
}
}
impl #impl_generics ::leptos::DynBindings for #props_name #generics #where_clause {
fn dyn_bindings<B: Into<::leptos::leptos_dom::html::Binding>>(mut self, bindings: impl std::iter::IntoIterator<Item = B>) -> Self {
for binding in bindings.into_iter() {
let binding: ::leptos::leptos_dom::html::Binding = binding.into();
match binding {
#dyn_binding_props
_ => {}
}
}
self
}
}
#into_view
#docs_and_prop_docs
#docs
#[doc = ""]
#component_fn_prop_docs
#[allow(non_snake_case, clippy::too_many_arguments)]
#[allow(clippy::needless_lifetimes)]
#tracing_instrument_attr
@@ -1231,57 +1179,3 @@ fn is_valid_into_view_return_type(ty: &ReturnType) -> bool {
pub fn unmodified_fn_name_from_fn_name(ident: &Ident) -> Ident {
Ident::new(&format!("__{ident}"), ident.span())
}
/// Converts all `impl Trait`s in a function signature to use generic params instead.
fn convert_impl_trait_to_generic(sig: &mut Signature) {
fn new_generic_ident(i: usize, span: Span) -> Ident {
Ident::new(&format!("__ImplTrait{i}"), span)
}
// First: visit all `impl Trait`s and replace them with new generic params.
#[derive(Default)]
struct RemoveImplTrait(Vec<TypeImplTrait>);
impl VisitMut for RemoveImplTrait {
fn visit_type_mut(&mut self, ty: &mut Type) {
syn::visit_mut::visit_type_mut(self, ty);
if matches!(ty, Type::ImplTrait(_)) {
let ident = new_generic_ident(self.0.len(), ty.span());
let generic_type = Type::Path(TypePath {
qself: None,
path: Path::from(ident),
});
let Type::ImplTrait(impl_trait) =
std::mem::replace(ty, generic_type)
else {
unreachable!();
};
self.0.push(impl_trait);
}
}
// Early exits.
fn visit_attribute_mut(&mut self, _: &mut Attribute) {}
fn visit_pat_mut(&mut self, _: &mut Pat) {}
}
let mut visitor = RemoveImplTrait::default();
for fn_arg in sig.inputs.iter_mut() {
visitor.visit_fn_arg_mut(fn_arg);
}
let RemoveImplTrait(impl_traits) = visitor;
// Second: Add the new generic params into the signature.
for (i, impl_trait) in impl_traits.into_iter().enumerate() {
let span = impl_trait.span();
let ident = new_generic_ident(i, span);
// We can simply append to the end (only lifetime params must be first).
// Note currently default generics are not allowed in `fn`, so not a concern.
sig.generics.params.push(GenericParam::Type(TypeParam {
attrs: vec![],
ident,
colon_token: Some(Colon { spans: [span] }),
bounds: impl_trait.bounds,
eq_token: None,
default: None,
}));
}
}

View File

@@ -24,7 +24,10 @@ pub(crate) enum Mode {
impl Default for Mode {
fn default() -> Self {
if cfg!(feature = "hydrate") || cfg!(feature = "csr") {
if cfg!(feature = "hydrate")
|| cfg!(feature = "csr")
|| cfg!(feature = "web")
{
Mode::Client
} else {
Mode::Ssr
@@ -486,7 +489,7 @@ pub fn template(tokens: TokenStream) -> TokenStream {
/// use leptos::html::Div;
///
/// #[component]
/// fn MyComponent(render_prop: impl Fn() -> HtmlElement<Div>) -> impl IntoView {
/// fn MyComponent<T: Fn() -> HtmlElement<Div>>(render_prop: impl Fn() -> HtmlElement<Div>) -> impl IntoView {
/// }
/// ```
///

View File

@@ -11,7 +11,7 @@ use syn::{
struct SliceMacroInput {
root: syn::Ident,
path: Punctuated<syn::Member, Token![.]>,
path: Punctuated<syn::Ident, Token![.]>,
}
impl Parse for SliceMacroInput {
@@ -19,7 +19,7 @@ impl Parse for SliceMacroInput {
let root: syn::Ident = input.parse()?;
input.parse::<Token![.]>()?;
// do not accept trailing punctuation
let path: Punctuated<syn::Member, Token![.]> =
let path: Punctuated<syn::Ident, Token![.]> =
Punctuated::parse_separated_nonempty(input)?;
if path.is_empty() {

View File

@@ -52,10 +52,12 @@ pub(crate) fn fragment_to_tokens(
None,
)?;
let node = quote_spanned!(span => { #node });
let node = quote_spanned! {span=>
#[allow(unused_braces)] {#node}
};
Some(quote! {
::leptos::IntoView::into_view(#[allow(unused_braces)] #node)
::leptos::IntoView::into_view(#node)
})
})
.peekable();
@@ -296,38 +298,34 @@ pub(crate) fn element_to_tokens(
let children = node
.children
.iter()
.filter_map(|node| match node {
Node::Fragment(fragment) => Some(
fragment_to_tokens(
&fragment.children,
true,
parent_type,
None,
global_class,
None,
)
.unwrap_or(quote_spanned! {
Span::call_site()=> ::leptos::leptos_dom::Unit
}),
),
Node::Text(node) => Some(quote! { #node }),
.map(|node| match node {
Node::Fragment(fragment) => fragment_to_tokens(
&fragment.children,
true,
parent_type,
None,
global_class,
None,
)
.unwrap_or(quote_spanned! {
Span::call_site()=> ::leptos::leptos_dom::Unit
}),
Node::Text(node) => quote! { #node },
Node::RawText(node) => {
let text = node.to_string_best();
let text = syn::LitStr::new(&text, node.span());
Some(quote! { #text })
quote! { #text }
}
Node::Block(node) => Some(quote! { #node }),
Node::Element(node) => Some(
element_to_tokens(
node,
parent_type,
None,
global_class,
None,
)
.unwrap_or_default(),
),
Node::Comment(_) | Node::Doctype(_) => None,
Node::Block(node) => quote! { #node },
Node::Element(node) => element_to_tokens(
node,
parent_type,
None,
global_class,
None,
)
.unwrap_or_default(),
Node::Comment(_) | Node::Doctype(_) => quote! {},
})
.map(|node| quote!(.child(#node)));
@@ -337,7 +335,9 @@ pub(crate) fn element_to_tokens(
quote! {}
};
let ide_helper_close_tag = ide_helper_close_tag.into_iter();
let result = quote_spanned! {node.span()=> {
Some(quote_spanned! {node.span()=>
#[allow(unused_braces)]
{
#(#ide_helper_close_tag)*
#name
#(#attrs)*
@@ -348,10 +348,7 @@ pub(crate) fn element_to_tokens(
#(#children)*
#view_marker
}
};
// We need to move "allow" out of "quote_spanned" because it breaks hovering in rust-analyzer
Some(quote!(#[allow(unused_braces)] #result))
})
}
}

View File

@@ -27,30 +27,6 @@ pub(crate) fn component_to_tokens(
}
});
let spread_bindings = node.attributes().iter().filter_map(|node| {
use rstml::node::NodeBlock;
use syn::{Expr, ExprRange, RangeLimits, Stmt};
if let NodeAttribute::Block(NodeBlock::ValidBlock(block)) = node {
match block.stmts.first()? {
Stmt::Expr(
Expr::Range(ExprRange {
start: None,
limits: RangeLimits::HalfOpen(_),
end: Some(end),
..
}),
_,
) => Some(
quote! { .dyn_bindings(#[allow(unused_brace)] {#end}) },
),
_ => None,
}
} else {
None
}
});
let props = attrs
.clone()
.filter(|attr| {
@@ -70,10 +46,12 @@ pub(crate) fn component_to_tokens(
})
.unwrap_or_else(|| quote! { #name });
let value = quote_spanned!(value.span()=> { #value });
let value = quote_spanned! {value.span()=>
#[allow(unused_braces)] {#value}
};
quote_spanned! {attr.span()=>
.#name(#[allow(unused_braces)] #value)
.#name(#value)
}
});
@@ -235,17 +213,24 @@ pub(crate) fn component_to_tokens(
#[allow(unused_mut)] // used in debug
let mut component = quote_spanned! {node.span()=>
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
#name_ref,
#component_props_builder
{
let props = #component_props_builder
#(#props)*
#(#slots)*
#children
#children;
#[allow(clippy::let_unit_value, clippy::unit_arg)]
let props = props
#build
#dyn_attrs
#(#spread_bindings)*
)
#dyn_attrs;
#[allow(unreachable_code)]
::leptos::component_view(
#[allow(clippy::needless_borrows_for_generic_args)]
#name_ref,
props
)
}
};
// (Temporarily?) removed

View File

@@ -3,11 +3,7 @@ use convert_case::{Case::Snake, Casing};
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use quote::{quote, quote_spanned};
use rstml::node::{KeyedAttribute, Node, NodeElement, NodeName};
use syn::{
spanned::Spanned,
Expr::{self, Tuple},
ExprArray, ExprLit, ExprPath, Lit,
};
use syn::{spanned::Spanned, Expr, Expr::Tuple, ExprLit, ExprPath, Lit};
pub mod client_builder;
pub mod client_template;
@@ -408,72 +404,27 @@ fn fancy_class_name<'a>(
node.key.span()=> .class
};
let class_name = &tuple.elems[0];
let class_name = if let Expr::Lit(ExprLit {
lit: Lit::Str(s),
..
}) = class_name
{
s.value()
} else {
proc_macro_error::emit_error!(
class_name.span(),
"class name must be a string literal"
);
Default::default()
};
let value = &tuple.elems[1];
match class_name {
Expr::Lit(ExprLit {
lit: Lit::Str(s), ..
}) => {
let class_name = s.value();
return Some((
quote! {
#class(#class_name, #value)
},
class_name,
value,
));
}
Expr::Array(ExprArray { elems, .. }) => {
let (tokens, class_name): (Vec<_>, Vec<_>) = elems
.iter()
.map(|elem| match elem {
Expr::Lit(ExprLit {
lit: Lit::Str(s), ..
}) => {
let class_name = s.value();
let tokens = quote! {
#class(#class_name, #value)
};
(tokens, class_name)
}
_ => {
proc_macro_error::emit_error!(
elem.span(),
"class name elements must be string \
literals"
);
(TokenStream::new(), Default::default())
}
})
.unzip();
let class_name = class_name.join(" ");
return Some((
quote! { #(#tokens)*},
class_name,
value,
));
}
_ => {
proc_macro_error::emit_error!(
class_name.span(),
"class name must be a string literal or array of \
string literals"
);
let class_name = Default::default();
return Some((
quote! {
#class(#class_name, #value)
},
class_name,
value,
));
}
}
return Some((
quote! {
#class(#class_name, #value)
},
class_name,
value,
));
} else {
proc_macro_error::emit_error!(
tuple.span(),

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