Compare commits
293 Commits
fix-tailwi
...
debug-meta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cadb04b076 | ||
|
|
490b7a1596 | ||
|
|
f4d781e739 | ||
|
|
ebe5bf4600 | ||
|
|
d62046dc6f | ||
|
|
c7abb57168 | ||
|
|
7df67444f9 | ||
|
|
40155e91ea | ||
|
|
5c062fa6f1 | ||
|
|
3517820afd | ||
|
|
300cc4f54c | ||
|
|
586e9be99a | ||
|
|
6ed86d0ee9 | ||
|
|
1fe93fd588 | ||
|
|
2723871a80 | ||
|
|
70d92c7f42 | ||
|
|
e96d4b0687 | ||
|
|
ce0910caca | ||
|
|
81a937277d | ||
|
|
355e711964 | ||
|
|
27ec506fd5 | ||
|
|
79c76ae4cb | ||
|
|
e416815591 | ||
|
|
81bdd6788f | ||
|
|
f7d5567a35 | ||
|
|
ae0a243cc0 | ||
|
|
7893ff8b55 | ||
|
|
6130e708ce | ||
|
|
d049d2f36b | ||
|
|
61ca6465df | ||
|
|
10a833d763 | ||
|
|
17982e8ac5 | ||
|
|
6cfb6227f5 | ||
|
|
159852b8d7 | ||
|
|
6f95713b59 | ||
|
|
e17afd4559 | ||
|
|
e0aa1e245b | ||
|
|
7951a6e9cf | ||
|
|
1f39299303 | ||
|
|
2be4610233 | ||
|
|
af254e9b61 | ||
|
|
4aae8a5088 | ||
|
|
7ff044cef6 | ||
|
|
11122b575e | ||
|
|
a62ee4031b | ||
|
|
19b43607a1 | ||
|
|
884297706a | ||
|
|
fb4c208609 | ||
|
|
5701e74efb | ||
|
|
2afe8e202a | ||
|
|
08ec473304 | ||
|
|
3ae0880db4 | ||
|
|
9180aaad7e | ||
|
|
89be6bc68e | ||
|
|
56426170b0 | ||
|
|
0571ea4103 | ||
|
|
cdf709fb09 | ||
|
|
7eaa36812d | ||
|
|
2ef36c65fd | ||
|
|
a16540ccc5 | ||
|
|
613c7b32a1 | ||
|
|
8f2a731c9f | ||
|
|
1621b86d8f | ||
|
|
b1ac17995d | ||
|
|
6471af8b89 | ||
|
|
abf54b832e | ||
|
|
91e839c71a | ||
|
|
b944b17e6d | ||
|
|
54c1abb4b7 | ||
|
|
c24f33aeb2 | ||
|
|
9e83acfe63 | ||
|
|
46254a18f3 | ||
|
|
7d7a96d9bc | ||
|
|
339c920b19 | ||
|
|
ecc24fa65d | ||
|
|
3f036ee321 | ||
|
|
4e00ec2348 | ||
|
|
9c59b720b7 | ||
|
|
da4340894f | ||
|
|
02f5c3891c | ||
|
|
087e67466f | ||
|
|
1925c5bbe5 | ||
|
|
1613616008 | ||
|
|
8a01880ade | ||
|
|
180ab87ff9 | ||
|
|
e324fb6e76 | ||
|
|
0547b4f846 | ||
|
|
75659ce674 | ||
|
|
190cb162ad | ||
|
|
1f556cefb0 | ||
|
|
6a68ef67f3 | ||
|
|
23bbd90c81 | ||
|
|
27b8553076 | ||
|
|
5bfeb93e3d | ||
|
|
dd9ae1b7b1 | ||
|
|
ad34a5d9c6 | ||
|
|
ace5e7cbba | ||
|
|
c8f0988e53 | ||
|
|
64f0f8879b | ||
|
|
b8cafeb650 | ||
|
|
992b218ffe | ||
|
|
5df89b0d25 | ||
|
|
c050456a47 | ||
|
|
8a8d7cbe1b | ||
|
|
f5f345e623 | ||
|
|
4df3687463 | ||
|
|
f6622448e9 | ||
|
|
78825401c5 | ||
|
|
f2b7ad6244 | ||
|
|
43f107d9bd | ||
|
|
a2612ca1fc | ||
|
|
a000c84e1a | ||
|
|
ee647cba1c | ||
|
|
1377b823e2 | ||
|
|
4d21f5ac63 | ||
|
|
1ec603ee58 | ||
|
|
c56806713e | ||
|
|
2f6aa6753d | ||
|
|
2544687acd | ||
|
|
3d25e86c23 | ||
|
|
28ec3a6cda | ||
|
|
8b92a561a3 | ||
|
|
e490c0423f | ||
|
|
b6579a040a | ||
|
|
01e024b726 | ||
|
|
6603c44ce2 | ||
|
|
977f11b180 | ||
|
|
fb34b29ccf | ||
|
|
6576d8eda1 | ||
|
|
6b729f9131 | ||
|
|
dc60c35b58 | ||
|
|
32ec9cc57e | ||
|
|
35601d8284 | ||
|
|
49bc7d2a27 | ||
|
|
aa7c7367dc | ||
|
|
70808c5262 | ||
|
|
67503a108d | ||
|
|
ef52a01838 | ||
|
|
4cacfe98d8 | ||
|
|
52e653316e | ||
|
|
bf5b6ca9c2 | ||
|
|
8875939a27 | ||
|
|
b9a83277d9 | ||
|
|
dad84b5867 | ||
|
|
1f29d29947 | ||
|
|
1b8175e2fa | ||
|
|
dbe3ec015c | ||
|
|
492fa6c6d3 | ||
|
|
343e8c8abe | ||
|
|
656d20cb65 | ||
|
|
a0a66b75dd | ||
|
|
085ba3506c | ||
|
|
63f3780eda | ||
|
|
c41cf879d1 | ||
|
|
b34f2070d3 | ||
|
|
7fa21defa6 | ||
|
|
bdd9abc04d | ||
|
|
1d25134213 | ||
|
|
de73622949 | ||
|
|
5d3cfc6483 | ||
|
|
6fbbd09000 | ||
|
|
f2842cf14e | ||
|
|
a6b6864bc5 | ||
|
|
063b946cd4 | ||
|
|
5a2c9ea345 | ||
|
|
808d87598b | ||
|
|
0956c48b1e | ||
|
|
8915e2615b | ||
|
|
7f47134058 | ||
|
|
af7b93fa1e | ||
|
|
ed940f577a | ||
|
|
916f30a07b | ||
|
|
e01c565de1 | ||
|
|
dffe195cdc | ||
|
|
a5e2587555 | ||
|
|
af8889fab2 | ||
|
|
267c1cfc34 | ||
|
|
3498378e60 | ||
|
|
f8c680d14d | ||
|
|
b852e459a9 | ||
|
|
681f10ec8d | ||
|
|
1d480791a1 | ||
|
|
7acc309f66 | ||
|
|
9527de15ed | ||
|
|
aeb25a715a | ||
|
|
46e91a538c | ||
|
|
1fe526c99c | ||
|
|
6b05918807 | ||
|
|
05d2eb8ce0 | ||
|
|
e12c2d9769 | ||
|
|
825245b65f | ||
|
|
ef067f18e1 | ||
|
|
844dc21efd | ||
|
|
1a00e99a24 | ||
|
|
6c5bcf30ba | ||
|
|
be8ffe935d | ||
|
|
0b80bba4ec | ||
|
|
9cc38988d8 | ||
|
|
0d92a5dec8 | ||
|
|
677e4f2540 | ||
|
|
0029e1d8f7 | ||
|
|
635aa5c681 | ||
|
|
f5c4c9448c | ||
|
|
63b1837315 | ||
|
|
bc43a9d329 | ||
|
|
1850c28d3a | ||
|
|
319a058e63 | ||
|
|
678e49268f | ||
|
|
6df4a6f120 | ||
|
|
73c6bbb225 | ||
|
|
fa57085946 | ||
|
|
aef589cd24 | ||
|
|
05ffd8c989 | ||
|
|
1125a5f7cb | ||
|
|
dfba1d9656 | ||
|
|
96418ed684 | ||
|
|
b010233bb4 | ||
|
|
7e28f56f01 | ||
|
|
dd35c31db1 | ||
|
|
c9ac4ed2b5 | ||
|
|
7631ce3b09 | ||
|
|
a5988c59ee | ||
|
|
9ba807f79b | ||
|
|
9d8627b337 | ||
|
|
64bf01c59e | ||
|
|
ed023c8970 | ||
|
|
13bdef22bd | ||
|
|
6f49a6c12a | ||
|
|
7db292779b | ||
|
|
e2496e01d0 | ||
|
|
d5bda04306 | ||
|
|
9165242744 | ||
|
|
927fe0949f | ||
|
|
459216a30e | ||
|
|
c7fa041469 | ||
|
|
cab7360bef | ||
|
|
159ec4a7bd | ||
|
|
ae40f3134a | ||
|
|
3c080e0564 | ||
|
|
e8c1bf5055 | ||
|
|
2a4a5f75c9 | ||
|
|
91b65654d6 | ||
|
|
dcca6e4e17 | ||
|
|
4550545e4f | ||
|
|
af1a4492e8 | ||
|
|
6b1b4463a0 | ||
|
|
632267c13a | ||
|
|
a349707e1f | ||
|
|
84fa6cd3a8 | ||
|
|
05468d3307 | ||
|
|
0da88f39cd | ||
|
|
5dffb0a803 | ||
|
|
e2a5c2d78f | ||
|
|
ca679ec496 | ||
|
|
10282857fe | ||
|
|
263d5b1d89 | ||
|
|
6a4cbbf266 | ||
|
|
8d14972808 | ||
|
|
441eb1697e | ||
|
|
64e6eedb4d | ||
|
|
78d965cc91 | ||
|
|
28dce925b0 | ||
|
|
a2943c4649 | ||
|
|
d4b5b958f3 | ||
|
|
9537cafe25 | ||
|
|
95dd252c14 | ||
|
|
755ceb7d75 | ||
|
|
d5b74dacc8 | ||
|
|
411fc51ea2 | ||
|
|
e714cac0ec | ||
|
|
ab0b687943 | ||
|
|
7f21ee97a8 | ||
|
|
0ed56d382d | ||
|
|
a47cac6e3c | ||
|
|
edbd3612b3 | ||
|
|
e2517c99b8 | ||
|
|
2b01bf99b4 | ||
|
|
bd5bd71a21 | ||
|
|
60187961a0 | ||
|
|
1344f113c5 | ||
|
|
96bbb86346 | ||
|
|
7478315970 | ||
|
|
b894444b8d | ||
|
|
9f8bcd6fb1 | ||
|
|
d82781abbd | ||
|
|
f8c4cac6d3 | ||
|
|
4264b15aab | ||
|
|
168f9d3a45 | ||
|
|
9663555195 | ||
|
|
e92176029c | ||
|
|
fe820c48c8 | ||
|
|
87cd4b8f00 | ||
|
|
9ba06cd604 |
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: gbj
|
||||
17
.github/workflows/test.yml
vendored
@@ -21,13 +21,14 @@ jobs:
|
||||
- ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt
|
||||
|
||||
- name: Setup cargo-make
|
||||
uses: davidB/rust-cargo-make@v1
|
||||
@@ -35,16 +36,10 @@ jobs:
|
||||
- name: Cargo generate-lockfile
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
- name: Run Rustfmt
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Run tests with all features
|
||||
run: cargo make ci
|
||||
|
||||
51
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
_This Code of Conduct is based on the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct)
|
||||
and the [Bevy Code of Conduct](https://raw.githubusercontent.com/bevyengine/bevy/main/CODE_OF_CONDUCT.md),
|
||||
which are adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling)
|
||||
and the [Contributor Covenant](https://www.contributor-covenant.org)._
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
We are a community of people learning and exploring how to build better web applications
|
||||
with Rust. When interacting with one another, please remember that there are no experts and there are
|
||||
no stupid questions. Assume the best in other people's communication, and take a step back if
|
||||
you find yourself getting defensive.
|
||||
|
||||
Please note the following guidelines as well:
|
||||
|
||||
* Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all.
|
||||
* Please be kind and courteous. There’s no need to be mean or rude.
|
||||
* Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
|
||||
* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
|
||||
* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term “harassment” as including the definition in the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups.
|
||||
* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact the maintainers immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back.
|
||||
* Do not make casual mention of slavery or indentured servitude and/or false comparisons of one's occupation or situation to slavery. Please consider using or asking about alternate terminology when referring to such metaphors in technology.
|
||||
* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
|
||||
|
||||
## Moderation
|
||||
|
||||
These are the policies for upholding [our community’s standards of conduct](#our-standards). If you feel that a thread needs moderation, please contact the maintainers.
|
||||
|
||||
1. Remarks that violate the community standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner).
|
||||
2. Remarks that maintainers find inappropriate, whether listed in the code of conduct or not, are also not allowed.
|
||||
3. Maintainers will first respond to such remarks with a warning.
|
||||
4. If the warning is unheeded, the user will be “kicked,” i.e., kicked out of the communication channel to cool off.
|
||||
5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
|
||||
6. Maintainers may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.
|
||||
7. If a maintainer bans someone and you think it was unjustified, please take it up with that maintainer, or with a different maintainer, in private. Complaints about bans in-channel are not allowed.
|
||||
8. Maintainers are held to a higher standard than other community members. If a maintainer creates an inappropriate situation, they should expect less leeway than others.
|
||||
|
||||
The enforcement policies in the code of conduct apply to all official venues, including Discord channels, GitHub repositories, and all other forums.
|
||||
15
Cargo.toml
@@ -23,10 +23,23 @@ members = [
|
||||
]
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
leptos = { path = "./leptos", default-features = false, version = "0.1.0" }
|
||||
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.1.0" }
|
||||
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.1.0" }
|
||||
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.1.0" }
|
||||
leptos_server = { path = "./leptos_server", default-features = false, version = "0.1.0" }
|
||||
leptos_config = { path = "./leptos_config", default-features = false, version = "0.1.0" }
|
||||
leptos_router = { path = "./router", version = "0.1.0" }
|
||||
leptos_meta = { path = "./meta", default-feature = false, version = "0.1.0" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
|
||||
[workspace.metadata.cargo-all-features]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
default_to_workspace = false
|
||||
|
||||
[tasks.ci]
|
||||
dependencies = ["build", "test"]
|
||||
dependencies = ["build", "build-examples", "test"]
|
||||
|
||||
[tasks.build]
|
||||
clear = true
|
||||
@@ -19,6 +19,24 @@ command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.build-examples]
|
||||
clear = true
|
||||
dependencies = [
|
||||
{ name = "build", path = "examples/counter" },
|
||||
{ name = "build", path = "examples/counter_isomorphic" },
|
||||
{ name = "build", path = "examples/counters" },
|
||||
{ name = "build", path = "examples/counters_stable" },
|
||||
{ name = "build", path = "examples/fetch" },
|
||||
{ name = "build", path = "examples/hackernews" },
|
||||
{ name = "build", path = "examples/hackernews_axum" },
|
||||
{ name = "build", path = "examples/parent_child" },
|
||||
{ name = "build", path = "examples/router" },
|
||||
{ name = "build", path = "examples/tailwind" },
|
||||
{ name = "build", path = "examples/todo_app_sqlite" },
|
||||
{ name = "build", path = "examples/todo_app_sqlite_axum" },
|
||||
{ name = "build", path = "examples/todomvc" },
|
||||
]
|
||||
|
||||
[tasks.test]
|
||||
clear = true
|
||||
dependencies = ["test-all"]
|
||||
|
||||
23
README.md
@@ -1,6 +1,7 @@
|
||||
**NOTE: We're in the middle of merging changes and making fixes to support our upcoming `0.1.0` release. Some of the examples may be in a broken state. You can continue using the `0.0` releases with no issues.**
|
||||
|
||||
<img src="https://raw.githubusercontent.com/gbj/leptos/main/docs/logos/logo.svg" alt="Leptos Logo" style="width: 100%; height: auto; display: block; margin: auto;">
|
||||
<picture>
|
||||
<source srcset="https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_pref_dark_RGB.svg" media="(prefers-color-scheme: dark)">
|
||||
<img src="https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_RGB.svg" alt="Leptos Logo">
|
||||
</picture>
|
||||
|
||||
[](https://crates.io/crates/leptos)
|
||||
[](https://docs.rs/leptos)
|
||||
@@ -28,7 +29,7 @@ pub fn SimpleCounter(cx: Scope, initial_value: i32) -> impl IntoView {
|
||||
<div>
|
||||
<button on:click=clear>"Clear"</button>
|
||||
<button on:click=decrement>"-1"</button>
|
||||
<span>"Value: " {move || value().to_string()} "!"</span>
|
||||
<span>"Value: " {value} "!"</span>
|
||||
<button on:click=increment>"+1"</button>
|
||||
</div>
|
||||
}
|
||||
@@ -58,12 +59,11 @@ Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained re
|
||||
|
||||
Here are some resources for learning more about Leptos:
|
||||
|
||||
- [Examples](https://github.com/gbj/leptos/tree/main/examples)
|
||||
- [Examples](https://github.com/leptos-rs/leptos/tree/main/examples)
|
||||
- [API Documentation](https://docs.rs/leptos/latest/leptos/)
|
||||
- [Common Bugs](https://github.com/gbj/leptos/tree/main/docs/COMMON_BUGS.md) (and how to fix them!)
|
||||
- [Common Bugs](https://github.com/leptos-rs/leptos/tree/main/docs/COMMON_BUGS.md) (and how to fix them!)
|
||||
- Leptos Guide (in progress)
|
||||
|
||||
|
||||
## `nightly` Note
|
||||
|
||||
Most of the examples assume you’re using `nightly` Rust.
|
||||
@@ -80,13 +80,13 @@ If you’re on `stable`, note the following:
|
||||
|
||||
1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.1.0-alpha", features = ["stable"] }`
|
||||
2. `nightly` enables the function call syntax for accessing and setting signals. If you’re using `stable`,
|
||||
you’ll just call `.get()`, `.set()`, or `.update()` manually. Check out the
|
||||
[`counters-stable` example](https://github.com/gbj/leptos/blob/main/examples/counters-stable/src/main.rs)
|
||||
you’ll just call `.get()`, `.set()`, or `.update()` manually. Check out the
|
||||
[`counters_stable` example](https://github.com/leptos-rs/leptos/blob/main/examples/counters_stable/src/main.rs)
|
||||
for examples of the correct API.
|
||||
|
||||
## `cargo-leptos`
|
||||
|
||||
[`cargo-leptos`](https://github.com/akesson/cargo-leptos) is a build tool that's designed to make it easy to build apps that run on both the client and the server, with seamless integration. The best way to get started with a real Leptos project right now is to use `cargo-leptos` and our [starter template](https://github.com/leptos-rs/start).
|
||||
[`cargo-leptos`](https://github.com/leptos-rs/cargo-leptos) is a build tool that's designed to make it easy to build apps that run on both the client and the server, with seamless integration. The best way to get started with a real Leptos project right now is to use `cargo-leptos` and our [starter template](https://github.com/leptos-rs/start).
|
||||
|
||||
```bash
|
||||
cargo install cargo-leptos
|
||||
@@ -106,7 +106,7 @@ Sure! Obviously the `view` macro is for generating DOM nodes but you can use the
|
||||
- Use event listeners to update signals
|
||||
- Create effects to update the UI
|
||||
|
||||
I've put together a [very simple GTK example](https://github.com/gbj/leptos/blob/main/examples/gtk/src/main.rs) so you can see what I mean.
|
||||
I've put together a [very simple GTK example](https://github.com/leptos-rs/leptos/blob/main/examples/gtk/src/main.rs) so you can see what I mean.
|
||||
|
||||
### How is this different from Yew/Dioxus?
|
||||
|
||||
@@ -124,7 +124,6 @@ There are some practical differences that make a significant difference:
|
||||
|
||||
- **Maturity:** Sycamore is obviously a much more mature and stable library with a larger ecosystem.
|
||||
- **Templating:** Leptos uses a JSX-like template format (built on [syn-rsx](https://github.com/stoically/syn-rsx)) for its `view` macro. Sycamore offers the choice of its own templating DSL or a builder syntax.
|
||||
- **Template node cloning:** Leptos's `view` macro compiles to a static HTML string and a set of instructions of how to assign its reactive values. This means that at runtime, Leptos can clone a `<template>` node rather than calling `document.createElement()` to create DOM nodes. This is a _significantly_ faster way of rendering components.
|
||||
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);` _(If you prefer or if it's more convenient for your API, you can use `create_rw_signal` to give a unified read/write signal.)_
|
||||
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Introduction
|
||||
|
||||
This book is intended as an introduction to the [Leptos](https://github.com/gbj/leptos) Web framework. Together, we’ll build a simple todo app—first as a client-side app, then as a full-stack app.
|
||||
This book is intended as an introduction to the [Leptos](https://github.com/leptos-rs/leptos) Web framework. Together, we’ll build a simple todo app—first as a client-side app, then as a full-stack app.
|
||||
|
||||
The guide doesn’t assume you know anything about fine-grained reactivity or the details of modern Web frameworks. It does assume you are familiar with the Rust programming language, HTML, CSS, and the DOM and other Web APIs.
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Getting Started
|
||||
|
||||
> The code for this chapter can be found [here](https://github.com/gbj/leptos/tree/main/docs/book/project/ch02_getting_started).
|
||||
> The code for this chapter can be found [here](https://github.com/leptos-rs/leptos/tree/main/docs/book/project/ch02_getting_started).
|
||||
|
||||
The easiest way to get started using Leptos is to use [Trunk](https://trunkrs.dev/), as many of our [examples](https://github.com/gbj/leptos/tree/main/examples) do. (Trunk is a simple build tool that includes a dev server.)
|
||||
The easiest way to get started using Leptos is to use [Trunk](https://trunkrs.dev/), as many of our [examples](https://github.com/leptos-rs/leptos/tree/main/examples) do. (Trunk is a simple build tool that includes a dev server.)
|
||||
|
||||
If you don’t already have it installed, you can install Trunk by running
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Templating: Building User Interfaces
|
||||
|
||||
> The code for this chapter can be found [here](https://github.com/gbj/leptos/tree/main/docs/book/project/ch03_building_ui).
|
||||
> The code for this chapter can be found [here](https://github.com/leptos-rs/leptos/tree/main/docs/book/project/ch03_building_ui).
|
||||
|
||||
## RSX and the `view!` macro
|
||||
|
||||
|
||||
BIN
docs/logos/Leptos_logo_RGB.png
Executable file
|
After Width: | Height: | Size: 72 KiB |
64
docs/logos/Leptos_logo_RGB.svg
Executable file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 437.4294 209.6185" style="enable-background:new 0 0 437.4294 209.6185;" xml:space="preserve">
|
||||
<path style="fill:none;" d="M130.0327,79.3931c-11.4854-0.23-22.52,9.3486-24.5034,21.0117l49.1157,0.0293
|
||||
c-2.1729-10.418-11.1821-21.0449-24.1987-21.0449C130.3081,79.3892,130.1714,79.3907,130.0327,79.3931z"/>
|
||||
<path style="fill:#181139;" d="M95.1109,128.1089H58.6797V65.6861c0-1.5234-0.8169-2.4331-2.1855-2.4331h-3.1187
|
||||
c-1.3159,0-2.2349,1.0005-2.2349,2.4331v67.4297c0,1.4521,0.8145,2.2852,2.2349,2.2852h41.7353c1.4844,0,2.4819-0.9375,2.4819-2.333
|
||||
v-2.7744C97.5928,128.9253,96.6651,128.1089,95.1109,128.1089z"/>
|
||||
<path style="fill:#181139;" d="M146.4561,77.1739c-4.8252-3.001-10.3037-4.5249-16.2837-4.5288c-0.0068,0-0.0137,0-0.0205,0
|
||||
c-5.7349,0-11.1377,1.4639-16.0566,4.3511c-4.916,2.8853-8.8721,6.8364-11.7593,11.7456
|
||||
c-2.8975,4.9248-4.3687,10.332-4.3721,16.0713c-0.0034,5.7188,1.4966,11.0654,4.4565,15.8887
|
||||
c2.9893,4.9209,6.8789,8.7334,11.8887,11.6514c4.8657,2.8633,10.2397,4.3174,15.9717,4.3203c0.0073,0,0.0146,0,0.022,0
|
||||
c8.123,0,14.7441-2.5869,21.4683-8.3906c0.5493-0.4805,0.8516-1.1201,0.8516-1.8008c0.001-0.6074-0.1743-1.1035-0.5205-1.4756
|
||||
l-1.3569-1.8428l-0.0732-0.0859c-0.2637-0.2637-0.6929-0.6152-1.3716-0.6152c-0.6421,0-1.2549,0.2217-1.7124,0.6143
|
||||
c-1.9346,1.585-3.5459,2.8008-4.7969,3.6182c-1.7979,1.208-5.8218,3.2314-12.5986,3.2314c-0.0073,0-0.0142,0-0.021,0
|
||||
c-0.1357,0.0029-0.269,0.0039-0.4043,0.0039c-12.2642,0-23.4736-10.3262-24.5088-22.4814l53.0127,0.0322c0.0015,0,0.0024,0,0.0034,0
|
||||
c2.2373,0,3.4697-1.1621,3.4712-3.2715c0.0034-5.2588-1.3574-10.3945-4.0464-15.2705
|
||||
C155.0015,84.0953,151.2188,80.1363,146.4561,77.1739z M154.6451,100.4341l-49.1157-0.0293
|
||||
c1.9834-11.6631,13.0181-21.2417,24.5034-21.0117c0.1387-0.0024,0.2754-0.0039,0.4136-0.0039
|
||||
C143.4629,79.3892,152.4722,90.0162,154.6451,100.4341z"/>
|
||||
<path style="fill:#181139;" d="M204.0386,136.6382c5.7319,0,11.1069-1.4502,15.9746-4.3115
|
||||
c4.938-2.9014,8.75-6.7129,11.6533-11.6533c2.8608-4.8672,4.311-10.2578,4.311-16.0244c0-5.7324-1.4502-11.1064-4.311-15.9746
|
||||
c-2.9019-4.9385-6.7134-8.75-11.6533-11.6533c-4.8687-2.8618-10.2437-4.3125-15.9746-4.3125
|
||||
c-9.938,0-19.2021,4.7583-24.3516,12.3174v-9.438c0-0.5946-0.1465-1.0788-0.411-1.4511c-0.3815-0.5369-1.0157-0.834-1.8727-0.834
|
||||
h-2.6738c-1.4521,0-2.2852,0.833-2.2852,2.2852v5.6964v46.4791v23.9676c0,1.2568,0.7808,2.0371,2.0371,2.0371h3.3667
|
||||
c0.9209,0,1.6421-0.6992,1.6421-1.5908v-17.098v-10.984C185.0884,131.8892,194.2749,136.6382,204.0386,136.6382z M186.6358,122.5591
|
||||
c-4.9346-4.9346-7.6831-11.4932-7.542-18.0254c-0.1367-6.3506,2.5439-12.751,7.3545-17.5605
|
||||
c4.8521-4.8521,11.3037-7.5547,17.7383-7.417c4.3691,0,8.4863,1.1465,12.2314,3.4043c3.7344,2.2979,6.7456,5.4053,8.9492,9.2354
|
||||
c2.1699,3.9072,3.2695,8.0967,3.2695,12.4697c0.1396,6.4619-2.5967,12.9844-7.5083,17.8955
|
||||
c-4.7617,4.7617-11.0469,7.3857-17.2544,7.2803C197.6856,129.9712,191.396,127.3208,186.6358,122.5591z"/>
|
||||
<path style="fill:#181139;" d="M241.8955,80.3975h7.5669v42.0259c0,6.8174,4.5674,12.1309,11.0825,12.9189
|
||||
c0.6836,0.1055,1.8379,0.1572,3.5303,0.1572c2.0078,0,3.0273-0.3535,3.0273-2.2842v-2.377c0-1.7891-1.334-2.0371-2.7568-2.0371
|
||||
c0,0-0.001,0-0.002,0l-1.7871-0.0488c-2.0117-0.0439-3.4883-0.7627-4.3896-2.1367c-0.9697-1.4805-1.4619-3.1738-1.4619-5.0352
|
||||
V80.3975h10.0928c1.3076,0,2.2852-1.3628,2.2852-2.5815v-1.9312c0-1.3999-0.8359-2.2354-2.2354-2.2354h-10.1426V60.6861
|
||||
c0-1.4619-0.7969-2.4829-1.9375-2.4829c-0.1865,0-0.4121,0-0.6392,0.0884l-2.6489,0.6865
|
||||
c-1.2109,0.3682-2.0171,0.9263-2.0171,2.4507v12.2207h-7.5669c-1.4185,0-2.335,0.897-2.335,2.2852v1.8813
|
||||
C239.5606,79.2393,240.6079,80.3975,241.8955,80.3975z"/>
|
||||
<path style="fill:#181139;" d="M379.1182,106.2691c-4.0488-2.9219-8.8545-5.0293-14.291-6.2646
|
||||
c-6.5049-1.3975-13.4473-5.2129-13.3203-10.3066c0-7.5225,6.6367-10.1914,12.3203-10.1914c5.3574,0,10.2207,3.002,13.001,8.0146
|
||||
c0.6729,1.2861,1.4785,1.9375,2.3955,1.9375c0.3311,0,0.7061-0.1113,0.9922-0.2832l2.2021-1.1523
|
||||
c0.5947-0.3408,0.9229-0.9414,0.9229-1.6924c0-0.5205-0.0908-0.9541-0.2617-1.292c-3.6367-8.2466-10.0967-12.4282-19.2021-12.4282
|
||||
c-11.7305,0-19.6123,6.9263-19.6123,17.2349c0,4.3125,1.8438,7.9746,5.4756,10.8809c3.4482,2.7979,7.9121,4.8623,13.2705,6.1377
|
||||
c4.5859,1.085,8.3193,2.5654,11.0977,4.4023c1.4159,0.9354,2.4412,2.0535,3.106,3.3672c0.6053,1.1962,0.9135,2.5535,0.9135,4.1005
|
||||
c0.0742,2.3857-0.79,4.5176-2.5684,6.3389c-3.1445,3.2178-8.4053,4.6689-12.0205,4.6689c-0.0361,0-0.0723,0-0.1074,0
|
||||
c-3.4268,0-6.4893-0.8438-9.1035-2.5068c-2.5918-1.6484-4.2363-3.8076-5.0293-6.6064c-0.3203-1.0996-0.751-2.1738-2.1553-2.1738
|
||||
c-0.0742,0-0.2109,0.0146-0.4062,0.0449c-0.1133,0.0166-0.2559,0.0381-0.5088,0.0742l-1.8818,0.4463l-0.1045,0.0332
|
||||
c-1.0244,0.4082-1.6113,1.1846-1.6113,2.1309c0,0.2285,0.0625,0.6592,0.2178,1.1094c1.9707,8.5801,10.2432,14.3447,20.5732,14.3447
|
||||
c0.125,0.002,0.249,0.002,0.374,0.002c6.5947,0,12.6748-2.3193,16.7275-6.3945c3.1895-3.208,4.8311-7.2363,4.748-11.6357
|
||||
c0-2.8187-0.6185-5.3109-1.8062-7.481C382.4437,109.2624,381.0062,107.631,379.1182,106.2691z"/>
|
||||
<path style="fill:#EF3939;" d="M348.9043,45.7325c0-6.3157-3.2826-11.8699-8.2238-15.0756
|
||||
c-2.811-1.8237-6.1537-2.8947-9.7469-2.8947c-9.9092,0-17.9707,8.0615-17.9707,17.9702c0,4.7659,1.8775,9.0925,4.9157,12.3123
|
||||
c-3.6619,4.3709-6.6334,9.3336-8.7663,14.7186c-1.5873-0.2422-3.2123-0.3683-4.8662-0.3683
|
||||
c-17.7158,0-32.1289,14.4131-32.1289,32.1289c0,14.6854,9.9077,27.0922,23.3869,30.9101
|
||||
c-6.7762,17.3461-23.6572,29.6719-43.3742,29.6719c-16.8195,0-31.583-8.9662-39.7656-22.369
|
||||
c-2.4778,0.5446-5.0429,0.8519-7.6721,0.9023c9.0226,16.99,26.8969,28.5917,47.4377,28.5917
|
||||
c23.2646,0,43.1121-14.8788,50.5461-35.6179c0.5204,0.0251,1.0435,0.0398,1.5701,0.0398c17.7158,0,32.1289-14.4131,32.1289-32.1289
|
||||
c0-13.557-8.4446-25.1712-20.3465-29.8811c1.9001-4.5678,4.5115-8.7646,7.6888-12.4641c0.9996,0.4404,2.0479,0.785,3.1324,1.0384
|
||||
c1.3144,0.3071,2.6773,0.486,4.0839,0.486C340.8428,63.7032,348.9043,55.6416,348.9043,45.7325z M304.2461,129.5279
|
||||
c-13.7871,0-25.0039-11.2168-25.0039-25.0039s11.2168-25.0039,25.0039-25.0039S329.25,90.7369,329.25,104.524
|
||||
S318.0332,129.5279,304.2461,129.5279z M330.9336,34.8872c0.645,0,1.2737,0.0671,1.8881,0.1755
|
||||
c5.0818,0.8974,8.9576,5.3347,8.9576,10.6697c0,5.9805-4.8652,10.8457-10.8457,10.8457s-10.8457-4.8652-10.8457-10.8457
|
||||
c0-1.3967,0.2746-2.7282,0.7576-3.9555C322.4306,37.7496,326.35,34.8872,330.9336,34.8872z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
61
docs/logos/Leptos_logo_Solid_Black.svg
Executable file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 437.4294 209.6185" style="enable-background:new 0 0 437.4294 209.6185;" xml:space="preserve">
|
||||
<path d="M95.1109,128.1089H58.6797V65.6861c0-1.5234-0.8169-2.4331-2.1855-2.4331h-3.1187c-1.3159,0-2.2349,1.0005-2.2349,2.4331
|
||||
v67.4297c0,1.4521,0.8145,2.2852,2.2349,2.2852h41.7353c1.4844,0,2.4819-0.9375,2.4819-2.333v-2.7744
|
||||
C97.5928,128.9253,96.6651,128.1089,95.1109,128.1089z"/>
|
||||
<path d="M146.4561,77.1739c-4.8252-3.001-10.3037-4.5249-16.2837-4.5288c-0.0068,0-0.0137,0-0.0205,0
|
||||
c-5.7349,0-11.1377,1.4639-16.0566,4.3511c-4.916,2.8853-8.8721,6.8364-11.7593,11.7456
|
||||
c-2.8975,4.9248-4.3687,10.332-4.3721,16.0713c-0.0034,5.7188,1.4966,11.0654,4.4565,15.8887
|
||||
c2.9893,4.9209,6.8789,8.7334,11.8887,11.6514c4.8657,2.8633,10.2397,4.3174,15.9717,4.3203c0.0073,0,0.0146,0,0.022,0
|
||||
c8.123,0,14.7441-2.5869,21.4683-8.3906c0.5493-0.4805,0.8516-1.1201,0.8516-1.8008c0.001-0.6074-0.1743-1.1035-0.5205-1.4756
|
||||
l-1.3569-1.8428l-0.0732-0.0859c-0.2637-0.2637-0.6929-0.6152-1.3716-0.6152c-0.6421,0-1.2549,0.2217-1.7124,0.6143
|
||||
c-1.9346,1.585-3.5459,2.8008-4.7969,3.6182c-1.7979,1.208-5.8218,3.2314-12.5986,3.2314c-0.0073,0-0.0142,0-0.021,0
|
||||
c-0.1357,0.0029-0.269,0.0039-0.4043,0.0039c-12.2642,0-23.4736-10.3262-24.5088-22.4814l53.0127,0.0322c0.0015,0,0.0024,0,0.0034,0
|
||||
c2.2373,0,3.4697-1.1621,3.4712-3.2715c0.0034-5.2588-1.3574-10.3945-4.0464-15.2705
|
||||
C155.0015,84.0953,151.2188,80.1363,146.4561,77.1739z M154.6451,100.4341l-49.1157-0.0293
|
||||
c1.9834-11.6631,13.0181-21.2417,24.5034-21.0117c0.1387-0.0024,0.2754-0.0039,0.4136-0.0039
|
||||
C143.4629,79.3892,152.4722,90.0162,154.6451,100.4341z"/>
|
||||
<path d="M204.0386,136.6382c5.7319,0,11.1069-1.4502,15.9746-4.3115c4.938-2.9014,8.75-6.7129,11.6533-11.6533
|
||||
c2.8608-4.8672,4.311-10.2578,4.311-16.0244c0-5.7324-1.4502-11.1064-4.311-15.9746c-2.9019-4.9385-6.7134-8.75-11.6533-11.6533
|
||||
c-4.8687-2.8618-10.2437-4.3125-15.9746-4.3125c-9.938,0-19.2021,4.7583-24.3516,12.3174v-9.438
|
||||
c0-0.5946-0.1465-1.0788-0.411-1.4511c-0.3815-0.5369-1.0157-0.834-1.8727-0.834h-2.6738c-1.4521,0-2.2852,0.833-2.2852,2.2852
|
||||
v5.6964v46.4791v23.9676c0,1.2568,0.7808,2.0371,2.0371,2.0371h3.3667c0.9209,0,1.6421-0.6992,1.6421-1.5908v-17.098v-10.984
|
||||
C185.0884,131.8892,194.2749,136.6382,204.0386,136.6382z M186.6358,122.5591c-4.9346-4.9346-7.6831-11.4932-7.542-18.0254
|
||||
c-0.1367-6.3506,2.5439-12.751,7.3545-17.5605c4.8521-4.8521,11.3037-7.5547,17.7383-7.417c4.3691,0,8.4863,1.1465,12.2314,3.4043
|
||||
c3.7344,2.2979,6.7456,5.4053,8.9492,9.2354c2.1699,3.9072,3.2695,8.0967,3.2695,12.4697
|
||||
c0.1396,6.4619-2.5967,12.9844-7.5083,17.8955c-4.7617,4.7617-11.0469,7.3857-17.2544,7.2803
|
||||
C197.6856,129.9712,191.396,127.3208,186.6358,122.5591z"/>
|
||||
<path d="M241.8955,80.3975h7.5669v42.0259c0,6.8174,4.5674,12.1309,11.0825,12.9189c0.6836,0.1055,1.8379,0.1572,3.5303,0.1572
|
||||
c2.0078,0,3.0273-0.3535,3.0273-2.2842v-2.377c0-1.7891-1.334-2.0371-2.7568-2.0371c0,0-0.001,0-0.002,0l-1.7871-0.0488
|
||||
c-2.0117-0.0439-3.4883-0.7627-4.3896-2.1367c-0.9697-1.4805-1.4619-3.1738-1.4619-5.0352V80.3975h10.0928
|
||||
c1.3076,0,2.2852-1.3628,2.2852-2.5815v-1.9312c0-1.3999-0.8359-2.2354-2.2354-2.2354h-10.1426V60.6861
|
||||
c0-1.4619-0.7969-2.4829-1.9375-2.4829c-0.1865,0-0.4121,0-0.6392,0.0884l-2.6489,0.6865
|
||||
c-1.2109,0.3682-2.0171,0.9263-2.0171,2.4507v12.2207h-7.5669c-1.4185,0-2.335,0.897-2.335,2.2852v1.8813
|
||||
C239.5606,79.2393,240.6079,80.3975,241.8955,80.3975z"/>
|
||||
<path d="M379.1182,106.2691c-4.0488-2.9219-8.8545-5.0293-14.291-6.2646c-6.5049-1.3975-13.4473-5.2129-13.3203-10.3066
|
||||
c0-7.5225,6.6367-10.1914,12.3203-10.1914c5.3574,0,10.2207,3.002,13.001,8.0146c0.6729,1.2861,1.4785,1.9375,2.3955,1.9375
|
||||
c0.3311,0,0.7061-0.1113,0.9922-0.2832l2.2021-1.1523c0.5947-0.3408,0.9229-0.9414,0.9229-1.6924
|
||||
c0-0.5205-0.0908-0.9541-0.2617-1.292c-3.6367-8.2466-10.0967-12.4282-19.2021-12.4282c-11.7305,0-19.6123,6.9263-19.6123,17.2349
|
||||
c0,4.3125,1.8438,7.9746,5.4756,10.8809c3.4482,2.7979,7.9121,4.8623,13.2705,6.1377c4.5859,1.085,8.3193,2.5654,11.0977,4.4023
|
||||
c1.4159,0.9354,2.4412,2.0535,3.106,3.3672c0.6053,1.1962,0.9135,2.5535,0.9135,4.1005c0.0742,2.3857-0.79,4.5176-2.5684,6.3389
|
||||
c-3.1445,3.2178-8.4053,4.6689-12.0205,4.6689c-0.0361,0-0.0723,0-0.1074,0c-3.4268,0-6.4893-0.8438-9.1035-2.5068
|
||||
c-2.5918-1.6484-4.2363-3.8076-5.0293-6.6064c-0.3203-1.0996-0.751-2.1738-2.1553-2.1738c-0.0742,0-0.2109,0.0146-0.4062,0.0449
|
||||
c-0.1133,0.0166-0.2559,0.0381-0.5088,0.0742l-1.8818,0.4463l-0.1045,0.0332c-1.0244,0.4082-1.6113,1.1846-1.6113,2.1309
|
||||
c0,0.2285,0.0625,0.6592,0.2178,1.1094c1.9707,8.5801,10.2432,14.3447,20.5732,14.3447c0.125,0.002,0.249,0.002,0.374,0.002
|
||||
c6.5947,0,12.6748-2.3193,16.7275-6.3945c3.1895-3.208,4.8311-7.2363,4.748-11.6357c0-2.8187-0.6185-5.3109-1.8062-7.481
|
||||
C382.4437,109.2624,381.0062,107.631,379.1182,106.2691z"/>
|
||||
<path d="M348.9043,45.7325c0-6.3157-3.2826-11.8699-8.2238-15.0756c-2.811-1.8237-6.1537-2.8947-9.7469-2.8947
|
||||
c-9.9092,0-17.9707,8.0615-17.9707,17.9702c0,4.7659,1.8775,9.0925,4.9157,12.3123c-3.6619,4.3709-6.6334,9.3336-8.7663,14.7186
|
||||
c-1.5873-0.2422-3.2123-0.3683-4.8662-0.3683c-17.7158,0-32.1289,14.4131-32.1289,32.1289c0,14.6854,9.9077,27.0922,23.3869,30.9101
|
||||
c-6.7762,17.3461-23.6572,29.6719-43.3742,29.6719c-16.8195,0-31.583-8.9662-39.7656-22.369
|
||||
c-2.4778,0.5446-5.0429,0.8519-7.6721,0.9023c9.0226,16.99,26.8969,28.5917,47.4377,28.5917
|
||||
c23.2646,0,43.1121-14.8788,50.5461-35.6179c0.5204,0.0251,1.0435,0.0398,1.5701,0.0398c17.7158,0,32.1289-14.4131,32.1289-32.1289
|
||||
c0-13.557-8.4446-25.1712-20.3465-29.8811c1.9001-4.5678,4.5115-8.7646,7.6888-12.4641c0.9996,0.4404,2.0479,0.785,3.1324,1.0384
|
||||
c1.3144,0.3071,2.6773,0.486,4.0839,0.486C340.8428,63.7032,348.9043,55.6416,348.9043,45.7325z M304.2461,129.5279
|
||||
c-13.7871,0-25.0039-11.2168-25.0039-25.0039s11.2168-25.0039,25.0039-25.0039S329.25,90.7369,329.25,104.524
|
||||
S318.0332,129.5279,304.2461,129.5279z M330.9336,34.8872c0.645,0,1.2737,0.0671,1.8881,0.1755
|
||||
c5.0818,0.8974,8.9576,5.3347,8.9576,10.6697c0,5.9805-4.8652,10.8457-10.8457,10.8457s-10.8457-4.8652-10.8457-10.8457
|
||||
c0-1.3967,0.2746-2.7282,0.7576-3.9555C322.4306,37.7496,326.35,34.8872,330.9336,34.8872z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
62
docs/logos/Leptos_logo_Solid_White.svg
Executable file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 437.4294 209.6185" style="enable-background:new 0 0 437.4294 209.6185;" xml:space="preserve">
|
||||
<path style="fill:#FFFFFF;" d="M95.1109,128.1089H58.6797V65.6861c0-1.5234-0.8169-2.4331-2.1855-2.4331h-3.1187
|
||||
c-1.3159,0-2.2349,1.0005-2.2349,2.4331v67.4297c0,1.4521,0.8145,2.2852,2.2349,2.2852h41.7353c1.4844,0,2.4819-0.9375,2.4819-2.333
|
||||
v-2.7744C97.5928,128.9253,96.6651,128.1089,95.1109,128.1089z"/>
|
||||
<path style="fill:#FFFFFF;" d="M146.4561,77.1739c-4.8252-3.001-10.3037-4.5249-16.2837-4.5288c-0.0068,0-0.0137,0-0.0205,0
|
||||
c-5.7349,0-11.1377,1.4639-16.0566,4.3511c-4.916,2.8853-8.8721,6.8364-11.7593,11.7456
|
||||
c-2.8975,4.9248-4.3687,10.332-4.3721,16.0713c-0.0034,5.7188,1.4966,11.0654,4.4565,15.8887
|
||||
c2.9893,4.9209,6.8789,8.7334,11.8887,11.6514c4.8657,2.8633,10.2397,4.3174,15.9717,4.3203c0.0073,0,0.0146,0,0.022,0
|
||||
c8.123,0,14.7441-2.5869,21.4683-8.3906c0.5493-0.4805,0.8516-1.1201,0.8516-1.8008c0.001-0.6074-0.1743-1.1035-0.5205-1.4756
|
||||
l-1.3569-1.8428l-0.0732-0.0859c-0.2637-0.2637-0.6929-0.6152-1.3716-0.6152c-0.6421,0-1.2549,0.2217-1.7124,0.6143
|
||||
c-1.9346,1.585-3.5459,2.8008-4.7969,3.6182c-1.7979,1.208-5.8218,3.2314-12.5986,3.2314c-0.0073,0-0.0142,0-0.021,0
|
||||
c-0.1357,0.0029-0.269,0.0039-0.4043,0.0039c-12.2642,0-23.4736-10.3262-24.5088-22.4814l53.0127,0.0322c0.0015,0,0.0024,0,0.0034,0
|
||||
c2.2373,0,3.4697-1.1621,3.4712-3.2715c0.0034-5.2588-1.3574-10.3945-4.0464-15.2705
|
||||
C155.0015,84.0953,151.2188,80.1363,146.4561,77.1739z M154.6451,100.4341l-49.1157-0.0293
|
||||
c1.9834-11.6631,13.0181-21.2417,24.5034-21.0117c0.1387-0.0024,0.2754-0.0039,0.4136-0.0039
|
||||
C143.4629,79.3892,152.4722,90.0162,154.6451,100.4341z"/>
|
||||
<path style="fill:#FFFFFF;" d="M204.0386,136.6382c5.7319,0,11.1069-1.4502,15.9746-4.3115
|
||||
c4.938-2.9014,8.75-6.7129,11.6533-11.6533c2.8608-4.8672,4.311-10.2578,4.311-16.0244c0-5.7324-1.4502-11.1064-4.311-15.9746
|
||||
c-2.9019-4.9385-6.7134-8.75-11.6533-11.6533c-4.8687-2.8618-10.2437-4.3125-15.9746-4.3125
|
||||
c-9.938,0-19.2021,4.7583-24.3516,12.3174v-9.438c0-0.5946-0.1465-1.0788-0.411-1.4511c-0.3815-0.5369-1.0157-0.834-1.8727-0.834
|
||||
h-2.6738c-1.4521,0-2.2852,0.833-2.2852,2.2852v5.6964v46.4791v23.9676c0,1.2568,0.7808,2.0371,2.0371,2.0371h3.3667
|
||||
c0.9209,0,1.6421-0.6992,1.6421-1.5908v-17.098v-10.984C185.0884,131.8892,194.2749,136.6382,204.0386,136.6382z M186.6358,122.5591
|
||||
c-4.9346-4.9346-7.6831-11.4932-7.542-18.0254c-0.1367-6.3506,2.5439-12.751,7.3545-17.5605
|
||||
c4.8521-4.8521,11.3037-7.5547,17.7383-7.417c4.3691,0,8.4863,1.1465,12.2314,3.4043c3.7344,2.2979,6.7456,5.4053,8.9492,9.2354
|
||||
c2.1699,3.9072,3.2695,8.0967,3.2695,12.4697c0.1396,6.4619-2.5967,12.9844-7.5083,17.8955
|
||||
c-4.7617,4.7617-11.0469,7.3857-17.2544,7.2803C197.6856,129.9712,191.396,127.3208,186.6358,122.5591z"/>
|
||||
<path style="fill:#FFFFFF;" d="M241.8955,80.3975h7.5669v42.0259c0,6.8174,4.5674,12.1309,11.0825,12.9189
|
||||
c0.6836,0.1055,1.8379,0.1572,3.5303,0.1572c2.0078,0,3.0273-0.3535,3.0273-2.2842v-2.377c0-1.7891-1.334-2.0371-2.7568-2.0371
|
||||
c0,0-0.001,0-0.002,0l-1.7871-0.0488c-2.0117-0.0439-3.4883-0.7627-4.3896-2.1367c-0.9697-1.4805-1.4619-3.1738-1.4619-5.0352
|
||||
V80.3975h10.0928c1.3076,0,2.2852-1.3628,2.2852-2.5815v-1.9312c0-1.3999-0.8359-2.2354-2.2354-2.2354h-10.1426V60.6861
|
||||
c0-1.4619-0.7969-2.4829-1.9375-2.4829c-0.1865,0-0.4121,0-0.6392,0.0884l-2.6489,0.6865
|
||||
c-1.2109,0.3682-2.0171,0.9263-2.0171,2.4507v12.2207h-7.5669c-1.4185,0-2.335,0.897-2.335,2.2852v1.8813
|
||||
C239.5606,79.2393,240.6079,80.3975,241.8955,80.3975z"/>
|
||||
<path style="fill:#FFFFFF;" d="M379.1182,106.2691c-4.0488-2.9219-8.8545-5.0293-14.291-6.2646
|
||||
c-6.5049-1.3975-13.4473-5.2129-13.3203-10.3066c0-7.5225,6.6367-10.1914,12.3203-10.1914c5.3574,0,10.2207,3.002,13.001,8.0146
|
||||
c0.6729,1.2861,1.4785,1.9375,2.3955,1.9375c0.3311,0,0.7061-0.1113,0.9922-0.2832l2.2021-1.1523
|
||||
c0.5947-0.3408,0.9229-0.9414,0.9229-1.6924c0-0.5205-0.0908-0.9541-0.2617-1.292c-3.6367-8.2466-10.0967-12.4282-19.2021-12.4282
|
||||
c-11.7305,0-19.6123,6.9263-19.6123,17.2349c0,4.3125,1.8438,7.9746,5.4756,10.8809c3.4482,2.7979,7.9121,4.8623,13.2705,6.1377
|
||||
c4.5859,1.085,8.3193,2.5654,11.0977,4.4023c1.4159,0.9354,2.4412,2.0535,3.106,3.3672c0.6053,1.1962,0.9135,2.5535,0.9135,4.1005
|
||||
c0.0742,2.3857-0.79,4.5176-2.5684,6.3389c-3.1445,3.2178-8.4053,4.6689-12.0205,4.6689c-0.0361,0-0.0723,0-0.1074,0
|
||||
c-3.4268,0-6.4893-0.8438-9.1035-2.5068c-2.5918-1.6484-4.2363-3.8076-5.0293-6.6064c-0.3203-1.0996-0.751-2.1738-2.1553-2.1738
|
||||
c-0.0742,0-0.2109,0.0146-0.4062,0.0449c-0.1133,0.0166-0.2559,0.0381-0.5088,0.0742l-1.8818,0.4463l-0.1045,0.0332
|
||||
c-1.0244,0.4082-1.6113,1.1846-1.6113,2.1309c0,0.2285,0.0625,0.6592,0.2178,1.1094c1.9707,8.5801,10.2432,14.3447,20.5732,14.3447
|
||||
c0.125,0.002,0.249,0.002,0.374,0.002c6.5947,0,12.6748-2.3193,16.7275-6.3945c3.1895-3.208,4.8311-7.2363,4.748-11.6357
|
||||
c0-2.8187-0.6185-5.3109-1.8062-7.481C382.4437,109.2624,381.0062,107.631,379.1182,106.2691z"/>
|
||||
<path style="fill:#FFFFFF;" d="M348.9043,45.7325c0-6.3157-3.2826-11.8699-8.2238-15.0756
|
||||
c-2.811-1.8237-6.1537-2.8947-9.7469-2.8947c-9.9092,0-17.9707,8.0615-17.9707,17.9702c0,4.7659,1.8775,9.0925,4.9157,12.3123
|
||||
c-3.6619,4.3709-6.6334,9.3336-8.7663,14.7186c-1.5873-0.2422-3.2123-0.3683-4.8662-0.3683
|
||||
c-17.7158,0-32.1289,14.4131-32.1289,32.1289c0,14.6854,9.9077,27.0922,23.3869,30.9101
|
||||
c-6.7762,17.3461-23.6572,29.6719-43.3742,29.6719c-16.8195,0-31.583-8.9662-39.7656-22.369
|
||||
c-2.4778,0.5446-5.0429,0.8519-7.6721,0.9023c9.0226,16.99,26.8969,28.5917,47.4377,28.5917
|
||||
c23.2646,0,43.1121-14.8788,50.5461-35.6179c0.5204,0.0251,1.0435,0.0398,1.5701,0.0398c17.7158,0,32.1289-14.4131,32.1289-32.1289
|
||||
c0-13.557-8.4446-25.1712-20.3465-29.8811c1.9001-4.5678,4.5115-8.7646,7.6888-12.4641c0.9996,0.4404,2.0479,0.785,3.1324,1.0384
|
||||
c1.3144,0.3071,2.6773,0.486,4.0839,0.486C340.8428,63.7032,348.9043,55.6416,348.9043,45.7325z M304.2461,129.5279
|
||||
c-13.7871,0-25.0039-11.2168-25.0039-25.0039s11.2168-25.0039,25.0039-25.0039S329.25,90.7369,329.25,104.524
|
||||
S318.0332,129.5279,304.2461,129.5279z M330.9336,34.8872c0.645,0,1.2737,0.0671,1.8881,0.1755
|
||||
c5.0818,0.8974,8.9576,5.3347,8.9576,10.6697c0,5.9805-4.8652,10.8457-10.8457,10.8457s-10.8457-4.8652-10.8457-10.8457
|
||||
c0-1.3967,0.2746-2.7282,0.7576-3.9555C322.4306,37.7496,326.35,34.8872,330.9336,34.8872z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
BIN
docs/logos/Leptos_logo_abbreviation__circle_RGB.png
Executable file
|
After Width: | Height: | Size: 38 KiB |
37
docs/logos/Leptos_logo_abbreviation__circle_RGB.svg
Executable file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 115.9988 115.9988" style="enable-background:new 0 0 115.9988 115.9988;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#180D38;" d="M29.1281,108.2941c9.5736-4.5548,17.1531-12.6456,21.0335-22.5787
|
||||
c-12.0865-3.4232-20.9707-14.548-20.9707-27.7159c0-15.8849,12.9236-28.8085,28.8085-28.8085
|
||||
c1.4832,0,2.9404,0.113,4.3639,0.3303c1.9125-4.8287,4.5771-9.2786,7.8607-13.1979c-2.7243-2.8871-4.4077-6.7665-4.4077-11.0399
|
||||
c0-1.6191,0.2457-3.1808,0.6921-4.6562C63.7305,0.2186,60.8908,0,57.9995,0C25.9672,0,0,25.9672,0,57.9994
|
||||
C0,79.5165,11.7263,98.2828,29.1281,108.2941z"/>
|
||||
<path style="fill:#EF3939;" d="M81.9297,15.0082c3.6788,0,6.886-2.0536,8.5379-5.0742
|
||||
c-5.3185-3.5997-11.2684-6.3339-17.646-8.0151c-0.3903,1.0504-0.6168,2.1798-0.6168,3.3644
|
||||
C72.2049,10.6458,76.5673,15.0082,81.9297,15.0082z"/>
|
||||
<path style="fill:#180D38;" d="M95.5663,13.828c-2.8537,4.5375-7.8918,7.5688-13.6366,7.5688
|
||||
c-1.2614,0-2.4835-0.1604-3.6622-0.4359c-0.9722-0.2272-1.9121-0.5362-2.8083-0.931c-2.8492,3.3173-5.1907,7.0806-6.8945,11.1766
|
||||
c10.6715,4.2233,18.2432,14.6371,18.2432,26.7928c0,15.8849-12.9235,28.8085-28.8085,28.8085
|
||||
c-0.4718,0-0.9406-0.0131-1.4069-0.0357c-3.7532,10.4704-11.0354,19.2744-20.406,24.9696
|
||||
c6.7355,2.7367,14.0948,4.257,21.8129,4.257c32.0322,0,57.9994-25.9672,57.9994-57.9995
|
||||
C115.9988,40.3018,108.0628,24.4664,95.5663,13.828z"/>
|
||||
<circle style="fill:#EF3939;" cx="57.9994" cy="57.9994" r="22.4198"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M78.2676,20.961c1.1786,0.2755,2.4008,0.4359,3.6622,0.4359
|
||||
c5.7448,0,10.7829-3.0313,13.6366-7.5688c-1.6275-1.3855-3.3236-2.6925-5.0987-3.894c-1.6519,3.0206-4.8591,5.0742-8.5379,5.0742
|
||||
c-5.3624,0-9.7249-4.3624-9.7249-9.7249c0-1.1846,0.2264-2.3141,0.6168-3.3644c-2.062-0.5436-4.1682-0.9763-6.3133-1.2917
|
||||
c-0.4464,1.4753-0.6921,3.0371-0.6921,4.6562c0,4.2734,1.6834,8.1528,4.4077,11.0399c-3.2836,3.9193-5.9482,8.3692-7.8607,13.1979
|
||||
c-1.4235-0.2172-2.8807-0.3303-4.3639-0.3303c-15.8849,0-28.8085,12.9235-28.8085,28.8085
|
||||
c0,13.168,8.8842,24.2928,20.9707,27.7159c-3.8804,9.9332-11.4599,18.0239-21.0335,22.5787
|
||||
c2.2621,1.3013,4.6175,2.456,7.0584,3.4478c9.3706-5.6952,16.6528-14.4992,20.406-24.9696
|
||||
c0.4663,0.0226,0.9351,0.0357,1.4069,0.0357c15.8849,0,28.8085-12.9236,28.8085-28.8085c0-12.1557-7.5717-22.5695-18.2432-26.7928
|
||||
c1.7038-4.0959,4.0453-7.8593,6.8945-11.1766C76.3555,20.4248,77.2953,20.7338,78.2676,20.961z M80.4193,57.9994
|
||||
c0,12.3623-10.0576,22.4199-22.4198,22.4199c-12.3623,0-22.4199-10.0576-22.4199-22.4199
|
||||
c0-12.3622,10.0576-22.4199,22.4199-22.4199C70.3617,35.5795,80.4193,45.6371,80.4193,57.9994z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
BIN
docs/logos/Leptos_logo_abbreviation__square_RGB.png
Executable file
|
After Width: | Height: | Size: 27 KiB |
27
docs/logos/Leptos_logo_abbreviation__square_RGB.svg
Executable file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 115.5026 115.5026" style="enable-background:new 0 0 115.5026 115.5026;" xml:space="preserve">
|
||||
<path style="fill:#181139;" d="M115.5026,0h-13.957c0.0002,0.0315,0.0031,0.0623,0.0031,0.0938
|
||||
c0,9.718-7.9059,17.6239-17.6239,17.6239c-1.3796,0-2.7163-0.1754-4.0055-0.4767c-1.0634-0.2485-2.0913-0.5864-3.0715-1.0182
|
||||
c-3.1162,3.6283-5.6772,7.7443-7.5408,12.2242c11.6719,4.6192,19.9532,16.0091,19.9532,29.3043
|
||||
c0,17.374-14.1349,31.5089-31.5089,31.5089c-0.5161,0-1.0288-0.0143-1.5388-0.039c-3.8856,10.8397-11.2302,20.0454-20.6959,26.2814
|
||||
h79.986V0z"/>
|
||||
<circle style="fill:#EF3939;" cx="57.7513" cy="57.7513" r="24.5214"/>
|
||||
<path style="fill:#181139;" d="M49.1788,88.0652c-13.2195-3.744-22.9364-15.9116-22.9364-30.3139
|
||||
c0-17.374,14.1349-31.5089,31.5089-31.5089c1.6223,0,3.2161,0.1237,4.7729,0.3612c2.0918-5.2813,5.0061-10.1484,8.5975-14.4351
|
||||
c-2.9796-3.1577-4.8209-7.4008-4.8209-12.0747c0-0.0317,0.0046-0.0622,0.0048-0.0938H0v115.5026h18.8623
|
||||
C32.7495,111.6378,43.9877,101.3537,49.1788,88.0652z"/>
|
||||
<path style="fill:#EF3939;" d="M83.9248,10.7302c5.8651,0,10.6364-4.7714,10.6364-10.6364c0-0.0316-0.004-0.0623-0.0043-0.0938
|
||||
H73.293c-0.0003,0.0316-0.0046,0.0621-0.0046,0.0938C73.2884,5.9589,78.0598,10.7302,83.9248,10.7302z"/>
|
||||
<path style="fill:#FFFFFF;" d="M56.2125,89.2212c0.51,0.0247,1.0228,0.039,1.5388,0.039c17.374,0,31.5089-14.1349,31.5089-31.5089
|
||||
c0-13.2952-8.2814-24.6851-19.9532-29.3043c1.8635-4.4799,4.4246-8.5959,7.5408-12.2242c0.9802,0.4318,2.0082,0.7698,3.0715,1.0182
|
||||
c1.2892,0.3013,2.6259,0.4767,4.0055,0.4767c9.718,0,17.6239-7.9059,17.6239-17.6239c0-0.0315-0.0029-0.0623-0.0031-0.0938h-6.9887
|
||||
c0.0003,0.0316,0.0043,0.0622,0.0043,0.0938c0,5.8651-4.7714,10.6364-10.6364,10.6364S73.2884,5.9589,73.2884,0.0938
|
||||
c0-0.0317,0.0043-0.0622,0.0046-0.0938h-6.9874c-0.0002,0.0316-0.0048,0.0621-0.0048,0.0938c0,4.674,1.8413,8.9171,4.8209,12.0747
|
||||
c-3.5914,4.2867-6.5057,9.1537-8.5975,14.4351c-1.5569-0.2375-3.1507-0.3612-4.7729-0.3612
|
||||
c-17.374,0-31.5089,14.1349-31.5089,31.5089c0,14.4023,9.7169,26.5699,22.9364,30.3139
|
||||
c-5.1912,13.2885-16.4293,23.5726-30.3165,27.4374h16.6543C44.9824,109.2666,52.327,100.0609,56.2125,89.2212z M33.2299,57.7513
|
||||
c0-13.5211,11.0004-24.5214,24.5214-24.5214s24.5214,11.0004,24.5214,24.5214S71.2724,82.2727,57.7513,82.2727
|
||||
S33.2299,71.2723,33.2299,57.7513z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
13
docs/logos/Leptos_logo_pref_dark_RGB.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 437.4 209.6" style="enable-background:new 0 0 437.4 209.6;" xmlns="http://www.w3.org/2000/svg">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#EF3939;}
|
||||
</style>
|
||||
<path class="st0" d="M95.1,128.1H58.7V65.7c0-1.5-0.8-2.4-2.2-2.4h-3.1c-1.3,0-2.2,1-2.2,2.4v67.4c0,1.5,0.8,2.3,2.2,2.3h41.7 c1.5,0,2.5-0.9,2.5-2.3v-2.8C97.6,128.9,96.7,128.1,95.1,128.1z"/>
|
||||
<path class="st0" d="M146.5,77.2c-4.8-3-10.3-4.5-16.3-4.5c0,0,0,0,0,0c-5.7,0-11.1,1.5-16.1,4.4c-4.9,2.9-8.9,6.8-11.8,11.7 c-2.9,4.9-4.4,10.3-4.4,16.1c0,5.7,1.5,11.1,4.5,15.9c3,4.9,6.9,8.7,11.9,11.7c4.9,2.9,10.2,4.3,16,4.3c0,0,0,0,0,0 c8.1,0,14.7-2.6,21.5-8.4c0.5-0.5,0.9-1.1,0.9-1.8c0-0.6-0.2-1.1-0.5-1.5l-1.4-1.8l-0.1-0.1c-0.3-0.3-0.7-0.6-1.4-0.6 c-0.6,0-1.3,0.2-1.7,0.6c-1.9,1.6-3.5,2.8-4.8,3.6c-1.8,1.2-5.8,3.2-12.6,3.2c0,0,0,0,0,0c-0.1,0-0.3,0-0.4,0 c-12.3,0-23.5-10.3-24.5-22.5l53,0c0,0,0,0,0,0c2.2,0,3.5-1.2,3.5-3.3c0-5.3-1.4-10.4-4-15.3C155,84.1,151.2,80.1,146.5,77.2z M154.6,100.4l-49.1,0c2-11.7,13-21.2,24.5-21c0.1,0,0.3,0,0.4,0C143.5,79.4,152.5,90,154.6,100.4z"/>
|
||||
<path class="st0" d="M204,136.6c5.7,0,11.1-1.5,16-4.3c4.9-2.9,8.8-6.7,11.7-11.7c2.9-4.9,4.3-10.3,4.3-16c0-5.7-1.5-11.1-4.3-16 c-2.9-4.9-6.7-8.8-11.7-11.7c-4.9-2.9-10.2-4.3-16-4.3c-9.9,0-19.2,4.8-24.4,12.3v-9.4c0-0.6-0.1-1.1-0.4-1.5 c-0.4-0.5-1-0.8-1.9-0.8h-2.7c-1.5,0-2.3,0.8-2.3,2.3v5.7v46.5v24c0,1.3,0.8,2,2,2h3.4c0.9,0,1.6-0.7,1.6-1.6v-17.1v-11 C185.1,131.9,194.3,136.6,204,136.6z M186.6,122.6c-4.9-4.9-7.7-11.5-7.5-18c-0.1-6.4,2.5-12.8,7.4-17.6c4.9-4.9,11.3-7.6,17.7-7.4 c4.4,0,8.5,1.1,12.2,3.4c3.7,2.3,6.7,5.4,8.9,9.2c2.2,3.9,3.3,8.1,3.3,12.5c0.1,6.5-2.6,13-7.5,17.9c-4.8,4.8-11,7.4-17.3,7.3 C197.7,130,191.4,127.3,186.6,122.6z"/>
|
||||
<path class="st0" d="M241.9,80.4h7.6v42c0,6.8,4.6,12.1,11.1,12.9c0.7,0.1,1.8,0.2,3.5,0.2c2,0,3-0.4,3-2.3v-2.4c0-1.8-1.3-2-2.8-2 c0,0,0,0,0,0l-1.8,0c-2,0-3.5-0.8-4.4-2.1c-1-1.5-1.5-3.2-1.5-5V80.4h10.1c1.3,0,2.3-1.4,2.3-2.6v-1.9c0-1.4-0.8-2.2-2.2-2.2h-10.1 v-13c0-1.5-0.8-2.5-1.9-2.5c-0.2,0-0.4,0-0.6,0.1l-2.6,0.7c-1.2,0.4-2,0.9-2,2.5v12.2h-7.6c-1.4,0-2.3,0.9-2.3,2.3v1.9 C239.6,79.2,240.6,80.4,241.9,80.4z"/>
|
||||
<path class="st0" d="M379.1,106.3c-4-2.9-8.9-5-14.3-6.3c-6.5-1.4-13.4-5.2-13.3-10.3c0-7.5,6.6-10.2,12.3-10.2c5.4,0,10.2,3,13,8 c0.7,1.3,1.5,1.9,2.4,1.9c0.3,0,0.7-0.1,1-0.3l2.2-1.2c0.6-0.3,0.9-0.9,0.9-1.7c0-0.5-0.1-1-0.3-1.3c-3.6-8.2-10.1-12.4-19.2-12.4 c-11.7,0-19.6,6.9-19.6,17.2c0,4.3,1.8,8,5.5,10.9c3.4,2.8,7.9,4.9,13.3,6.1c4.6,1.1,8.3,2.6,11.1,4.4c1.4,0.9,2.4,2.1,3.1,3.4 c0.6,1.2,0.9,2.6,0.9,4.1c0.1,2.4-0.8,4.5-2.6,6.3c-3.1,3.2-8.4,4.7-12,4.7c0,0-0.1,0-0.1,0c-3.4,0-6.5-0.8-9.1-2.5 c-2.6-1.6-4.2-3.8-5-6.6c-0.3-1.1-0.8-2.2-2.2-2.2c-0.1,0-0.2,0-0.4,0c-0.1,0-0.3,0-0.5,0.1l-1.9,0.4l-0.1,0c-1,0.4-1.6,1.2-1.6,2.1 c0,0.2,0.1,0.7,0.2,1.1c2,8.6,10.2,14.3,20.6,14.3c0.1,0,0.2,0,0.4,0c6.6,0,12.7-2.3,16.7-6.4c3.2-3.2,4.8-7.2,4.7-11.6 c0-2.8-0.6-5.3-1.8-7.5C382.4,109.3,381,107.6,379.1,106.3z"/>
|
||||
<path class="st1" d="M348.9,45.7c0-6.3-3.3-11.9-8.2-15.1c-2.8-1.8-6.2-2.9-9.7-2.9c-9.9,0-18,8.1-18,18c0,4.8,1.9,9.1,4.9,12.3 c-3.7,4.4-6.6,9.3-8.8,14.7c-1.6-0.2-3.2-0.4-4.9-0.4c-17.7,0-32.1,14.4-32.1,32.1c0,14.7,9.9,27.1,23.4,30.9 c-6.8,17.3-23.7,29.7-43.4,29.7c-16.8,0-31.6-9-39.8-22.4c-2.5,0.5-5,0.9-7.7,0.9c9,17,26.9,28.6,47.4,28.6 c23.3,0,43.1-14.9,50.5-35.6c0.5,0,1,0,1.6,0c17.7,0,32.1-14.4,32.1-32.1c0-13.6-8.4-25.2-20.3-29.9c1.9-4.6,4.5-8.8,7.7-12.5 c1,0.4,2,0.8,3.1,1c1.3,0.3,2.7,0.5,4.1,0.5C340.8,63.7,348.9,55.6,348.9,45.7z M304.2,129.5c-13.8,0-25-11.2-25-25s11.2-25,25-25 s25,11.2,25,25S318,129.5,304.2,129.5z M330.9,34.9c0.6,0,1.3,0.1,1.9,0.2c5.1,0.9,9,5.3,9,10.7c0,6-4.9,10.8-10.8,10.8 s-10.8-4.9-10.8-10.8c0-1.4,0.3-2.7,0.8-4C322.4,37.7,326.4,34.9,330.9,34.9z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
4
examples/counter/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
BIN
examples/counter/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -17,7 +17,7 @@ pub fn SimpleCounter(
|
||||
<div>
|
||||
<button on:click=move |_| set_value(0)>"Clear"</button>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= step)>"-1"</button>
|
||||
<span>"Value: " {move || value().to_string()} "!"</span>
|
||||
<span>"Value: " {value} "!"</span>
|
||||
<button on:click=move |_| set_value.update(|value| *value += step)>"+1"</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -23,12 +23,11 @@ leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_meta = { path = "../../meta", default-features = false }
|
||||
leptos_router = { path = "../../router", default-features = false }
|
||||
log = "0.4"
|
||||
simple_logger = "2"
|
||||
simple_logger = "4.0.0"
|
||||
gloo-net = { git = "https://github.com/rustwasm/gloo" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
@@ -41,31 +40,31 @@ ssr = [
|
||||
stable = ["leptos/stable", "leptos_router/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix", "stable"]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
output-name = "counter_isomorphic"
|
||||
output-name = "counter_isomorphic"
|
||||
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
|
||||
site-root = "target/site"
|
||||
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
# style-file = "src/styles/tailwind.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
# assets-dir = "static/assets"
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-address = "127.0.0.1:3000"
|
||||
site-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
reload-port = 3001
|
||||
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
watch = false
|
||||
watch = false
|
||||
# The environment Leptos will run in, usually either "DEV" or "PROD"
|
||||
env = "DEV"
|
||||
# The features to use when compiling the bin target
|
||||
@@ -86,4 +85,4 @@ lib-features = ["hydrate"]
|
||||
# If the --no-default-features flag should be used when compiling the lib target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
lib-default-features = false
|
||||
lib-default-features = false
|
||||
|
||||
4
examples/counter_isomorphic/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -25,7 +25,7 @@ cargo leptos build --release
|
||||
## Server Side Rendering without cargo-leptos
|
||||
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
|
||||
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time.
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes.
|
||||
1. Install wasm-pack
|
||||
```bash
|
||||
cargo install wasm-pack
|
||||
|
||||
BIN
examples/counter_isomorphic/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -1,5 +1,6 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use leptos_meta::*;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
@@ -44,6 +45,7 @@ pub async fn clear_server_count() -> Result<i32, ServerFnError> {
|
||||
}
|
||||
#[component]
|
||||
pub fn Counters(cx: Scope) -> impl IntoView {
|
||||
provide_meta_context(cx);
|
||||
view! {
|
||||
cx,
|
||||
<Router>
|
||||
@@ -59,6 +61,7 @@ pub fn Counters(cx: Scope) -> impl IntoView {
|
||||
<li><A href="multi">"Multi-User"</A></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=|cx| view! {
|
||||
@@ -113,9 +116,10 @@ pub fn Counter(cx: Scope) -> impl IntoView {
|
||||
<div>
|
||||
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
|
||||
<button on:click=move |_| dec.dispatch(())>"-1"</button>
|
||||
<span>"Value: " {move || value().to_string()} "!"</span>
|
||||
<span>"Value: " {value} "!"</span>
|
||||
<button on:click=move |_| inc.dispatch(())>"+1"</button>
|
||||
</div>
|
||||
{move || error_msg().map(|msg| view! { cx, <p>"Error: " {msg.to_string()}</p>})}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -208,8 +212,8 @@ pub fn MultiuserCounter(cx: Scope) -> impl IntoView {
|
||||
};
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
let multiplayer_value =
|
||||
create_signal_from_stream(cx, futures::stream::once(Box::pin(async { 0.to_string() })));
|
||||
let (multiplayer_value, _) =
|
||||
create_signal(cx, None::<i32>);
|
||||
|
||||
view! {
|
||||
cx,
|
||||
|
||||
@@ -9,6 +9,7 @@ cfg_if! {
|
||||
use actix_files::{Files};
|
||||
use actix_web::*;
|
||||
use crate::counters::*;
|
||||
use leptos_actix::{generate_route_list, LeptosRoutes};
|
||||
|
||||
#[get("/api/events")]
|
||||
async fn counter_events() -> impl Responder {
|
||||
@@ -29,23 +30,25 @@ cfg_if! {
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
|
||||
crate::counters::register_server_functions();
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars.
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
let routes = generate_route_list(|cx| view! { cx, <Counters/> });
|
||||
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
|
||||
App::new()
|
||||
.service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos
|
||||
.service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run.
|
||||
.service(counter_events)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <Counters/> }))
|
||||
//.wrap(middleware::Compress::default())
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, <Counters/> })
|
||||
.service(Files::new("/", &site_root))
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(&addr)?
|
||||
.run()
|
||||
@@ -55,7 +58,7 @@ cfg_if! {
|
||||
|
||||
// client-only stuff for Trunk
|
||||
else {
|
||||
use leptos_counter_isomorphic::counters::*;
|
||||
use counter_isomorphic::counters::*;
|
||||
|
||||
pub fn main() {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
|
||||
4
examples/counters/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -97,10 +97,10 @@ fn Counter(
|
||||
<li>
|
||||
<button on:click=move |_| set_value.update(move |value| *value -= 1)>"-1"</button>
|
||||
<input type="text"
|
||||
prop:value={move || value().to_string()}
|
||||
prop:value={value}
|
||||
on:input=input
|
||||
/>
|
||||
<span>{move || value().to_string()}</span>
|
||||
<span>{value}</span>
|
||||
<button on:click=move |_| set_value.update(move |value| *value += 1)>"+1"</button>
|
||||
<button on:click=move |_| set_counters.update(move |counters| counters.retain(|(counter_id, _)| counter_id != &id))>"x"</button>
|
||||
</li>
|
||||
|
||||
4
examples/counters_stable/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+stable", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
4
examples/fetch/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
</head>
|
||||
<style>
|
||||
img {
|
||||
|
||||
BIN
examples/fetch/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -19,7 +19,7 @@ leptos_meta = { path = "../../meta", default-features = false }
|
||||
leptos_actix = { path = "../../integrations/actix", default-features = false, optional = true }
|
||||
leptos_router = { path = "../../router", default-features = false }
|
||||
log = "0.4"
|
||||
simple_logger = "2"
|
||||
simple_logger = "4.0.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
@@ -56,7 +56,7 @@ site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "./style.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
# assets-dir = "static/assets"
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
4
examples/hackernews/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -25,7 +25,7 @@ cargo leptos build --release
|
||||
## Server Side Rendering without cargo-leptos
|
||||
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
|
||||
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time.
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes..
|
||||
1. Install wasm-pack
|
||||
```bash
|
||||
cargo install wasm-pack
|
||||
|
||||
BIN
examples/hackernews/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -1,4 +1,4 @@
|
||||
use leptos::{on_cleanup, Scope, Serializable};
|
||||
use leptos::{Scope, Serializable};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn story(path: &str) -> String {
|
||||
@@ -29,7 +29,7 @@ where
|
||||
|
||||
// abort in-flight requests if the Scope is disposed
|
||||
// i.e., if we've navigated away from this page
|
||||
on_cleanup(cx, move || {
|
||||
leptos::on_cleanup(cx, move || {
|
||||
if let Some(abort_controller) = abort_controller {
|
||||
abort_controller.abort()
|
||||
}
|
||||
@@ -38,7 +38,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn fetch_api<T>(cx: Scope, path: &str) -> Option<T>
|
||||
pub async fn fetch_api<T>(_cx: Scope, path: &str) -> Option<T>
|
||||
where
|
||||
T: Serializable,
|
||||
{
|
||||
|
||||
@@ -15,7 +15,8 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
view! {
|
||||
cx,
|
||||
<>
|
||||
<Stylesheet id="leptos" href="./target/site/pkg/hackernews.css"/>
|
||||
<Stylesheet id="leptos" href="/pkg/hackernews.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
|
||||
<Router>
|
||||
<Nav />
|
||||
@@ -23,7 +24,7 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
<Routes>
|
||||
<Route path="users/:id" view=|cx| view! { cx, <User/> }/>
|
||||
<Route path="stories/:id" view=|cx| view! { cx, <Story/> }/>
|
||||
<Route path="*stories" view=|cx| view! { cx, <Stories/> }/>
|
||||
<Route path=":stories?" view=|cx| view! { cx, <Stories/> }/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -8,28 +8,36 @@ cfg_if! {
|
||||
use actix_files::{Files};
|
||||
use actix_web::*;
|
||||
use hackernews::{App,AppProps};
|
||||
use leptos_actix::{LeptosRoutes, generate_route_list};
|
||||
|
||||
#[get("/style.css")]
|
||||
async fn css() -> impl Responder {
|
||||
actix_files::NamedFile::open_async("./style.css").await
|
||||
}
|
||||
#[get("/favicon.ico")]
|
||||
async fn favicon() -> impl Responder {
|
||||
actix_files::NamedFile::open_async("./target/site//favicon.ico").await
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars.
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> });
|
||||
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
|
||||
App::new()
|
||||
.service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos
|
||||
.service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run.
|
||||
.service(css)
|
||||
.service(favicon)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <App/> }))
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, <App/> })
|
||||
.service(Files::new("/", &site_root))
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(&addr)?
|
||||
|
||||
@@ -134,7 +134,7 @@ fn Story(cx: Scope, story: api::Story) -> impl IntoView {
|
||||
view! { cx,
|
||||
<span>
|
||||
{"by "}
|
||||
{story.user.map(|user| view ! { cx, <A href=format!("/users/{}", user)>{user.clone()}</A>})}
|
||||
{story.user.map(|user| view ! { cx, <A href=format!("/users/{user}")>{user.clone()}</A>})}
|
||||
{format!(" {} | ", story.time_ago)}
|
||||
<A href=format!("/stories/{}", story.id)>
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
}
|
||||
},
|
||||
);
|
||||
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title.clone())).unwrap_or_else(|| "Loading story...".to_string());
|
||||
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
@@ -37,7 +37,7 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
{story.user.map(|user| view! { cx, <p class="meta">
|
||||
{story.points}
|
||||
" points | by "
|
||||
<A href=format!("/users/{}", user)>{user.clone()}</A>
|
||||
<A href=format!("/users/{user}")>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>})}
|
||||
</div>
|
||||
|
||||
@@ -64,7 +64,7 @@ site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "./style.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
# assets-dir = "static/assets"
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
4
examples/hackernews_axum/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -25,7 +25,7 @@ cargo leptos build --release
|
||||
## Server Side Rendering without cargo-leptos
|
||||
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
|
||||
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time.
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes..
|
||||
1. Install wasm-pack
|
||||
```bash
|
||||
cargo install wasm-pack
|
||||
|
||||
BIN
examples/hackernews_axum/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
41
examples/hackernews_axum/src/file.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::Extension,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
use std::sync::Arc;
|
||||
use leptos::LeptosOptions;
|
||||
|
||||
pub async fn file_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
|
||||
let options = &*options;
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await?;
|
||||
|
||||
match res.status() {
|
||||
StatusCode::OK => Ok(res),
|
||||
_ => Err((res.status(), "File Not Found".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap();
|
||||
let root_path = format!("{root}");
|
||||
// `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_path).oneshot(req).await {
|
||||
Ok(res) => Ok(res.map(boxed)),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", err),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use leptos::{component, view, IntoView, Scope};
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
mod api;
|
||||
pub mod file;
|
||||
pub mod handlers;
|
||||
mod routes;
|
||||
use routes::nav::*;
|
||||
@@ -16,7 +17,8 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
view! {
|
||||
cx,
|
||||
<>
|
||||
<Stylesheet id="leptos" href="./target/site/pkg/hackernews_axum.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Stylesheet id="leptos" href="/pkg/hackernews_axum.css"/>
|
||||
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
|
||||
<Router>
|
||||
<Nav />
|
||||
@@ -24,7 +26,7 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
<Routes>
|
||||
<Route path="users/:id" view=|cx| view! { cx, <User/> }/>
|
||||
<Route path="stories/:id" view=|cx| view! { cx, <Story/> }/>
|
||||
<Route path="*stories" view=|cx| view! { cx, <Stories/> }/>
|
||||
<Route path=":stories?" view=|cx| view! { cx, <Stories/> }/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -6,10 +6,12 @@ cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
Router,
|
||||
error_handling::HandleError,
|
||||
routing::get,
|
||||
extract::Extension,
|
||||
};
|
||||
use http::StatusCode;
|
||||
use tower_http::services::ServeDir;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use std::sync::Arc;
|
||||
use hackernews_axum::file::file_handler;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@@ -17,40 +19,17 @@ if #[cfg(feature = "ssr")] {
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
|
||||
// The URL path of the generated JS/WASM bundle from cargo-leptos
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
// The filesystem path of the generated JS/WASM bundle from cargo-leptos
|
||||
let bundle_filepath = format!("./{site_root}/{pkg_dir}");
|
||||
let addr = leptos_options.site_address.clone();
|
||||
log::debug!("serving at {addr}");
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> }).await;
|
||||
|
||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||
|
||||
// These are Tower Services that will serve files from the static and pkg repos.
|
||||
// HandleError is needed as Axum requires services to implement Infallible Errors
|
||||
// because all Errors are converted into Responses
|
||||
let static_service = HandleError::new( ServeDir::new("./static"), handle_file_error);
|
||||
let pkg_service =HandleError::new( ServeDir::new("./pkg"), handle_file_error);
|
||||
let cargo_leptos_service = HandleError::new( ServeDir::new(&bundle_filepath), handle_file_error);
|
||||
|
||||
/// Convert the Errors from ServeDir to a type that implements IntoResponse
|
||||
async fn handle_file_error(err: std::io::Error) -> (StatusCode, String) {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
format!("File Not Found: {}", err),
|
||||
)
|
||||
}
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
// `GET /` goes to `root`
|
||||
.nest_service("/pkg", pkg_service) // Only need if using wasm-pack. Can be deleted if using cargo-leptos
|
||||
.nest_service(&bundle_path, cargo_leptos_service) // Only needed if using cargo-leptos. Can be deleted if using wasm-pack and cargo-run
|
||||
.nest_service("/static", static_service)
|
||||
.fallback(leptos_axum::render_app_to_stream(leptos_options, |cx| view! { cx, <App/> }));
|
||||
.route("/favicon.ico", get(file_handler))
|
||||
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <App/> } )
|
||||
.fallback(file_handler)
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
||||
@@ -134,7 +134,7 @@ fn Story(cx: Scope, story: api::Story) -> impl IntoView {
|
||||
view! { cx,
|
||||
<span>
|
||||
{"by "}
|
||||
{story.user.map(|user| view ! { cx, <A href=format!("/users/{}", user)>{user.clone()}</A>})}
|
||||
{story.user.map(|user| view ! { cx, <A href=format!("/users/{user}")>{user.clone()}</A>})}
|
||||
{format!(" {} | ", story.time_ago)}
|
||||
<A href=format!("/stories/{}", story.id)>
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
}
|
||||
},
|
||||
);
|
||||
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title.clone())).unwrap_or_else(|| "Loading story...".to_string());
|
||||
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
@@ -37,7 +37,7 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
{story.user.map(|user| view! { cx, <p class="meta">
|
||||
{story.points}
|
||||
" points | by "
|
||||
<A href=format!("/users/{}", user)>{user.clone()}</A>
|
||||
<A href=format!("/users/{user}")>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>})}
|
||||
</div>
|
||||
|
||||
@@ -1,326 +0,0 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
font-size: 15px;
|
||||
background-color: #f2f3f5;
|
||||
margin: 0;
|
||||
padding-top: 55px;
|
||||
color: #34495e;
|
||||
overflow-y: scroll
|
||||
}
|
||||
|
||||
a {
|
||||
color: #34495e;
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #335d92;
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
height: 55px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0
|
||||
}
|
||||
|
||||
.header .inner {
|
||||
max-width: 800px;
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto;
|
||||
padding: 15px 5px
|
||||
}
|
||||
|
||||
.header a {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
line-height: 24px;
|
||||
transition: color .15s ease;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-weight: 300;
|
||||
letter-spacing: .075em;
|
||||
margin-right: 1.8em
|
||||
}
|
||||
|
||||
.header a:hover {
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.header a.active {
|
||||
color: #fff;
|
||||
font-weight: 400
|
||||
}
|
||||
|
||||
.header a:nth-child(6) {
|
||||
margin-right: 0
|
||||
}
|
||||
|
||||
.header .github {
|
||||
color: #fff;
|
||||
font-size: .9em;
|
||||
margin: 0;
|
||||
float: right
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 24px;
|
||||
margin-right: 10px;
|
||||
display: inline-block;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
.view {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-exit-active {
|
||||
transition: all .2s ease
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-exit-active {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
@media (max-width:860px) {
|
||||
.header .inner {
|
||||
padding: 15px 30px
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:600px) {
|
||||
.header .inner {
|
||||
padding: 15px
|
||||
}
|
||||
|
||||
.header a {
|
||||
margin-right: 1em
|
||||
}
|
||||
|
||||
.header .github {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
|
||||
.news-view {
|
||||
padding-top: 45px
|
||||
}
|
||||
|
||||
.news-list,
|
||||
.news-list-nav {
|
||||
background-color: #fff;
|
||||
border-radius: 2px
|
||||
}
|
||||
|
||||
.news-list-nav {
|
||||
padding: 15px 30px;
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
top: 55px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 998;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .1)
|
||||
}
|
||||
|
||||
.news-list-nav .page-link {
|
||||
margin: 0 1em
|
||||
}
|
||||
|
||||
.news-list-nav .disabled {
|
||||
color: #aaa
|
||||
}
|
||||
|
||||
.news-list {
|
||||
position: absolute;
|
||||
margin: 30px 0;
|
||||
width: 100%;
|
||||
transition: all .5s cubic-bezier(.55, 0, .1, 1)
|
||||
}
|
||||
|
||||
.news-list ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
@media (max-width:600px) {
|
||||
.news-list {
|
||||
margin: 10px 0
|
||||
}
|
||||
}
|
||||
|
||||
.news-item {
|
||||
background-color: #fff;
|
||||
padding: 20px 30px 20px 80px;
|
||||
border-bottom: 1px solid #eee;
|
||||
position: relative;
|
||||
line-height: 20px
|
||||
}
|
||||
|
||||
.news-item .score {
|
||||
color: #335d92;
|
||||
font-size: 1.1em;
|
||||
font-weight: 700;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
margin-top: -10px
|
||||
}
|
||||
|
||||
.news-item .host,
|
||||
.news-item .meta {
|
||||
font-size: .85em;
|
||||
color: #626262
|
||||
}
|
||||
|
||||
.news-item .host a,
|
||||
.news-item .meta a {
|
||||
color: #626262;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.news-item .host a:hover,
|
||||
.news-item .meta a:hover {
|
||||
color: #335d92
|
||||
}
|
||||
|
||||
.item-view-header {
|
||||
background-color: #fff;
|
||||
padding: 1.8em 2em 1em;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .1)
|
||||
}
|
||||
|
||||
.item-view-header h1 {
|
||||
display: inline;
|
||||
font-size: 1.5em;
|
||||
margin: 0;
|
||||
margin-right: .5em
|
||||
}
|
||||
|
||||
.item-view-header .host,
|
||||
.item-view-header .meta,
|
||||
.item-view-header .meta a {
|
||||
color: #626262
|
||||
}
|
||||
|
||||
.item-view-header .meta a {
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.item-view-comments {
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
padding: 0 2em .5em
|
||||
}
|
||||
|
||||
.item-view-comments-header {
|
||||
margin: 0;
|
||||
font-size: 1.1em;
|
||||
padding: 1em 0;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.item-view-comments-header .spinner {
|
||||
display: inline-block;
|
||||
margin: -15px 0
|
||||
}
|
||||
|
||||
.comment-children {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
@media (max-width:600px) {
|
||||
.item-view-header h1 {
|
||||
font-size: 1.25em
|
||||
}
|
||||
}
|
||||
|
||||
.comment-children .comment-children {
|
||||
margin-left: 1.5em
|
||||
}
|
||||
|
||||
.comment {
|
||||
border-top: 1px solid #eee;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.comment .by,
|
||||
.comment .text,
|
||||
.comment .toggle {
|
||||
font-size: .9em;
|
||||
margin: 1em 0
|
||||
}
|
||||
|
||||
.comment .by {
|
||||
color: #626262
|
||||
}
|
||||
|
||||
.comment .by a {
|
||||
color: #626262;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.comment .text {
|
||||
overflow-wrap: break-word
|
||||
}
|
||||
|
||||
.comment .text a:hover {
|
||||
color: #335d92
|
||||
}
|
||||
|
||||
.comment .text pre {
|
||||
white-space: pre-wrap
|
||||
}
|
||||
|
||||
.comment .toggle {
|
||||
background-color: #fffbf2;
|
||||
padding: .3em .5em;
|
||||
border-radius: 4px
|
||||
}
|
||||
|
||||
.comment .toggle a {
|
||||
color: #626262;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.comment .toggle.open {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
margin-bottom: -.5em
|
||||
}
|
||||
|
||||
.user-view {
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
padding: 2em 3em
|
||||
}
|
||||
|
||||
.user-view h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5em
|
||||
}
|
||||
|
||||
.user-view .meta {
|
||||
list-style-type: none;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.user-view .label {
|
||||
display: inline-block;
|
||||
min-width: 4em
|
||||
}
|
||||
|
||||
.user-view .about {
|
||||
margin: 1em 0
|
||||
}
|
||||
|
||||
.user-view .links a {
|
||||
text-decoration: underline
|
||||
}
|
||||
4
examples/parent_child/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
<style>
|
||||
.red {
|
||||
color: red;
|
||||
|
||||
BIN
examples/parent_child/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -7,11 +7,11 @@ edition = "2021"
|
||||
console_log = "0.2"
|
||||
log = "0.4"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_router = { path = "../../router", features=["csr"] }
|
||||
leptos_router = { path = "../../router", features = ["csr"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
futures = "0.3"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos_meta = { path = "../../meta", default-features = false }
|
||||
leptos_meta = { path = "../../meta", features = ["csr"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
4
examples/router/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
<style>
|
||||
a[aria-current] {
|
||||
font-weight: bold;
|
||||
|
||||
BIN
examples/router/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -12,6 +12,11 @@ pub fn RouterExample(cx: Scope) -> impl IntoView {
|
||||
view! { cx,
|
||||
<Router>
|
||||
<nav>
|
||||
// ordinary <a> elements can be used for client-side navigation
|
||||
// using <A> has two effects:
|
||||
// 1) ensuring that relative routing works properly for nested routes
|
||||
// 2) setting the `aria-current` attribute on the current link,
|
||||
// for a11y and styling purposes
|
||||
<A exact=true href="/">"Contacts"</A>
|
||||
<A href="about">"About"</A>
|
||||
<A href="settings">"Settings"</A>
|
||||
@@ -105,12 +110,15 @@ pub fn Contact(cx: Scope) -> impl IntoView {
|
||||
// Some(None) => has loaded and found no contact
|
||||
Some(None) => Some(view! { cx, <p>"No contact with this ID was found."</p> }.into_any()),
|
||||
// Some(Some) => has loaded and found a contact
|
||||
Some(Some(contact)) => Some(view! { cx,
|
||||
<section class="card">
|
||||
<h1>{contact.first_name} " " {contact.last_name}</h1>
|
||||
<p>{contact.address_1}<br/>{contact.address_2}</p>
|
||||
</section>
|
||||
}.into_any()),
|
||||
Some(Some(contact)) => Some(
|
||||
view! { cx,
|
||||
<section class="card">
|
||||
<h1>{contact.first_name} " " {contact.last_name}</h1>
|
||||
<p>{contact.address_1}<br/>{contact.address_2}</p>
|
||||
</section>
|
||||
}
|
||||
.into_any(),
|
||||
),
|
||||
};
|
||||
|
||||
view! { cx,
|
||||
@@ -125,9 +133,18 @@ pub fn Contact(cx: Scope) -> impl IntoView {
|
||||
#[component]
|
||||
pub fn About(cx: Scope) -> impl IntoView {
|
||||
log::debug!("rendering <About/>");
|
||||
// use_navigate allows you to navigate programmatically by calling a function
|
||||
let navigate = use_navigate(cx);
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
// note: this is just an illustration of how to use `use_navigate`
|
||||
// <button on:click> to navigate is an *anti-pattern*
|
||||
// you should ordinarily use a link instead,
|
||||
// both semantically and so your link will work before WASM loads
|
||||
<button on:click=move |_| { _ = navigate("/", Default::default()); }>
|
||||
"Home"
|
||||
</button>
|
||||
<h1>"About"</h1>
|
||||
<p>"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."</p>
|
||||
</>
|
||||
|
||||
@@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", default-features = false, features = [
|
||||
"serde",
|
||||
"serde",
|
||||
] }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_meta = { path = "../../meta", default-features = false }
|
||||
@@ -30,10 +30,6 @@ actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", features = ["macros"], optional = true }
|
||||
futures = { version = "0.3", optional = true }
|
||||
simple_logger = { version = "4.0", optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
reqwest = { version = "0.11", features = ["json"], optional = true }
|
||||
|
||||
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
@@ -57,17 +53,24 @@ ssr = [
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:reqwest",
|
||||
"dep:leptos_actix",
|
||||
"dep:actix-web",
|
||||
"dep:actix-files",
|
||||
"dep:futures",
|
||||
"dep:simple_logger",
|
||||
"dep:serde_json",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix"]
|
||||
denylist = [
|
||||
"actix-files",
|
||||
"actix-web",
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"futures",
|
||||
"leptos_actix",
|
||||
"simple_logger",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[profile.release]
|
||||
@@ -77,26 +80,26 @@ opt-level = 'z'
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
output-name = "tailwind"
|
||||
output-name = "tailwind"
|
||||
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
|
||||
site-root = "target/site"
|
||||
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
# style-file = "src/styles/tailwind.css"
|
||||
style-file = "style/output.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
assets-dir = "assets"
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-address = "127.0.0.1:3000"
|
||||
site-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
reload-port = 3001
|
||||
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
|
||||
end2end-cmd = "npx playwright test"
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
|
||||
watch = false
|
||||
watch = false
|
||||
# The environment Leptos will run in, usually either "DEV" or "PROD"
|
||||
env = "DEV"
|
||||
# The features to use when compiling the bin target
|
||||
|
||||
4
examples/tailwind/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -1,6 +1,6 @@
|
||||
# Leptos Starter Template
|
||||
|
||||
This is a template demonstrating how to integrate [TailwindCSS](https://tailwindcss.com/) with the [Leptos](https://github.com/gbj/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
|
||||
This is a template demonstrating how to integrate [TailwindCSS](https://tailwindcss.com/) with the [Leptos](https://github.com/leptos-rs/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
|
||||
|
||||
If you don't have `cargo-leptos` installed you can install it with
|
||||
|
||||
@@ -52,13 +52,11 @@ If you're using VS Code, add the following to your `settings.json`
|
||||
"css.validate": false,
|
||||
```
|
||||
|
||||
|
||||
Install [Tailwind CSS Intellisense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss).
|
||||
|
||||
Install "VS Browser" extension, a browser at the right window.
|
||||
Allow vscode Ports forward: 3000, 3001.
|
||||
|
||||
|
||||
## Notes about Tooling
|
||||
|
||||
By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
|
||||
@@ -72,8 +70,8 @@ By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If
|
||||
## Alternatives to cargo-leptos
|
||||
|
||||
This crate can be run without `cargo-leptos`, using `wasm-pack` and `cargo`. To do so, you'll need to install some other tools.
|
||||
|
||||
1. `cargo install wasm-pack`
|
||||
0. `cargo install wasm-pack`
|
||||
1. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes.
|
||||
|
||||
### Server Side Rendering With Hydration
|
||||
|
||||
@@ -91,15 +89,16 @@ Then run the server with `cargo run` to serve the server side rendered HTML and
|
||||
cargo run --no-default-features --features=ssr
|
||||
```
|
||||
|
||||
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above before running
|
||||
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above before running
|
||||
> `cargo run`
|
||||
|
||||
### Client Side Rendering
|
||||
|
||||
You'll need to install trunk to client side render this bundle.
|
||||
|
||||
1. `cargo install trunk`
|
||||
Then the site can be served with `trunk serve --open`
|
||||
Then the site can be served with `trunk serve --open`
|
||||
|
||||
## Attribution
|
||||
|
||||
Many thanks to GreatGreg for putting together this guide. You can find the original, with added details, [here](https://github.com/gbj/leptos/discussions/125).
|
||||
Many thanks to GreatGreg for putting together this guide. You can find the original, with added details, [here](https://github.com/leptos-rs/leptos/discussions/125).
|
||||
|
||||
|
Before Width: | Height: | Size: 101 KiB |
BIN
examples/tailwind/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -1,28 +1,37 @@
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn App(cx: Scope) -> impl IntoView {
|
||||
provide_meta_context(cx);
|
||||
|
||||
let (count, set_count) = create_signal(cx, 0);
|
||||
|
||||
|
||||
view! {
|
||||
cx,
|
||||
<main class="my-0 mx-auto max-w-3xl text-center">
|
||||
<Stylesheet id="leptos" href="/style.css"/>
|
||||
<h2 class="p-6 text-4xl">"Welcome to Leptos with Tailwind"</h2>
|
||||
<p class="px-10 pb-10 text-left">"Tailwind will scan your Rust files for Tailwind class names and compile them into a CSS file."</p>
|
||||
<button
|
||||
class="bg-sky-600 hover:bg-sky-700 px-5 py-3 text-white rounded-lg"
|
||||
on:click=move |_| set_count.update(|count| *count += 1)
|
||||
>
|
||||
{move || if count() == 0 {
|
||||
"Click me!".to_string()
|
||||
} else {
|
||||
count().to_string()
|
||||
}}
|
||||
</button>
|
||||
</main>
|
||||
<Stylesheet id="leptos" href="/pkg/tailwind.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="" view= move |cx| view! {
|
||||
cx,
|
||||
<main class="my-0 mx-auto max-w-3xl text-center">
|
||||
<h2 class="p-6 text-4xl">"Welcome to Leptos with Tailwind"</h2>
|
||||
<p class="px-10 pb-10 text-left">"Tailwind will scan your Rust files for Tailwind class names and compile them into a CSS file."</p>
|
||||
<button
|
||||
class="bg-sky-600 hover:bg-sky-700 px-5 py-3 text-white rounded-lg"
|
||||
on:click=move |_| set_count.update(|count| *count += 1)
|
||||
>
|
||||
{move || if count() == 0 {
|
||||
"Click me!".to_string()
|
||||
} else {
|
||||
count().to_string()
|
||||
}}
|
||||
</button>
|
||||
</main>
|
||||
}/>
|
||||
</Routes>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
mod app;
|
||||
#[cfg(feature = "ssr")]
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
@@ -8,6 +7,7 @@ cfg_if! {
|
||||
use actix_web::*;
|
||||
use leptos::*;
|
||||
use crate::app::*;
|
||||
use leptos_actix::{generate_route_list, LeptosRoutes};
|
||||
|
||||
#[get("/style.css")]
|
||||
async fn css() -> impl Responder {
|
||||
@@ -16,20 +16,23 @@ cfg_if! {
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars.
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> });
|
||||
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let routes = &routes;
|
||||
App::new()
|
||||
.service(css)
|
||||
.service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos
|
||||
.service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run.
|
||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <App/> }))
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, <App/> })
|
||||
.service(Files::new("/", &site_root))
|
||||
.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(&addr)?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com
|
||||
! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -30,7 +30,6 @@
|
||||
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.
|
||||
*/
|
||||
|
||||
html {
|
||||
@@ -45,8 +44,6 @@ html {
|
||||
/* 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 */
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -413,13 +410,54 @@ video {
|
||||
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-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: ;
|
||||
}
|
||||
|
||||
*, ::before, ::after {
|
||||
::-webkit-backdrop {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
|
||||
@@ -58,7 +58,7 @@ site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "./style.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
# assets-dir = "static/assets"
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
4
examples/todo_app_sqlite/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -25,7 +25,7 @@ cargo leptos build --release
|
||||
## Server Side Rendering without cargo-leptos
|
||||
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
|
||||
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS file is no longer processed by cargo-leptos. Building to alternative folders is not supported at this time
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes.
|
||||
1. Install wasm-pack
|
||||
```bash
|
||||
cargo install wasm-pack
|
||||
|
||||
BIN
examples/todo_app_sqlite/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -9,6 +9,7 @@ cfg_if! {
|
||||
use actix_files::{Files};
|
||||
use actix_web::*;
|
||||
use crate::todo::*;
|
||||
use leptos_actix::{generate_route_list, LeptosRoutes};
|
||||
|
||||
#[get("/style.css")]
|
||||
async fn css() -> impl Responder {
|
||||
@@ -25,23 +26,25 @@ cfg_if! {
|
||||
|
||||
crate::todo::register_server_functions();
|
||||
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars.
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> });
|
||||
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
let routes = &routes;
|
||||
|
||||
App::new()
|
||||
.service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos
|
||||
.service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run.
|
||||
.service(css)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <TodoApp/> }))
|
||||
//.wrap(middleware::Compress::default())
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, <TodoApp/> })
|
||||
.service(Files::new("/", &site_root))
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(addr)?
|
||||
.run()
|
||||
|
||||
@@ -38,9 +38,11 @@ cfg_if! {
|
||||
pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
|
||||
// this is just an example of how to access server context injected in the handlers
|
||||
let req =
|
||||
use_context::<actix_web::HttpRequest>(cx).expect("couldn't get HttpRequest from context");
|
||||
println!("req.path = {:?}", req.path());
|
||||
|
||||
use_context::<actix_web::HttpRequest>(cx);
|
||||
|
||||
if let Some(req) = req{
|
||||
println!("req.path = {:#?}", req.path());
|
||||
}
|
||||
use futures::TryStreamExt;
|
||||
|
||||
let mut conn = db().await?;
|
||||
@@ -90,9 +92,11 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
|
||||
#[component]
|
||||
pub fn TodoApp(cx: Scope) -> impl IntoView {
|
||||
provide_meta_context(cx);
|
||||
view! {
|
||||
cx,
|
||||
<Stylesheet id="leptos" href="./target/site/pkg/todo_app_sqlite.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Stylesheet id="leptos" href="/pkg/todo_app_sqlite.css"/>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Tasks"</h1>
|
||||
|
||||
@@ -72,7 +72,7 @@ site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "./style.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
# assets-dir = "static/assets"
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
4
examples/todo_app_sqlite_axum/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
BIN
examples/todo_app_sqlite_axum/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
41
examples/todo_app_sqlite_axum/src/file.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::Extension,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
use std::sync::Arc;
|
||||
use leptos::LeptosOptions;
|
||||
|
||||
pub async fn file_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
|
||||
let options = &*options;
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await?;
|
||||
|
||||
match res.status() {
|
||||
StatusCode::OK => Ok(res),
|
||||
_ => Err((res.status(), "File Not Found".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap();
|
||||
let root_path = format!("{root}");
|
||||
// `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_path).oneshot(req).await {
|
||||
Ok(res) => Ok(res.map(boxed)),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", err),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod file;
|
||||
pub mod todo;
|
||||
|
||||
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
|
||||
// boilerplate to run in different modes
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
routing::{post},
|
||||
error_handling::HandleError,
|
||||
routing::post,
|
||||
extract::Extension,
|
||||
Router,
|
||||
};
|
||||
use crate::todo::*;
|
||||
use todo_app_sqlite_axum::*;
|
||||
use http::StatusCode;
|
||||
use tower_http::services::ServeDir;
|
||||
use crate::file::file_handler;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||
|
||||
let mut conn = db().await.expect("couldn't connect to DB");
|
||||
let conn = db().await.expect("couldn't connect to DB");
|
||||
/* sqlx::migrate!()
|
||||
.run(&mut conn)
|
||||
.await
|
||||
@@ -26,40 +26,18 @@ if #[cfg(feature = "ssr")] {
|
||||
|
||||
crate::todo::register_server_functions();
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
|
||||
// The URL path of the generated JS/WASM bundle from cargo-leptos
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
// The filesystem path of the generated JS/WASM bundle from cargo-leptos
|
||||
let bundle_filepath = format!("./{site_root}/{pkg_dir}");
|
||||
let addr = leptos_options.site_address.clone();
|
||||
log::debug!("serving at {addr}");
|
||||
|
||||
// These are Tower Services that will serve files from the static and pkg repos.
|
||||
// HandleError is needed as Axum requires services to implement Infallible Errors
|
||||
// because all Errors are converted into Responses
|
||||
let static_service = HandleError::new( ServeDir::new("./static"), handle_file_error);
|
||||
let pkg_service = HandleError::new( ServeDir::new("./pkg"), handle_file_error);
|
||||
let cargo_leptos_service = HandleError::new( ServeDir::new(&bundle_filepath), handle_file_error);
|
||||
|
||||
/// Convert the Errors from ServeDir to a type that implements IntoResponse
|
||||
async fn handle_file_error(err: std::io::Error) -> (StatusCode, String) {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
format!("File Not Found: {}", err),
|
||||
)
|
||||
}
|
||||
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> }).await;
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||
.nest_service("/pkg", pkg_service) // Only need if using wasm-pack. Can be deleted if using cargo-leptos
|
||||
.nest_service(&bundle_path, cargo_leptos_service) // Only needed if using cargo-leptos. Can be deleted if using wasm-pack and cargo-run
|
||||
.nest_service("/static", static_service)
|
||||
.fallback(leptos_axum::render_app_to_stream(leptos_options, |cx| view! { cx, <TodoApp/> }));
|
||||
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <TodoApp/> } )
|
||||
.fallback(file_handler)
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
||||
@@ -39,9 +39,11 @@ cfg_if! {
|
||||
pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
|
||||
// this is just an example of how to access server context injected in the handlers
|
||||
// http::Request doesn't implement Clone, so more work will be needed to do use_context() on this
|
||||
let req_parts = use_context::<leptos_axum::RequestParts>(cx).unwrap();
|
||||
println!("\ncalling server fn");
|
||||
let req_parts = use_context::<leptos_axum::RequestParts>(cx);
|
||||
|
||||
if let Some(req_parts) = req_parts{
|
||||
println!("Uri = {:?}", req_parts.uri);
|
||||
}
|
||||
|
||||
use futures::TryStreamExt;
|
||||
|
||||
@@ -105,9 +107,11 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
|
||||
#[component]
|
||||
pub fn TodoApp(cx: Scope) -> impl IntoView {
|
||||
provide_meta_context(cx);
|
||||
view! {
|
||||
cx,
|
||||
<Stylesheet id="leptos" href="./target/site/pkg/todo_app_sqlite_axum.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Stylesheet id="leptos" href="/pkg/todo_app_sqlite_axum.css"/>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Tasks"</h1>
|
||||
|
||||
4
examples/todomvc/Makefile.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -7,6 +7,7 @@
|
||||
<link data-trunk rel="css" href="./node_modules/todomvc-app-css/index.css">
|
||||
<title>Leptos • TodoMVC</title>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
BIN
examples/todomvc/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -1,23 +1,17 @@
|
||||
[package]
|
||||
name = "leptos_actix"
|
||||
version = "0.1.0-alpha"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Actix integrations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4"
|
||||
futures = "0.3"
|
||||
leptos = { path = "../../leptos", default-features = false, version = "0.1.0-alpha", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_meta = { path = "../../meta", default-features = false, version = "0.1.0-alpha", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_router = { path = "../../router", default-features = false, version = "0.1.0-alpha", features = [
|
||||
"ssr",
|
||||
] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
regex = "1.7.0"
|
||||
tokio = "1.24.1"
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
use actix_web::{http::header::HeaderMap, web::Bytes, *};
|
||||
use futures::{StreamExt};
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use actix_web::{
|
||||
body::BoxBody,
|
||||
dev::{ServiceFactory, ServiceRequest},
|
||||
http::header,
|
||||
web::Bytes,
|
||||
*,
|
||||
};
|
||||
use futures::{Future, StreamExt};
|
||||
use http::StatusCode;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
use regex::Regex;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@@ -12,10 +20,21 @@ use tokio::sync::RwLock;
|
||||
/// Typically contained inside of a ResponseOptions. Setting this is useful for cookies and custom responses.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ResponseParts {
|
||||
pub headers: HeaderMap,
|
||||
pub headers: header::HeaderMap,
|
||||
pub status: Option<StatusCode>,
|
||||
}
|
||||
|
||||
impl ResponseParts {
|
||||
/// Insert a header, overwriting any previous value with the same key
|
||||
pub fn insert_header(&mut self, key: header::HeaderName, value: header::HeaderValue) {
|
||||
self.headers.insert(key, value);
|
||||
}
|
||||
/// Append a header, leaving any header with the same key intact
|
||||
pub fn append_header(&mut self, key: header::HeaderName, value: header::HeaderValue) {
|
||||
self.headers.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adding this Struct to your Scope inside of a Server Fn or Elements will allow you to override details of the Response
|
||||
/// like StatusCode and add Headers/Cookies. Because Elements and Server Fns are lower in the tree than the Response generation
|
||||
/// code, it needs to be wrapped in an `Arc<RwLock<>>` so that it can be surfaced
|
||||
@@ -28,6 +47,38 @@ impl ResponseOptions {
|
||||
let mut writable = self.0.write().await;
|
||||
*writable = parts
|
||||
}
|
||||
/// Set the status of the returned Response
|
||||
pub async fn set_status(&self, status: StatusCode) {
|
||||
let mut writeable = self.0.write().await;
|
||||
let res_parts = &mut *writeable;
|
||||
res_parts.status = Some(status);
|
||||
}
|
||||
/// Insert a header, overwriting any previous value with the same key
|
||||
pub async fn insert_header(&self, key: header::HeaderName, value: header::HeaderValue) {
|
||||
let mut writeable = self.0.write().await;
|
||||
let res_parts = &mut *writeable;
|
||||
res_parts.headers.insert(key, value);
|
||||
}
|
||||
/// Append a header, leaving any header with the same key intact
|
||||
pub async fn append_header(&self, key: header::HeaderName, value: header::HeaderValue) {
|
||||
let mut writeable = self.0.write().await;
|
||||
let res_parts = &mut *writeable;
|
||||
res_parts.headers.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides an easy way to redirect the user from within a server function. Mimicing the Remix `redirect()`,
|
||||
/// it sets a StatusCode of 302 and a LOCATION header with the provided value.
|
||||
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead
|
||||
pub async fn redirect(cx: leptos::Scope, path: &str) {
|
||||
let response_options = use_context::<ResponseOptions>(cx).unwrap();
|
||||
response_options.set_status(StatusCode::FOUND).await;
|
||||
response_options
|
||||
.insert_header(
|
||||
header::LOCATION,
|
||||
header::HeaderValue::from_str(path).expect("Failed to create HeaderValue"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// An Actix [Route](actix_web::Route) that listens for a `POST` request with
|
||||
@@ -94,7 +145,7 @@ pub fn handle_server_fns() -> Route {
|
||||
|
||||
let mut res: HttpResponseBuilder;
|
||||
let mut res_parts = res_options.0.write().await;
|
||||
|
||||
|
||||
if accept_header == Some("application/json")
|
||||
|| accept_header == Some("application/x-www-form-urlencoded")
|
||||
|| accept_header == Some("application/cbor")
|
||||
@@ -146,8 +197,12 @@ pub fn handle_server_fns() -> Route {
|
||||
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
|
||||
}
|
||||
} else {
|
||||
HttpResponse::BadRequest()
|
||||
.body(format!("Could not find a server function at that route."))
|
||||
HttpResponse::BadRequest().body(format!(
|
||||
"Could not find a server function at the route {:?}. \
|
||||
\n\nIt's likely that you need to call ServerFn::register() on the \
|
||||
server function type, somewhere in your `main` function.",
|
||||
req.path()
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -197,148 +252,358 @@ pub fn render_app_to_stream<IV>(
|
||||
options: LeptosOptions,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
) -> Route
|
||||
where IV: IntoView
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
web::get().to(move |req: HttpRequest| {
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let res_options = ResponseOptions::default();
|
||||
let res_options_default = res_options.clone();
|
||||
|
||||
async move {
|
||||
let path = req.path();
|
||||
|
||||
let query = req.query_string();
|
||||
let path = if query.is_empty() {
|
||||
"http://leptos".to_string() + path
|
||||
} else {
|
||||
"http://leptos".to_string() + path + "?" + query
|
||||
};
|
||||
|
||||
let app = {
|
||||
let app_fn = app_fn.clone();
|
||||
let res_options = res_options.clone();
|
||||
move |cx| {
|
||||
let integration = ServerIntegration { path: path.clone() };
|
||||
provide_context(cx, RouterIntegrationContext::new(integration));
|
||||
provide_context(cx, MetaContext::new());
|
||||
provide_context(cx, res_options_default.clone());
|
||||
provide_context(cx, req.clone());
|
||||
|
||||
provide_contexts(cx, &req, res_options);
|
||||
(app_fn)(cx).into_view(cx)
|
||||
}
|
||||
};
|
||||
|
||||
let site_root = &options.site_root;
|
||||
|
||||
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
|
||||
// we add _bg to the wasm files if cargo-leptos doesn't set the env var OUTPUT_NAME
|
||||
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
|
||||
let output_name = &options.output_name;
|
||||
let mut wasm_output_name = output_name.clone();
|
||||
|
||||
if std::env::var("OUTPUT_NAME").is_err() {
|
||||
wasm_output_name.push_str("_bg");
|
||||
}
|
||||
|
||||
|
||||
let site_ip = &options.site_address.ip().to_string();
|
||||
let reload_port = options.reload_port;
|
||||
let site_root = &options.site_root;
|
||||
let pkg_path = &options.site_pkg_dir;
|
||||
|
||||
// We need to do some logic to check if the site_root is pkg
|
||||
// if it is, then we need to not add pkg_path. This would mean
|
||||
// the site was built with cargo run and not cargo-leptos
|
||||
let bundle_path = match site_root.as_ref() {
|
||||
"pkg" => "pkg".to_string(),
|
||||
_ => format!("{}/{}", site_root, pkg_path),
|
||||
};
|
||||
|
||||
let leptos_autoreload = match std::env::var("LEPTOS_WATCH").is_ok() {
|
||||
true => format!(
|
||||
r#"
|
||||
<script crossorigin="">(function () {{
|
||||
var ws = new WebSocket('ws://{site_ip}:{reload_port}/live_reload');
|
||||
ws.onmessage = (ev) => {{
|
||||
let msg = JSON.parse(event.data);
|
||||
if (msg.all) window.location.reload();
|
||||
if (msg.css) {{
|
||||
const link = document.querySelector("link#leptos");
|
||||
if (link) {{
|
||||
let href = link.getAttribute('href').split('?')[0];
|
||||
let newHref = href + '?version=' + new Date().getMilliseconds();
|
||||
link.setAttribute('href', newHref);
|
||||
}} else {{
|
||||
console.warn("Could not find link#leptos");
|
||||
}}
|
||||
}};
|
||||
}};
|
||||
ws.onclose = () => console.warn('Live-reload stopped. Manual reload necessary.');
|
||||
}})()
|
||||
</script>
|
||||
"#
|
||||
),
|
||||
false => "".to_string(),
|
||||
};
|
||||
|
||||
let head = format!(
|
||||
r#"<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="modulepreload" href="/{bundle_path}/{output_name}.js">
|
||||
<link rel="preload" href="/{bundle_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<script type="module">import init, {{ hydrate }} from '/{bundle_path}/{output_name}.js'; init('/{bundle_path}/{wasm_output_name}.wasm').then(hydrate);</script>
|
||||
{leptos_autoreload}
|
||||
"#
|
||||
);
|
||||
let tail = "</body></html>";
|
||||
|
||||
let (stream, runtime, _) = render_to_stream_with_prefix_undisposed(
|
||||
app,
|
||||
move |cx| {
|
||||
let head = use_context::<MetaContext>(cx)
|
||||
.map(|meta| meta.dehydrate())
|
||||
.unwrap_or_default();
|
||||
format!("{head}</head><body>").into()
|
||||
});
|
||||
|
||||
let mut stream = Box::pin(futures::stream::once(async move { head.clone() })
|
||||
.chain(stream)
|
||||
.chain(futures::stream::once(async move {
|
||||
runtime.dispose();
|
||||
tail.to_string()
|
||||
}))
|
||||
.map(|html| Ok(web::Bytes::from(html)) as Result<web::Bytes>));
|
||||
|
||||
// Get the first, second, and third chunks in the stream, which renders the app shell, and thus allows Resources to run
|
||||
let first_chunk = stream.next().await;
|
||||
let second_chunk = stream.next().await;
|
||||
let third_chunk = stream.next().await;
|
||||
|
||||
let res_options = res_options.0.read().await;
|
||||
|
||||
let (status, mut headers) = (res_options.status.clone(), res_options.headers.clone());
|
||||
let status = status.unwrap_or_default();
|
||||
|
||||
let complete_stream =
|
||||
futures::stream::iter([first_chunk.unwrap(), second_chunk.unwrap(), third_chunk.unwrap()])
|
||||
.chain(stream);
|
||||
let mut res = HttpResponse::Ok().content_type("text/html").streaming(
|
||||
complete_stream
|
||||
);
|
||||
// Add headers manipulated in the response
|
||||
for (key, value) in headers.drain(){
|
||||
if let Some(key) = key{
|
||||
res.headers_mut().append(key, value);
|
||||
}
|
||||
};
|
||||
// Set status to what is returned in the function
|
||||
let res_status = res.status_mut();
|
||||
*res_status = status;
|
||||
// Return the response
|
||||
res
|
||||
let (head, tail) = html_parts(&options);
|
||||
|
||||
stream_app(app, head, tail, res_options).await
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an Actix [Route](actix_web::Route) that listens for a `GET` request and tries
|
||||
/// to route it using [leptos_router], serving an HTML stream of your application.
|
||||
///
|
||||
/// The provides a [MetaContext] and a [RouterIntegrationContext] to app’s context before
|
||||
/// rendering it, and includes any meta tags injected using [leptos_meta].
|
||||
///
|
||||
/// The HTML stream is rendered using [render_to_stream], and includes everything described in
|
||||
/// the documentation for that function.
|
||||
///
|
||||
/// This can then be set up at an appropriate route in your application:
|
||||
/// ```
|
||||
/// use actix_web::{HttpServer, App};
|
||||
/// use leptos::*;
|
||||
/// use std::{env,net::SocketAddr};
|
||||
/// use leptos_actix::DataResponse;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn MyApp(cx: Scope, data: &'static str) -> impl IntoView {
|
||||
/// view! { cx, <main>"Hello, world!"</main> }
|
||||
/// }
|
||||
///
|
||||
/// # if false { // don't actually try to run a server in a doctest...
|
||||
/// #[actix_web::main]
|
||||
/// async fn main() -> std::io::Result<()> {
|
||||
/// let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
/// let addr = conf.leptos_options.site_address.clone();
|
||||
/// HttpServer::new(move || {
|
||||
/// let leptos_options = &conf.leptos_options;
|
||||
///
|
||||
/// App::new()
|
||||
/// // {tail:.*} passes the remainder of the URL as the route
|
||||
/// // the actual routing will be handled by `leptos_router`
|
||||
/// .route("/{tail:.*}", leptos_actix::render_preloaded_data_app(
|
||||
/// leptos_options.to_owned(),
|
||||
/// |req| async move { Ok(DataResponse::Data("async func that can preload data")) },
|
||||
/// |cx, data| view! { cx, <MyApp data/> })
|
||||
/// )
|
||||
/// })
|
||||
/// .bind(&addr)?
|
||||
/// .run()
|
||||
/// .await
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn render_preloaded_data_app<Data, Fut, IV>(
|
||||
options: LeptosOptions,
|
||||
data_fn: impl Fn(HttpRequest) -> Fut + Clone + 'static,
|
||||
app_fn: impl Fn(leptos::Scope, Data) -> IV + Clone + Send + 'static,
|
||||
) -> Route
|
||||
where
|
||||
Data: 'static,
|
||||
Fut: Future<Output = Result<DataResponse<Data>, actix_web::Error>>,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
web::get().to(move |req: HttpRequest| {
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let data_fn = data_fn.clone();
|
||||
let res_options = ResponseOptions::default();
|
||||
|
||||
async move {
|
||||
let data = match data_fn(req.clone()).await {
|
||||
Err(e) => return HttpResponse::from_error(e),
|
||||
Ok(DataResponse::Response(r)) => return r.into(),
|
||||
Ok(DataResponse::Data(d)) => d,
|
||||
};
|
||||
|
||||
let app = {
|
||||
let app_fn = app_fn.clone();
|
||||
let res_options = res_options.clone();
|
||||
move |cx| {
|
||||
provide_contexts(cx, &req, res_options);
|
||||
(app_fn)(cx, data).into_view(cx)
|
||||
}
|
||||
};
|
||||
|
||||
let (head, tail) = html_parts(&options);
|
||||
|
||||
stream_app(app, head, tail, res_options).await
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn provide_contexts(cx: leptos::Scope, req: &HttpRequest, res_options: ResponseOptions) {
|
||||
let path = leptos_corrected_path(req);
|
||||
|
||||
let integration = ServerIntegration { path };
|
||||
provide_context(cx, RouterIntegrationContext::new(integration));
|
||||
provide_context(cx, MetaContext::new());
|
||||
provide_context(cx, res_options);
|
||||
provide_context(cx, req.clone());
|
||||
}
|
||||
|
||||
fn leptos_corrected_path(req: &HttpRequest) -> String {
|
||||
let path = req.path();
|
||||
let query = req.query_string();
|
||||
if query.is_empty() {
|
||||
"http://leptos".to_string() + path
|
||||
} else {
|
||||
"http://leptos".to_string() + path + "?" + query
|
||||
}
|
||||
}
|
||||
|
||||
async fn stream_app(
|
||||
app: impl FnOnce(leptos::Scope) -> View + 'static,
|
||||
head: String,
|
||||
tail: String,
|
||||
res_options: ResponseOptions,
|
||||
) -> HttpResponse<BoxBody> {
|
||||
let (stream, runtime, _) = render_to_stream_with_prefix_undisposed(app, move |cx| {
|
||||
let head = use_context::<MetaContext>(cx)
|
||||
.map(|meta| meta.dehydrate())
|
||||
.unwrap_or_default();
|
||||
format!("{head}</head><body>").into()
|
||||
});
|
||||
|
||||
let mut stream = Box::pin(
|
||||
futures::stream::once(async move { head.clone() })
|
||||
.chain(stream)
|
||||
.chain(futures::stream::once(async move {
|
||||
runtime.dispose();
|
||||
tail.to_string()
|
||||
}))
|
||||
.map(|html| Ok(web::Bytes::from(html)) as Result<web::Bytes>),
|
||||
);
|
||||
|
||||
// Get the first, second, and third chunks in the stream, which renders the app shell, and thus allows Resources to run
|
||||
let first_chunk = stream.next().await;
|
||||
let second_chunk = stream.next().await;
|
||||
let third_chunk = stream.next().await;
|
||||
|
||||
let res_options = res_options.0.read().await;
|
||||
|
||||
let (status, mut headers) = (res_options.status, res_options.headers.clone());
|
||||
let status = status.unwrap_or_default();
|
||||
|
||||
let complete_stream = futures::stream::iter([
|
||||
first_chunk.unwrap(),
|
||||
second_chunk.unwrap(),
|
||||
third_chunk.unwrap(),
|
||||
])
|
||||
.chain(stream);
|
||||
let mut res = HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.streaming(complete_stream);
|
||||
// Add headers manipulated in the response
|
||||
for (key, value) in headers.drain() {
|
||||
if let Some(key) = key {
|
||||
res.headers_mut().append(key, value);
|
||||
}
|
||||
}
|
||||
// Set status to what is returned in the function
|
||||
let res_status = res.status_mut();
|
||||
*res_status = status;
|
||||
// Return the response
|
||||
res
|
||||
}
|
||||
|
||||
fn html_parts(options: &LeptosOptions) -> (String, String) {
|
||||
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
|
||||
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
|
||||
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
|
||||
let output_name = &options.output_name;
|
||||
let mut wasm_output_name = output_name.clone();
|
||||
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
|
||||
wasm_output_name.push_str("_bg");
|
||||
}
|
||||
|
||||
let site_ip = &options.site_address.ip().to_string();
|
||||
let reload_port = options.reload_port;
|
||||
let pkg_path = &options.site_pkg_dir;
|
||||
|
||||
let leptos_autoreload = match std::env::var("LEPTOS_WATCH").is_ok() {
|
||||
true => format!(
|
||||
r#"
|
||||
<script crossorigin="">(function () {{
|
||||
var ws = new WebSocket('ws://{site_ip}:{reload_port}/live_reload');
|
||||
ws.onmessage = (ev) => {{
|
||||
let msg = JSON.parse(ev.data);
|
||||
if (msg.all) window.location.reload();
|
||||
if (msg.css) {{
|
||||
const link = document.querySelector("link#leptos");
|
||||
if (link) {{
|
||||
let href = link.getAttribute('href').split('?')[0];
|
||||
let newHref = href + '?version=' + new Date().getMilliseconds();
|
||||
link.setAttribute('href', newHref);
|
||||
}} else {{
|
||||
console.warn("Could not find link#leptos");
|
||||
}}
|
||||
}};
|
||||
}};
|
||||
ws.onclose = () => console.warn('Live-reload stopped. Manual reload necessary.');
|
||||
}})()
|
||||
</script>
|
||||
"#
|
||||
),
|
||||
false => "".to_string(),
|
||||
};
|
||||
|
||||
let head = format!(
|
||||
r#"<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="modulepreload" href="/{pkg_path}/{output_name}.js">
|
||||
<link rel="preload" href="/{pkg_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<script type="module">import init, {{ hydrate }} from '/{pkg_path}/{output_name}.js'; init('/{pkg_path}/{wasm_output_name}.wasm').then(hydrate);</script>
|
||||
{leptos_autoreload}
|
||||
"#
|
||||
);
|
||||
let tail = "</body></html>".to_string();
|
||||
|
||||
(head, tail)
|
||||
}
|
||||
|
||||
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
|
||||
/// create routes in Actix's App without having to use wildcard matching or fallbacks. Takes in your root app Element
|
||||
/// as an argument so it can walk you app tree. This version is tailored to generated Actix compatible paths.
|
||||
pub fn generate_route_list<IV>(app_fn: impl FnOnce(leptos::Scope) -> IV + 'static) -> Vec<String>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut routes = leptos_router::generate_route_list_inner(app_fn);
|
||||
|
||||
// Empty strings screw with Actix pathing, they need to be "/"
|
||||
routes = routes
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if s.is_empty() {
|
||||
return "/".to_string();
|
||||
}
|
||||
s.to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Actix's Router doesn't follow Leptos's
|
||||
// Match `*` or `*someword` to replace with replace it with "/{tail.*}
|
||||
let wildcard_re = Regex::new(r"\*.*").unwrap();
|
||||
// Match `:some_word` but only capture `some_word` in the groups to replace with `{some_word}`
|
||||
let capture_re = Regex::new(r":((?:[^.,/]+)+)[^/]?").unwrap();
|
||||
|
||||
let routes: Vec<String> = routes
|
||||
.iter()
|
||||
.map(|s| wildcard_re.replace_all(s, "{tail:.*}").to_string())
|
||||
.map(|s| capture_re.replace_all(&s, "{$1}").to_string())
|
||||
.collect();
|
||||
|
||||
if routes.is_empty() {
|
||||
vec!["/".to_string()]
|
||||
} else {
|
||||
routes
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DataResponse<T> {
|
||||
Data(T),
|
||||
Response(actix_web::dev::Response<BoxBody>),
|
||||
}
|
||||
|
||||
/// This trait allows one to pass a list of routes and a render function to Axum's router, letting us avoid
|
||||
/// having to use wildcards or manually define all routes in multiple places.
|
||||
pub trait LeptosRoutes {
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
|
||||
fn leptos_preloaded_data_routes<Data, Fut, IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
data_fn: impl Fn(HttpRequest) -> Fut + Clone + 'static,
|
||||
app_fn: impl Fn(leptos::Scope, Data) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Data: 'static,
|
||||
Fut: Future<Output = Result<DataResponse<Data>, actix_web::Error>>,
|
||||
IV: IntoView + 'static;
|
||||
}
|
||||
|
||||
/// The default implementation of `LeptosRoutes` which takes in a list of paths, and dispatches GET requests
|
||||
/// to those paths to Leptos's renderer.
|
||||
impl<T> LeptosRoutes for actix_web::App<T>
|
||||
where
|
||||
T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
|
||||
{
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
for path in paths.iter() {
|
||||
router = router.route(path, render_app_to_stream(options.clone(), app_fn.clone()));
|
||||
}
|
||||
router
|
||||
}
|
||||
|
||||
fn leptos_preloaded_data_routes<Data, Fut, IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
data_fn: impl Fn(HttpRequest) -> Fut + Clone + 'static,
|
||||
app_fn: impl Fn(leptos::Scope, Data) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Data: 'static,
|
||||
Fut: Future<Output = Result<DataResponse<Data>, actix_web::Error>>,
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
|
||||
for path in paths.iter() {
|
||||
router = router.route(
|
||||
path,
|
||||
render_preloaded_data_app(options.clone(), data_fn.clone(), app_fn.clone()),
|
||||
);
|
||||
}
|
||||
router
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,19 @@
|
||||
[package]
|
||||
name = "leptos_axum"
|
||||
version = "0.1.0-alpha"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Axum integrations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
axum = {version="0.6", features=["macros"]}
|
||||
derive_builder = "0.12.0"
|
||||
axum = { version = "0.6", features = ["macros"] }
|
||||
futures = "0.3"
|
||||
http = "0.2.8"
|
||||
hyper = "0.14.23"
|
||||
kdl = "4.6.0"
|
||||
leptos = { path = "../../leptos", default-features = false, version = "0.1.0-alpha", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_meta = { path = "../../meta", default-features = false, version = "0.1.0-alpha", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_router = { path = "../../router", default-features = false, version = "0.1.0-alpha", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_config = { path = "../../leptos_config", default-features = false, version = "0.1.0-alpha" }
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_config = { workspace = true }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use axum::{
|
||||
body::{Body, Bytes, Full, StreamBody},
|
||||
extract::Path,
|
||||
http::{HeaderMap, HeaderValue, Request, StatusCode},
|
||||
http::{header::HeaderName, header::HeaderValue, HeaderMap, Request, StatusCode},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
};
|
||||
use futures::{Future, SinkExt, Stream, StreamExt};
|
||||
use http::{method::Method, uri::Uri, version::Version, Response};
|
||||
use http::{header, method::Method, uri::Uri, version::Version, Response};
|
||||
use hyper::body;
|
||||
use leptos::*;
|
||||
use leptos_meta::MetaContext;
|
||||
use leptos_router::*;
|
||||
use std::{io, pin::Pin, sync::Arc};
|
||||
use tokio::{sync::RwLock, task::spawn_blocking};
|
||||
use tokio::{sync::RwLock, task::spawn_blocking, task::LocalSet};
|
||||
|
||||
/// A struct to hold the parts of the incoming Request. Since `http::Request` isn't cloneable, we're forced
|
||||
/// to construct this for Leptos to use in Axum
|
||||
@@ -31,6 +34,17 @@ pub struct ResponseParts {
|
||||
pub headers: HeaderMap,
|
||||
}
|
||||
|
||||
impl ResponseParts {
|
||||
/// Insert a header, overwriting any previous value with the same key
|
||||
pub fn insert_header(&mut self, key: HeaderName, value: HeaderValue) {
|
||||
self.headers.insert(key, value);
|
||||
}
|
||||
/// Append a header, leaving any header with the same key intact
|
||||
pub fn append_header(&mut self, key: HeaderName, value: HeaderValue) {
|
||||
self.headers.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adding this Struct to your Scope inside of a Server Fn or Element will allow you to override details of the Response
|
||||
/// like status and add Headers/Cookies. Because Elements and Server Fns are lower in the tree than the Response generation
|
||||
/// code, it needs to be wrapped in an `Arc<RwLock<>>` so that it can be surfaced.
|
||||
@@ -38,11 +52,43 @@ pub struct ResponseParts {
|
||||
pub struct ResponseOptions(pub Arc<RwLock<ResponseParts>>);
|
||||
|
||||
impl ResponseOptions {
|
||||
/// A less boilerplatey way to overwrite the default contents of `ResponseOptions` with a new `ResponseParts`
|
||||
/// A less boilerplatey way to overwrite the contents of `ResponseOptions` with a new `ResponseParts`
|
||||
pub async fn overwrite(&self, parts: ResponseParts) {
|
||||
let mut writable = self.0.write().await;
|
||||
*writable = parts
|
||||
}
|
||||
/// Set the status of the returned Response
|
||||
pub async fn set_status(&self, status: StatusCode) {
|
||||
let mut writeable = self.0.write().await;
|
||||
let res_parts = &mut *writeable;
|
||||
res_parts.status = Some(status);
|
||||
}
|
||||
/// Insert a header, overwriting any previous value with the same key
|
||||
pub async fn insert_header(&self, key: HeaderName, value: HeaderValue) {
|
||||
let mut writeable = self.0.write().await;
|
||||
let res_parts = &mut *writeable;
|
||||
res_parts.headers.insert(key, value);
|
||||
}
|
||||
/// Append a header, leaving any header with the same key intact
|
||||
pub async fn append_header(&self, key: HeaderName, value: HeaderValue) {
|
||||
let mut writeable = self.0.write().await;
|
||||
let res_parts = &mut *writeable;
|
||||
res_parts.headers.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides an easy way to redirect the user from within a server function. Mimicing the Remix `redirect()`,
|
||||
/// it sets a StatusCode of 302 and a LOCATION header with the provided value.
|
||||
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead
|
||||
pub async fn redirect(cx: leptos::Scope, path: &str) {
|
||||
let response_options = use_context::<ResponseOptions>(cx).unwrap();
|
||||
response_options.set_status(StatusCode::FOUND).await;
|
||||
response_options
|
||||
.insert_header(
|
||||
header::LOCATION,
|
||||
header::HeaderValue::from_str(path).expect("Failed to create HeaderValue"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn generate_request_parts(req: Request<Body>) -> RequestParts {
|
||||
@@ -54,7 +100,7 @@ pub async fn generate_request_parts(req: Request<Body>) -> RequestParts {
|
||||
uri: parts.uri,
|
||||
headers: parts.headers,
|
||||
version: parts.version,
|
||||
body: body.clone(),
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +142,7 @@ pub async fn handle_server_fns(
|
||||
req: Request<Body>,
|
||||
) -> impl IntoResponse {
|
||||
// Axum Path extractor doesn't remove the first slash from the path, while Actix does
|
||||
let fn_name: String = match fn_name.strip_prefix("/") {
|
||||
let fn_name: String = match fn_name.strip_prefix('/') {
|
||||
Some(path) => path.to_string(),
|
||||
None => fn_name,
|
||||
};
|
||||
@@ -137,15 +183,12 @@ pub async fn handle_server_fns(
|
||||
let res_options_outer = res_options.unwrap().0;
|
||||
let res_options_inner = res_options_outer.read().await;
|
||||
let (status, mut res_headers) = (
|
||||
res_options_inner.status.clone(),
|
||||
res_options_inner.status,
|
||||
res_options_inner.headers.clone(),
|
||||
);
|
||||
|
||||
match res.headers_mut() {
|
||||
Some(header_ref) => {
|
||||
header_ref.extend(res_headers.drain());
|
||||
}
|
||||
None => (),
|
||||
if let Some(header_ref) = res.headers_mut() {
|
||||
header_ref.extend(res_headers.drain());
|
||||
};
|
||||
|
||||
if accept_header == Some("application/json")
|
||||
@@ -194,7 +237,9 @@ pub async fn handle_server_fns(
|
||||
Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Full::from(
|
||||
"Could not find a server function at that route.".to_string(),
|
||||
format!("Could not find a server function at the route {fn_name}. \
|
||||
\n\nIt's likely that you need to call ServerFn::register() on the \
|
||||
server function type, somewhere in your `main` function." )
|
||||
))
|
||||
}
|
||||
.expect("could not build Response");
|
||||
@@ -276,34 +321,20 @@ where
|
||||
|
||||
async move {
|
||||
// Need to get the path and query string of the Request
|
||||
let path = req.uri();
|
||||
let query = path.query();
|
||||
// For reasons that escape me, if the incoming URI protocol is https, it provides the absolute URI
|
||||
// if http, it returns a relative path. Adding .path() seems to make it explicitly return the relative uri
|
||||
let path = req.uri().path_and_query().unwrap().as_str();
|
||||
|
||||
let full_path;
|
||||
if let Some(query) = query {
|
||||
full_path = "http://leptos".to_string() + &path.to_string() + "?" + query
|
||||
} else {
|
||||
full_path = "http://leptos".to_string() + &path.to_string()
|
||||
}
|
||||
let full_path = format!("http://leptos.dev{path}");
|
||||
|
||||
let site_root = &options.site_root;
|
||||
let pkg_path = &options.site_pkg_dir;
|
||||
|
||||
// We need to do some logic to check if the site_root is pkg
|
||||
// if it is, then we need to not add pkg_path. This would mean
|
||||
// the site was built with cargo run and not cargo-leptos
|
||||
let bundle_path = match site_root.as_ref() {
|
||||
"pkg" => "pkg".to_string(),
|
||||
_ => format!("{}/{}", site_root, pkg_path),
|
||||
};
|
||||
|
||||
let output_name = &options.output_name;
|
||||
|
||||
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
|
||||
// we add _bg to the wasm files if cargo-leptos doesn't set the env var OUTPUT_NAME
|
||||
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
|
||||
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
|
||||
let mut wasm_output_name = output_name.clone();
|
||||
if std::env::var("OUTPUT_NAME").is_err() {
|
||||
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
|
||||
wasm_output_name.push_str("_bg");
|
||||
}
|
||||
|
||||
@@ -316,7 +347,7 @@ where
|
||||
<script crossorigin="">(function () {{
|
||||
var ws = new WebSocket('ws://{site_ip}:{reload_port}/live_reload');
|
||||
ws.onmessage = (ev) => {{
|
||||
let msg = JSON.parse(event.data);
|
||||
let msg = JSON.parse(ev.data);
|
||||
if (msg.all) window.location.reload();
|
||||
if (msg.css) {{
|
||||
const link = document.querySelector("link#leptos");
|
||||
@@ -343,9 +374,9 @@ where
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="modulepreload" href="/{bundle_path}/{output_name}.js">
|
||||
<link rel="preload" href="/{bundle_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<script type="module">import init, {{ hydrate }} from '/{bundle_path}/{output_name}.js'; init('/{bundle_path}/{wasm_output_name}.wasm').then(hydrate);</script>
|
||||
<link rel="modulepreload" href="/{pkg_path}/{output_name}.js">
|
||||
<link rel="preload" href="/{pkg_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<script type="module">import init, {{ hydrate }} from '/{pkg_path}/{output_name}.js'; init('/{pkg_path}/{wasm_output_name}.wasm').then(hydrate);</script>
|
||||
{leptos_autoreload}
|
||||
"#
|
||||
);
|
||||
@@ -442,10 +473,9 @@ where
|
||||
Box::pin(complete_stream) as PinnedHtmlStream
|
||||
));
|
||||
|
||||
match res_options.status {
|
||||
Some(status) => *res.status_mut() = status,
|
||||
None => (),
|
||||
};
|
||||
if let Some(status) = res_options.status {
|
||||
*res.status_mut() = status
|
||||
}
|
||||
let mut res_headers = res_options.headers.clone();
|
||||
res.headers_mut().extend(res_headers.drain());
|
||||
|
||||
@@ -454,3 +484,85 @@ where
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
|
||||
/// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element
|
||||
/// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths.
|
||||
pub async fn generate_route_list<IV>(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec<String>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Routes(pub Arc<RwLock<Vec<String>>>);
|
||||
|
||||
let routes = Routes::default();
|
||||
let routes_inner = routes.clone();
|
||||
|
||||
let local = LocalSet::new();
|
||||
// Run the local task set.
|
||||
|
||||
local
|
||||
.run_until(async move {
|
||||
tokio::task::spawn_local(async move {
|
||||
let routes = leptos_router::generate_route_list_inner(app_fn);
|
||||
let mut writable = routes_inner.0.write().await;
|
||||
*writable = routes;
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
.await;
|
||||
|
||||
let routes = routes.0.read().await.to_owned();
|
||||
// Axum's Router defines Root routes as "/" not ""
|
||||
let routes: Vec<String> = routes
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if s.is_empty() {
|
||||
return "/".to_string();
|
||||
}
|
||||
s.to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
if routes.is_empty() {
|
||||
vec!["/".to_string()]
|
||||
} else {
|
||||
routes
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait allows one to pass a list of routes and a render function to Axum's router, letting us avoid
|
||||
/// having to use wildcards or manually define all routes in multiple places.
|
||||
pub trait LeptosRoutes {
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
}
|
||||
/// The default implementation of `LeptosRoutes` which takes in a list of paths, and dispatches GET requests
|
||||
/// to those paths to Leptos's renderer.
|
||||
impl LeptosRoutes for axum::Router {
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
for path in paths.iter() {
|
||||
router = router.route(
|
||||
path,
|
||||
get(render_app_to_stream(options.clone(), app_fn.clone())),
|
||||
);
|
||||
}
|
||||
router
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos"
|
||||
version = "0.1.0-alpha"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -10,11 +10,11 @@ readme = "../README.md"
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
leptos_config = { path = "../leptos_config", default-features = false, version = "0.1.0-alpha" }
|
||||
leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.1.0-alpha" }
|
||||
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.1.0-alpha" }
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.1.0-alpha" }
|
||||
leptos_server = { path = "../leptos_server", default-features = false, version = "0.1.0-alpha" }
|
||||
leptos_dom = { workspace = true }
|
||||
leptos_macro = { workspace = true }
|
||||
leptos_reactive = { workspace = true }
|
||||
leptos_server = { workspace = true }
|
||||
leptos_config = { workspace = true }
|
||||
tracing = "0.1"
|
||||
typed-builder = "0.11"
|
||||
|
||||
@@ -24,28 +24,28 @@ leptos = { path = ".", default-features = false }
|
||||
[features]
|
||||
default = ["csr", "serde"]
|
||||
csr = [
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/csr",
|
||||
"leptos_reactive/csr",
|
||||
"leptos_server/csr",
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/csr",
|
||||
"leptos_reactive/csr",
|
||||
"leptos_server/csr",
|
||||
]
|
||||
hydrate = [
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/hydrate",
|
||||
"leptos_reactive/hydrate",
|
||||
"leptos_server/hydrate",
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/hydrate",
|
||||
"leptos_reactive/hydrate",
|
||||
"leptos_server/hydrate",
|
||||
]
|
||||
ssr = [
|
||||
"leptos_dom/ssr",
|
||||
"leptos_macro/ssr",
|
||||
"leptos_reactive/ssr",
|
||||
"leptos_server/ssr",
|
||||
"leptos_dom/ssr",
|
||||
"leptos_macro/ssr",
|
||||
"leptos_reactive/ssr",
|
||||
"leptos_server/ssr",
|
||||
]
|
||||
stable = [
|
||||
"leptos_dom/stable",
|
||||
"leptos_macro/stable",
|
||||
"leptos_reactive/stable",
|
||||
"leptos_server/stable",
|
||||
"leptos_dom/stable",
|
||||
"leptos_macro/stable",
|
||||
"leptos_reactive/stable",
|
||||
"leptos_server/stable",
|
||||
]
|
||||
serde = ["leptos_reactive/serde"]
|
||||
serde-lite = ["leptos_reactive/serde-lite"]
|
||||
@@ -53,7 +53,7 @@ miniserde = ["leptos_reactive/miniserde"]
|
||||
tracing = ["leptos_macro/tracing"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
denylist = ["stable", "tracing"]
|
||||
skip_feature_sets = [
|
||||
[
|
||||
"csr",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! # About Leptos
|
||||
//!
|
||||
@@ -18,33 +19,33 @@
|
||||
//! 1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.0", features = ["stable"] }`
|
||||
//! 2. `nightly` enables the function call syntax for accessing and setting signals. If you’re using `stable`,
|
||||
//! you’ll just call `.get()`, `.set()`, or `.update()` manually. Check out the
|
||||
//! [`counters-stable` example](https://github.com/gbj/leptos/blob/main/examples/counters-stable/src/main.rs)
|
||||
//! [`counters_stable` example](https://github.com/leptos-rs/leptos/blob/main/examples/counters_stable/src/main.rs)
|
||||
//! for examples of the correct API.
|
||||
//!
|
||||
//! # Learning by Example
|
||||
//!
|
||||
//! These docs are a work in progress. If you want to see what Leptos is capable of, check out
|
||||
//! the [examples](https://github.com/gbj/leptos/tree/main/examples):
|
||||
//! - [`counter`](https://github.com/gbj/leptos/tree/main/examples/counter) is the classic
|
||||
//! the [examples](https://github.com/leptos-rs/leptos/tree/main/examples):
|
||||
//! - [`counter`](https://github.com/leptos-rs/leptos/tree/main/examples/counter) is the classic
|
||||
//! counter example, showing the basics of client-side rendering and reactive DOM updates
|
||||
//! - [`counters`](https://github.com/gbj/leptos/tree/main/examples/counter) introduces parent-child
|
||||
//! - [`counters`](https://github.com/leptos-rs/leptos/tree/main/examples/counters) introduces parent-child
|
||||
//! communication via contexts, and the `<For/>` component for efficient keyed list updates.
|
||||
//! - [`parent-child`](https://github.com/gbj/leptos/tree/main/examples/parent-child) shows four different
|
||||
//! - [`parent_child`](https://github.com/leptos-rs/leptos/tree/main/examples/parent_child) shows four different
|
||||
//! ways a parent component can communicate with a child, including passing a closure, context, and more
|
||||
//! - [`todomvc`](https://github.com/gbj/leptos/tree/main/examples/todomvc) implements the classic to-do
|
||||
//! - [`todomvc`](https://github.com/leptos-rs/leptos/tree/main/examples/todomvc) implements the classic to-do
|
||||
//! app in Leptos. This is a good example of a complete, simple app. In particular, you might want to
|
||||
//! see how we use [create_effect] to [serialize JSON to `localStorage`](https://github.com/gbj/leptos/blob/16f084a71268ac325fbc4a5e50c260df185eadb6/examples/todomvc/src/lib.rs#L164)
|
||||
//! and [reactively call DOM methods](https://github.com/gbj/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L291)
|
||||
//! on [references to elements](https://github.com/gbj/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L254).
|
||||
//! - [`fetch`](https://github.com/gbj/leptos/tree/main/examples/fetch) introduces
|
||||
//! see how we use [create_effect] to [serialize JSON to `localStorage`](https://github.com/leptos-rs/leptos/blob/16f084a71268ac325fbc4a5e50c260df185eadb6/examples/todomvc/src/lib.rs#L164)
|
||||
//! and [reactively call DOM methods](https://github.com/leptos-rs/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L291)
|
||||
//! on [references to elements](https://github.com/leptos-rs/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L254).
|
||||
//! - [`fetch`](https://github.com/leptos-rs/leptos/tree/main/examples/fetch) introduces
|
||||
//! [Resource](leptos_reactive::Resource)s, which allow you to integrate arbitrary `async` code like an
|
||||
//! HTTP request within your reactive code.
|
||||
//! - [`router`](https://github.com/gbj/leptos/tree/main/examples/router) shows how to use Leptos’s nested router
|
||||
//! - [`router`](https://github.com/leptos-rs/leptos/tree/main/examples/router) shows how to use Leptos’s nested router
|
||||
//! to enable client-side navigation and route-specific, reactive data loading.
|
||||
//! - [`todomvc`](https://github.com/gbj/leptos/tree/main/examples/todomvc) shows the basics of building an
|
||||
//! - [`todomvc`](https://github.com/leptos-rs/leptos/tree/main/examples/todomvc) shows the basics of building an
|
||||
//! isomorphic web app. Both the server and the client import the same app code from the `todomvc` example.
|
||||
//! The server renders the app directly to an HTML string, and the client hydrates that HTML to make it interactive.
|
||||
//! - [`hackernews`](https://github.com/gbj/leptos/tree/main/examples/hackernews) pulls everything together.
|
||||
//! - [`hackernews`](https://github.com/leptos-rs/leptos/tree/main/examples/hackernews) pulls everything together.
|
||||
//! It integrates calls to a real external REST API, routing, server-side rendering and hydration to create
|
||||
//! a fully-functional PEMPA that works as intended even before WASM has loaded and begun to run.
|
||||
//!
|
||||
@@ -147,4 +148,4 @@ pub use transition::*;
|
||||
|
||||
pub use leptos_reactive::debug_warn;
|
||||
|
||||
extern crate self as leptos;
|
||||
extern crate self as leptos;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos_macro::component;
|
||||
use std::rc::Rc;
|
||||
use leptos_dom::{DynChild, Fragment, IntoView, Component};
|
||||
use leptos_reactive::{provide_context, Scope, SuspenseContext};
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
use leptos_dom::{HydrationCtx, HydrationKey};
|
||||
use leptos_dom::HydrationCtx;
|
||||
use leptos_dom::{DynChild, Fragment, IntoView};
|
||||
use leptos_macro::component;
|
||||
use leptos_reactive::{provide_context, Scope, SuspenseContext};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// If any [Resources](leptos_reactive::Resource) are read in the `children` of this
|
||||
/// component, it will show the `fallback` while they are loading. Once all are resolved,
|
||||
@@ -73,7 +73,7 @@ where
|
||||
|
||||
let orig_child = Rc::new(children);
|
||||
|
||||
Component::new("Suspense", move |cx| {
|
||||
leptos_dom::custom(cx, leptos_dom::Custom::new("leptos-suspense")).child({
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
let current_id = HydrationCtx::peek();
|
||||
|
||||
@@ -88,8 +88,8 @@ where
|
||||
} else {
|
||||
// run the child; we'll probably throw this away, but it will register resource reads
|
||||
let child = orig_child(cx).into_view(cx);
|
||||
|
||||
let initial = {
|
||||
|
||||
let initial = {
|
||||
// no resources were read under this, so just return the child
|
||||
if context.pending_resources.get() == 0 {
|
||||
child.clone()
|
||||
@@ -97,37 +97,33 @@ where
|
||||
// show the fallback, but also prepare to stream HTML
|
||||
else {
|
||||
let orig_child = Rc::clone(&orig_child);
|
||||
|
||||
|
||||
cx.register_suspense(
|
||||
context,
|
||||
&id_before_suspense.to_string(),
|
||||
&id_before_suspense.to_string(),
|
||||
¤t_id.to_string(),
|
||||
{
|
||||
let current_id = current_id.clone();
|
||||
let fragment_id = HydrationKey {
|
||||
previous: current_id.previous,
|
||||
offset: current_id.offset + 1
|
||||
};
|
||||
move || {
|
||||
HydrationCtx::continue_from(fragment_id);
|
||||
orig_child(cx)
|
||||
HydrationCtx::continue_from(current_id.clone());
|
||||
DynChild::new(move || orig_child(cx))
|
||||
.into_view(cx)
|
||||
.render_to_string(cx)
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// return the fallback for now, wrapped in fragment identifer
|
||||
fallback().into_view(cx)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
HydrationCtx::continue_from(current_id.clone());
|
||||
|
||||
|
||||
initial
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use leptos_dom::{Fragment, IntoView, View};
|
||||
use leptos_macro::component;
|
||||
use leptos_reactive::{ Scope, SignalSetter};
|
||||
use leptos_reactive::{Scope, SignalSetter};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
/// If any [Resource](leptos_reactive::Resource)s are read in the `children` of this
|
||||
@@ -66,7 +66,7 @@ pub fn Transition<F, E>(
|
||||
#[prop(optional)]
|
||||
set_pending: Option<SignalSetter<bool>>,
|
||||
/// Will be displayed once all resources have resolved.
|
||||
children: Box<dyn Fn(Scope) -> Fragment>
|
||||
children: Box<dyn Fn(Scope) -> Fragment>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
F: Fn() -> E + 'static,
|
||||
@@ -78,7 +78,6 @@ where
|
||||
crate::SuspenseProps::builder()
|
||||
.fallback({
|
||||
let prev_child = Rc::clone(&prev_children);
|
||||
let set_pending = set_pending.clone();
|
||||
move || {
|
||||
if let Some(set_pending) = &set_pending {
|
||||
set_pending.set(true);
|
||||
@@ -98,6 +97,6 @@ where
|
||||
}
|
||||
frag
|
||||
}))
|
||||
.build()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ fn simple_ssr_test() {
|
||||
|
||||
assert_eq!(
|
||||
rendered.into_view(cx).render_to_string(cx),
|
||||
"<div id=\"_0-1\"><button id=\"_0-2\">-1</button><span id=\"_0-3\">Value: <leptos-dyn-child-start leptos id=\"_0-4o\"></leptos-dyn-child-start>0<leptos-dyn-child-end leptos id=\"_0-4c\"></leptos-dyn-child-end>!</span><button id=\"_0-5\">+1</button></div>"
|
||||
"<div id=\"_0-1\"><button id=\"_0-2\">-1</button><span id=\"_0-3\">Value: <!--hk=_0-4o|leptos-dyn-child-start-->0<!--hk=_0-4c|leptos-dyn-child-end-->!</span><button id=\"_0-5\">+1</button></div>"
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -50,7 +50,7 @@ fn ssr_test_with_components() {
|
||||
|
||||
assert_eq!(
|
||||
rendered.into_view(cx).render_to_string(cx),
|
||||
"<div class=\"counters\" id=\"_0-1\"><leptos-counter-start leptos id=\"_0-1-0o\"></leptos-counter-start><div id=\"_0-1-1\"><button id=\"_0-1-2\">-1</button><span id=\"_0-1-3\">Value: <leptos-dyn-child-start leptos id=\"_0-1-4o\"></leptos-dyn-child-start>1<leptos-dyn-child-end leptos id=\"_0-1-4c\"></leptos-dyn-child-end>!</span><button id=\"_0-1-5\">+1</button></div><leptos-counter-end leptos id=\"_0-1-0c\"></leptos-counter-end><leptos-counter-start leptos id=\"_0-1-5-0o\"></leptos-counter-start><div id=\"_0-1-5-1\"><button id=\"_0-1-5-2\">-1</button><span id=\"_0-1-5-3\">Value: <leptos-dyn-child-start leptos id=\"_0-1-5-4o\"></leptos-dyn-child-start>2<leptos-dyn-child-end leptos id=\"_0-1-5-4c\"></leptos-dyn-child-end>!</span><button id=\"_0-1-5-5\">+1</button></div><leptos-counter-end leptos id=\"_0-1-5-0c\"></leptos-counter-end></div>"
|
||||
"<div class=\"counters\" id=\"_0-1\"><!--hk=_0-1-0o|leptos-counter-start--><div id=\"_0-1-1\"><button id=\"_0-1-2\">-1</button><span id=\"_0-1-3\">Value: <!--hk=_0-1-4o|leptos-dyn-child-start-->1<!--hk=_0-1-4c|leptos-dyn-child-end-->!</span><button id=\"_0-1-5\">+1</button></div><!--hk=_0-1-0c|leptos-counter-end--><!--hk=_0-1-5-0o|leptos-counter-start--><div id=\"_0-1-5-1\"><button id=\"_0-1-5-2\">-1</button><span id=\"_0-1-5-3\">Value: <!--hk=_0-1-5-4o|leptos-dyn-child-start-->2<!--hk=_0-1-5-4c|leptos-dyn-child-end-->!</span><button id=\"_0-1-5-5\">+1</button></div><!--hk=_0-1-5-0c|leptos-counter-end--></div>"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
[package]
|
||||
name = "leptos_config"
|
||||
version = "0.1.0-alpha"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Configuraiton for the Leptos web framework."
|
||||
readme = "../README.md"
|
||||
|
||||
[dependencies]
|
||||
config = "0.13.3"
|
||||
@@ -13,5 +14,4 @@ fs = "0.0.5"
|
||||
regex = "1.7.0"
|
||||
serde = { version = "1.0.151", features = ["derive"] }
|
||||
thiserror = "1.0.38"
|
||||
typed-builder = "0.11.0"
|
||||
|
||||
typed-builder = "0.11"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::{net::AddrParseError, num::ParseIntError};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, Clone)]
|
||||
@@ -10,9 +12,23 @@ pub enum LeptosConfigError {
|
||||
EnvError,
|
||||
#[error("Config Error: {0}")]
|
||||
ConfigError(String),
|
||||
#[error("Config Error: {0}")]
|
||||
EnvVarError(String),
|
||||
}
|
||||
impl From<config::ConfigError> for LeptosConfigError {
|
||||
fn from(e: config::ConfigError) -> Self {
|
||||
Self::ConfigError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for LeptosConfigError {
|
||||
fn from(e: ParseIntError) -> Self {
|
||||
Self::ConfigError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddrParseError> for LeptosConfigError {
|
||||
fn from(e: AddrParseError) -> Self {
|
||||
Self::ConfigError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod errors;
|
||||
|
||||
use crate::errors::LeptosConfigError;
|
||||
@@ -10,21 +12,23 @@ use typed_builder::TypedBuilder;
|
||||
|
||||
/// A Struct to allow us to parse LeptosOptions from the file. Not really needed, most interactions should
|
||||
/// occur with LeptosOptions
|
||||
#[derive(Clone, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
pub struct ConfFile {
|
||||
pub leptos_options: LeptosOptions,
|
||||
}
|
||||
|
||||
/// This struct serves as a convenient place to store details used for configuring Leptos.
|
||||
/// It's used in our actix and axum integrations to generate the
|
||||
/// correct path for WASM, JS, and Websockets, as well as other configuration tasks.
|
||||
/// It shares keys with cargo-leptos, to allow for easy interoperability
|
||||
#[derive(TypedBuilder, Clone, serde::Deserialize)]
|
||||
#[derive(TypedBuilder, Debug, Clone, serde::Deserialize)]
|
||||
pub struct LeptosOptions {
|
||||
/// The name of the WASM and JS files generated by wasm-bindgen. Defaults to the crate name with underscores instead of dashes
|
||||
#[builder(setter(into))]
|
||||
pub output_name: String,
|
||||
/// The path of the all the files generated by cargo-leptos
|
||||
#[builder(setter(into), default="pkg".to_string())]
|
||||
/// The path of the all the files generated by cargo-leptos. This defaults to '.' for convenience when integrating with other
|
||||
/// tools.
|
||||
#[builder(setter(into), default=".".to_string())]
|
||||
pub site_root: String,
|
||||
/// The path of the WASM and JS files generated by wasm-bindgen from the root of your app
|
||||
/// By default, wasm-bindgen puts them in `pkg`.
|
||||
@@ -46,6 +50,28 @@ pub struct LeptosOptions {
|
||||
pub reload_port: u32,
|
||||
}
|
||||
|
||||
impl LeptosOptions {
|
||||
fn try_from_env() -> Result<Self, LeptosConfigError> {
|
||||
Ok(LeptosOptions {
|
||||
output_name: std::env::var("LEPTOS_OUTPUT_NAME")
|
||||
.map_err(|e| LeptosConfigError::EnvVarError(format!("LEPTOS_OUTPUT_NAME: {e}")))?,
|
||||
site_root: env_w_default("LEPTOS_SITE_ROOT", "target/site")?,
|
||||
site_pkg_dir: env_w_default("LEPTOS_SITE_PKG_DIR", "pkg")?,
|
||||
env: Env::default(),
|
||||
site_address: env_w_default("LEPTOS_SITE_ADDR", "127.0.0.1:3000")?.parse()?,
|
||||
reload_port: env_w_default("LEPTOS_RELOAD_PORT", "3001")?.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn env_w_default(key: &str, default: &str) -> Result<String, LeptosConfigError> {
|
||||
match std::env::var(key) {
|
||||
Ok(val) => Ok(val),
|
||||
Err(VarError::NotPresent) => Ok(default.to_string()),
|
||||
Err(e) => Err(LeptosConfigError::EnvVarError(format!("{key}: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum that can be used to define the environment Leptos is running in. Can be passed to [RenderOptions].
|
||||
/// Setting this to the `PROD` variant will not include the websockets code for `cargo-leptos` watch mode.
|
||||
/// Defaults to `DEV`.
|
||||
@@ -119,46 +145,48 @@ impl TryFrom<String> for Env {
|
||||
"prod" => Ok(Self::PROD),
|
||||
"production" => Ok(Self::PROD),
|
||||
other => Err(format!(
|
||||
"{} is not a supported environment. Use either `dev` or `production`.",
|
||||
other
|
||||
"{other} is not a supported environment. Use either `dev` or `production`."
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads [LeptosOptions] from a Cargo.toml with layered overrides. If an env var is specified, like `LEPTOS_ENV`,
|
||||
/// it will override a setting in the file.
|
||||
/// it will override a setting in the file. It takes in an optional path to a Cargo.toml file. If None is provided,
|
||||
/// you'll need to set the options as environment variables or rely on the defaults. This is the preferred
|
||||
/// approach for cargo-leptos. If Some("./Cargo.toml") is provided, Leptos will read in the settings itself. This
|
||||
/// option currently does not allow dashes in file or foldernames, as all dashes become underscores
|
||||
pub async fn get_configuration(path: Option<&str>) -> Result<ConfFile, LeptosConfigError> {
|
||||
// Allow Cargo.toml path to be specified in case of workspace wonkiness
|
||||
let text = match path {
|
||||
Some(p) => fs::read_to_string(p).map_err(|_| LeptosConfigError::ConfigNotFound)?,
|
||||
None => fs::read_to_string("Cargo.toml").map_err(|_| LeptosConfigError::ConfigNotFound)?,
|
||||
};
|
||||
if let Some(path) = path {
|
||||
let text = fs::read_to_string(path).map_err(|_| LeptosConfigError::ConfigNotFound)?;
|
||||
|
||||
let re: Regex =
|
||||
Regex::new(r#"(?m)^\[package.metadata.leptos\]|(?m)^\[\[workspace.metadata.leptos\]\]"#)
|
||||
.unwrap();
|
||||
let start = match re.find(&text) {
|
||||
Some(found) => found.start(),
|
||||
None => return Err(LeptosConfigError::ConfigSectionNotFound),
|
||||
};
|
||||
let re: Regex = Regex::new(r#"(?m)^\[package.metadata.leptos\]"#).unwrap();
|
||||
let start = match re.find(&text) {
|
||||
Some(found) => found.start(),
|
||||
None => return Err(LeptosConfigError::ConfigSectionNotFound),
|
||||
};
|
||||
|
||||
// so that serde error messages have right line number
|
||||
let newlines = text[..start].matches('\n').count();
|
||||
let input = "\n".repeat(newlines) + &text[start..];
|
||||
let toml = input
|
||||
.replace("[package.metadata.leptos]", "[leptos_options]")
|
||||
.replace("[[workspace.metadata.leptos]]", "[leptos_options]")
|
||||
.replace('-', "_");
|
||||
let settings = Config::builder()
|
||||
// Read the "default" configuration file
|
||||
.add_source(File::from_str(&toml, FileFormat::Toml))
|
||||
// Layer on the environment-specific values.
|
||||
// Add in settings from environment variables (with a prefix of LEPTOS and '_' as separator)
|
||||
// E.g. `LEPTOS_RELOAD_PORT=5001 would set `LeptosOptions.reload_port`
|
||||
.add_source(config::Environment::with_prefix("LEPTOS").separator("_"))
|
||||
.build()?;
|
||||
// so that serde error messages have right line number
|
||||
let newlines = text[..start].matches('\n').count();
|
||||
let input = "\n".repeat(newlines) + &text[start..];
|
||||
let toml = input
|
||||
.replace("[package.metadata.leptos]", "[leptos_options]")
|
||||
.replace('-', "_");
|
||||
let settings = Config::builder()
|
||||
// Read the "default" configuration file
|
||||
.add_source(File::from_str(&toml, FileFormat::Toml))
|
||||
// Layer on the environment-specific values.
|
||||
// Add in settings from environment variables (with a prefix of LEPTOS and '_' as separator)
|
||||
// E.g. `LEPTOS_RELOAD_PORT=5001 would set `LeptosOptions.reload_port`
|
||||
.add_source(config::Environment::with_prefix("LEPTOS").separator("_"))
|
||||
.build()?;
|
||||
|
||||
settings
|
||||
.try_deserialize()
|
||||
.map_err(|e| LeptosConfigError::ConfigError(e.to_string()))
|
||||
settings
|
||||
.try_deserialize()
|
||||
.map_err(|e| LeptosConfigError::ConfigError(e.to_string()))
|
||||
} else {
|
||||
Ok(ConfFile {
|
||||
leptos_options: LeptosOptions::try_from_env()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
[package]
|
||||
name = "leptos_dom"
|
||||
version = "0.1.0-alpha"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "DOM operations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
drain_filter_polyfill = "0.1"
|
||||
educe = "0.4"
|
||||
futures = "0.3"
|
||||
gloo = "0.8"
|
||||
gloo = { version = "0.8", features = ["futures"] }
|
||||
html-escape = "0.2"
|
||||
indexmap = "1.9"
|
||||
itertools = "0.10"
|
||||
js-sys = "0.3"
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.1.0-alpha" }
|
||||
leptos_reactive = { workspace = true }
|
||||
once_cell = "1"
|
||||
pad-adapter = "0.1"
|
||||
paste = "1"
|
||||
rustc-hash = "1.1.0"
|
||||
@@ -37,6 +39,7 @@ features = [
|
||||
"Range",
|
||||
"Text",
|
||||
"HtmlCollection",
|
||||
"TreeWalker",
|
||||
|
||||
# Events we cast to in leptos_macro -- added here so we don't force users to import them
|
||||
"AnimationEvent",
|
||||
@@ -134,10 +137,15 @@ features = [
|
||||
"HtmlMenuElement",
|
||||
"HtmlSlotElement",
|
||||
"HtmlTemplateElement",
|
||||
"HtmlOptionElement"
|
||||
"HtmlOptionElement",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
web = ["leptos_reactive/csr"]
|
||||
ssr = ["leptos_reactive/ssr"]
|
||||
stable = ["leptos_reactive/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
skip_feature_sets = [["web", "ssr"]]
|
||||
|
||||
@@ -6,8 +6,10 @@ edition = "2021"
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
gloo = { version = "0.8", features = ["futures"] }
|
||||
leptos = { path = "../../../leptos" }
|
||||
leptos = { path = "../../../leptos", features = ["tracing"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = "0.3"
|
||||
web-sys = "0.3"
|
||||
|
||||
[workspace]
|
||||
|
||||