Compare commits

...

37 Commits

Author SHA1 Message Date
Greg Johnston
3665c5227e meta 2023-07-24 09:48:53 -04:00
Greg Johnston
653432f827 more meta 2023-07-24 08:24:59 -04:00
Greg Johnston
05a863edb7 fix meta flags 2023-07-24 08:07:09 -04:00
Greg Johnston
ab28d26bba fix 2023-07-23 18:21:23 -04:00
Greg Johnston
da3ae03422 reorganize 2023-07-23 17:58:39 -04:00
Greg Johnston
89140b0c8d tweak 2023-07-23 17:45:35 -04:00
Greg Johnston
3fb34710e5 perf: don't include unused hydration code in CSR bundles 2023-07-22 20:33:32 -04:00
Greg Johnston
866212c2ae stash 2023-07-22 20:27:32 -04:00
Greg Johnston
39bf38d1e4 docs: CONTRIBUTING.md with helpful info re: CI (#1415) 2023-07-22 08:26:46 -04:00
Joshua Marsh
e6fd1379b8 docs: fix typo for WrapsChildren (#1402) 2023-07-22 08:26:36 -04:00
Doug A
1d9931a5a8 docs: typo in 01_basic_component.md (#1412)
typo fix
2023-07-22 08:24:30 -04:00
Greg Johnston
06164d34b5 docs: note about typed params on stable (#1414) 2023-07-22 08:23:05 -04:00
Joseph Cruz
f3de288e19 build(examples): make it easy to see which examples do what kind of testing (#1411)
* build(examples): list example projects with CI tests runners

* build(examples): add show all mode
2023-07-22 08:13:49 -04:00
Greg Johnston
62bf315059 fix: <use_/> as typed top-level element in view (#1410) 2023-07-21 10:07:34 -04:00
Greg Johnston
011c97e3a4 fix: closing element names wrong for svg::, math::, and use_ (closes #1403) (#1406) 2023-07-20 17:03:30 -04:00
Greg Johnston
2ca3d2c7a4 fix: RawText/unquoted text nodes in SSR (closes #1384) (#1407) 2023-07-20 17:03:19 -04:00
Greg Johnston
cc52c94348 docs/examples: use shorthand form for <Route/> views when possible (#1375) 2023-07-20 16:28:43 -04:00
Andrew Grande
4b8cc96dfa docs: typo in 16_routes.md
Fixed grammar
2023-07-20 16:13:25 -04:00
Greg Johnston
338d2ab839 Merge pull request #1379 from agilarity/lint-with-clippy
ci: lint with clippy
2023-07-20 14:15:16 -04:00
Greg Johnston
54fc6da24e feat: implement Resource::dispose() (#1393) 2023-07-20 14:14:49 -04:00
Andrew Grande
825b3fb858 chore: typo in README.md (#1399) 2023-07-20 14:00:43 -04:00
Andrew Grande
fd0212a142 docs: typo in 15_global_state.md (#1395)
Proofreading
2023-07-20 08:57:12 -04:00
Greg Johnston
3b397cb39c examples: remove random <form> (#1398) 2023-07-20 08:56:49 -04:00
martin frances
1e002c2c2f chore: Removed call to .into(), plus minor touch to docs. (#1396) 2023-07-20 08:07:31 -04:00
Greg Johnston
8f45daeca8 docs: correct docs for create_memo to reflect laziness (#1388) 2023-07-19 14:50:34 -04:00
Joseph Cruz
105ef989b7 ci: install clippy 2023-07-19 08:54:05 -04:00
Greg Johnston
9e7c31d1e4 docs: small issues (#1385) 2023-07-19 08:53:37 -04:00
Joseph Cruz
771dfa6b68 ci: lint with clippy 2023-07-19 08:44:36 -04:00
Joseph Cruz
fb52cfa73e fix: needless_raw_string_hashes 2023-07-19 08:43:57 -04:00
Ari Seyhun
b2c75d215b chore: remove unnecessary string allocation in TryFrom for Url (#1376) 2023-07-19 07:04:06 -04:00
Andrew Grande
951607de74 docs: typos
* Fixed wording

* Update ARCHITECTURE.md

Fixed superfluous whitespace
2023-07-19 07:03:50 -04:00
Joseph Cruz
122fd2bc74 fix: useless_conversion 2023-07-18 20:56:55 -04:00
Joseph Cruz
f102125d3c fix: needless_borrow 2023-07-18 20:56:55 -04:00
Joseph Cruz
14bda76b30 fix: needless_raw_string_hashes (allow) 2023-07-18 20:56:39 -04:00
Joseph Cruz
3af115a663 fix: maybe_misused_cfg 2023-07-18 20:56:39 -04:00
Joseph Cruz
a344804734 fix: incorrect_clone_impl_on_copy_type (allow) 2023-07-18 20:54:51 -04:00
Joseph Cruz
d8eaa5c004 build: use cargo hack for clippy 2023-07-18 08:02:24 -04:00
44 changed files with 324 additions and 191 deletions

View File

@@ -220,8 +220,8 @@ for reference: they include large amounts of manual SSR route handling, etc.
## `cargo-leptos` helpers
`leptos_config` and `leptos_hot_reload` exist to support two different features
of `cargo-leptos`, namely its configuration and its view-patching/hot-
reloading features.
of `cargo-leptos`, namely its configuration and its view-patching/hot-reloading
features.
Its important to say that the main feature `cargo-leptos` remains its ability
to conveniently tie together different build tooling, compiling your app to

View File

@@ -70,6 +70,25 @@ are a few guidelines that will make it a better experience for everyone:
`cargo-make` and using `cargo make check && cargo make test && cargo make
check-examples`.
## Before Submitting a PR
We have a fairly extensive CI setup that runs both lints (like `rustfmt` and `clippy`)
and tests on PRs. You can run most of these locally if you have `cargo-make` installed.
If you added an example, make sure to add it to the list in `examples/Makefile.toml`.
From the root directory of the repo, run
- `cargo +nightly fmt`
- `cargo +nightly make check`
- `cargo +nightly make test`
- `cargo +nightly make check-examples`
- `cargo +nightly make --profile=github-actions ci`
If you modified an example:
- `cd examples/your_example`
- `cargo +nightly fmt -- --config-path ../..`
- `cargo +nightly make --profile=github-actions verify-flow`
## Architecture
See [ARCHITECTURE.md](./ARCHITECTURE.md).

View File

@@ -107,7 +107,7 @@ Open browser to [http://localhost:3000/](http://localhost:3000/).
### Whats up with the name?
_Leptos_ (λεπτός) is an ancient Greek word meaning “thin, light, refine, fine-grained.” To me, a classicist and not a dog owner, it evokes the lightweight reactive system that powers the framework. I've since learned the same word is at the root of the medical term “leptospirosis,” a blood infection that affects humans and animals... My bad. No dogs were harmed in the creation of this framework.
_Leptos_ (λεπτός) is an ancient Greek word meaning “thin, light, refined, fine-grained.” To me, a classicist and not a dog owner, it evokes the lightweight reactive system that powers the framework. I've since learned the same word is at the root of the medical term “leptospirosis,” a blood infection that affects humans and animals... My bad. No dogs were harmed in the creation of this framework.
### Is it production ready?

View File

@@ -1,9 +1,11 @@
[tasks.pre-clippy]
env = { CARGO_MAKE_CLIPPY_ARGS = "--all-targets --all-features -- -D warnings" }
[tasks.check-style]
dependencies = ["check-format-flow", "clippy-flow"]
[tasks.lint]
dependencies = ["check-format-flow", "clippy-each-feature"]
[tasks.check-format]
env = { LEPTOS_PROJECT_DIRECTORY = "../" }
args = ["fmt", "--", "--check", "--config-path", "${LEPTOS_PROJECT_DIRECTORY}"]
[tasks.clippy-each-feature]
dependencies = ["install-clippy"]
command = "cargo"
args = ["hack", "clippy", "--all", "--each-feature", "--no-dev-deps"]

View File

@@ -13,6 +13,3 @@ RUSTFLAGS = "-D warnings"
[tasks.ci]
dependencies = ["lint", "test"]
[tasks.lint]
dependencies = ["check-format-flow"]

View File

@@ -136,7 +136,7 @@ view! { cx,
In this example, clicking the button will cause the text inside `<p>` to be updated, cloning `state.name` again! Because signals are the atomic unit of reactivity, updating any field of the signal triggers updates to everything that depends on the signal.
Theres a better way. You can use take fine-grained, reactive slices by using [`create_memo`](https://docs.rs/leptos/latest/leptos/fn.create_memo.html) or [`create_slice`](https://docs.rs/leptos/latest/leptos/fn.create_slice.html) (which uses `create_memo` but also provides a setter). “Memoizing” a value means creating a new reactive value which will only update when it changes. “Memoizing a slice” means creating a new reactive value which will only update when some field of the state struct updates.
Theres a better way. You can take fine-grained, reactive slices by using [`create_memo`](https://docs.rs/leptos/latest/leptos/fn.create_memo.html) or [`create_slice`](https://docs.rs/leptos/latest/leptos/fn.create_slice.html) (which uses `create_memo` but also provides a setter). “Memoizing” a value means creating a new reactive value which will only update when it changes. “Memoizing a slice” means creating a new reactive value which will only update when some field of the state struct updates.
Here, instead of reading from the state signal directly, we create “slices” of that state with fine-grained updates via `create_slice`. Each slice signal only updates when the particular piece of the larger struct it accesses updates. This means you can create a single root signal, and then take independent, fine-grained slices of it in different components, each of which can update without notifying the others of changes.

View File

@@ -68,7 +68,7 @@ pub fn SimpleCounter(cx: Scope) -> impl IntoView {
The `SimpleCounter` function itself runs once. The `value` signal is created once. The framework hands off the `increment` function to the browser as an event listener. When you click the button, the browser calls `increment`, which updates `value` via `set_value`. And that updates the single text node represented in our view by `{value}`.
Closures are key to reactivity. They provide the framework with the ability to rerun the smallest possible unit of your application in responsive to a change.
Closures are key to reactivity. They provide the framework with the ability to rerun the smallest possible unit of your application in response to a change.
So remember two things:

View File

@@ -24,7 +24,7 @@ use leptos_router::*;
Routing behavior is provided by the [`<Router/>`](https://docs.rs/leptos_router/latest/leptos_router/fn.Router.html) component. This should usually be somewhere near the root of your application, the rest of the app.
> You shouldnt try to use multiple `<Router/>`s in your app. Remember that the router drives global state: if you have multiple routers, which ones decides what to do when the URL changes?
> You shouldnt try to use multiple `<Router/>`s in your app. Remember that the router drives global state: if you have multiple routers, which one decides what to do when the URL changes?
Lets start with a simple `<App/>` component using the router:
@@ -87,15 +87,17 @@ The `view` is a function that takes a `Scope` and returns a view.
```rust
<Routes>
<Route path="/" view=|cx| view! { cx, <Home/> }/>
<Route path="/users" view=|cx| view! { cx, <Users/> }/>
<Route path="/users/:id" view=|cx| view! { cx, <UserProfile/> }/>
<Route path="/*any" view=|cx| view! { cx, <NotFound/> }/>
<Route path="/" view=Home/>
<Route path="/users" view=Users/>
<Route path="/users/:id" view=UserProfile/>
<Route path="/*any" view=NotFound/>
</Routes>
```
> The router scores each route to see how good a match it is, so you can define your routes in any order.
> `view` takes a `Fn(Scope) -> impl IntoView`. If a component has no props, it is a function that takes `Scope` and returns `impl IntoView`, so it can be passed directly into the `view`. In this case, `view=Home` is just a shorthand for `|cx| view! { cx, <Home/> }`.
Now if you navigate to `/` or to `/users` youll get the home page or the `<Users/>`. If you go to `/users/3` or `/blahblah` youll get a user profile or your 404 page (`<NotFound/>`). On every navigation, the router determines which `<Route/>` should be matched, and therefore what content should be displayed where the `<Routes/>` component is defined.
Note that you can define your routes in any order. The router scores each route to see how good a match it is, rather than simply trying to match them top to bottom.
Simple enough?

View File

@@ -4,10 +4,10 @@ We just defined the following set of routes:
```rust
<Routes>
<Route path="/" view=|cx| view! { cx, <Home /> }/>
<Route path="/users" view=|cx| view! { cx, <Users /> }/>
<Route path="/users/:id" view=|cx| view! { cx, <UserProfile /> }/>
<Route path="/*any" view=|cx| view! { cx, <NotFound /> }/>
<Route path="/" view=Home
<Route path="/users" view=Users
<Route path="/users/:id" view=UserProfile
<Route path="/*any" view=NotFound
</Routes>
```
@@ -17,11 +17,11 @@ Well... you can!
```rust
<Routes>
<Route path="/" view=|cx| view! { cx, <Home /> }/>
<Route path="/users" view=|cx| view! { cx, <Users /> }>
<Route path=":id" view=|cx| view! { cx, <UserProfile /> }/>
<Route path="/" view=Home
<Route path="/users" view=Users
<Route path=":id" view=UserProfile
</Route>
<Route path="/*any" view=|cx| view! { cx, <NotFound /> }/>
<Route path="/*any" view=NotFound
</Routes>
```
@@ -39,8 +39,8 @@ Lets look back at our practical example.
```rust
<Routes>
<Route path="/users" view=|cx| view! { cx, <Users /> }/>
<Route path="/users/:id" view=|cx| view! { cx, <UserProfile /> }/>
<Route path="/users" view=Users
<Route path="/users/:id" view=UserProfile
</Routes>
```
@@ -53,8 +53,8 @@ Lets say I use nested routes instead:
```rust
<Routes>
<Route path="/users" view=|cx| view! { cx, <Users /> }>
<Route path=":id" view=|cx| view! { cx, <UserProfile /> }/>
<Route path="/users" view=Users
<Route path=":id" view=UserProfile
</Route>
</Routes>
```
@@ -68,9 +68,9 @@ I actually need to add a fallback route
```rust
<Routes>
<Route path="/users" view=|cx| view! { cx, <Users /> }>
<Route path=":id" view=|cx| view! { cx, <UserProfile /> }/>
<Route path="" view=|cx| view! { cx, <NoUser /> }/>
<Route path="/users" view=Users
<Route path=":id" view=UserProfile
<Route path="" view=NoUser
</Route>
</Routes>
```
@@ -94,8 +94,8 @@ You can easily define this with nested routes
```rust
<Routes>
<Route path="/contacts" view=|cx| view! { cx, <ContactList/> }>
<Route path=":id" view=|cx| view! { cx, <ContactInfo/> }/>
<Route path="/contacts" view=ContactList
<Route path=":id" view=ContactInfo
<Route path="" view=|cx| view! { cx,
<p>"Select a contact to view more info."</p>
}/>
@@ -107,11 +107,11 @@ You can go even deeper. Say you want to have tabs for each contacts address,
```rust
<Routes>
<Route path="/contacts" view=|cx| view! { cx, <ContactList/> }>
<Route path=":id" view=|cx| view! { cx, <ContactInfo/> }>
<Route path="" view=|cx| view! { cx, <EmailAndPhone/> }/>
<Route path="address" view=|cx| view! { cx, <Address/> }/>
<Route path="messages" view=|cx| view! { cx, <Messages/> }/>
<Route path="/contacts" view=ContactList
<Route path=":id" view=ContactInfo
<Route path="" view=EmailAndPhone
<Route path="address" view=Address
<Route path="messages" view=Messages
</Route>
<Route path="" view=|cx| view! { cx,
<p>"Select a contact to view more info."</p>
@@ -201,12 +201,9 @@ fn App(cx: Scope) -> impl IntoView {
// /contacts has nested routes
<Route
path="/contacts"
view=|cx| view! { cx, <ContactList/> }
>
view=ContactList
// if no id specified, fall back
<Route path=":id" view=|cx| view! { cx,
<ContactInfo/>
}>
<Route path=":id" view=ContactInfo
<Route path="" view=|cx| view! { cx,
<div class="tab">
"(Contact Info)"

View File

@@ -36,6 +36,12 @@ struct ContactSearch {
```
> Note: The `Params` derive macro is located at `leptos::Params`, and the `Params` trait is at `leptos_router::Params`. If you avoid using glob imports like `use leptos::*;`, make sure youre importing the right one for the derive macro.
>
> If you are not using the `nightly` feature, you will get the error
> ```
> no function or associated item named `into_param` found for struct `std::string::String` in the current scope
> ```
> At the moment, supporting both `T: FromStr` and `Option<T>` for typed params requires a nightly feature. You can fix this by simply changing the struct to use `q: Option<String>` instead of `q: String`.
Now we can use them in a component. Imagine a URL that has both params and a query, like `/contacts/:id?q=Search`.
@@ -108,12 +114,9 @@ fn App(cx: Scope) -> impl IntoView {
// /contacts has nested routes
<Route
path="/contacts"
view=|cx| view! { cx, <ContactList/> }
>
view=ContactList
// if no id specified, fall back
<Route path=":id" view=|cx| view! { cx,
<ContactInfo/>
}>
<Route path=":id" view=ContactInfo
<Route path="" view=|cx| view! { cx,
<div class="tab">
"(Contact Info)"

View File

@@ -52,12 +52,9 @@ fn App(cx: Scope) -> impl IntoView {
// /contacts has nested routes
<Route
path="/contacts"
view=|cx| view! { cx, <ContactList/> }
>
view=ContactList
// if no id specified, fall back
<Route path=":id" view=|cx| view! { cx,
<ContactInfo/>
}>
<Route path=":id" view=ContactInfo
<Route path="" view=|cx| view! { cx,
<div class="tab">
"(Contact Info)"

View File

@@ -80,7 +80,7 @@ fn App(cx: Scope) -> impl IntoView {
<h1><code>"<Form/>"</code></h1>
<main>
<Routes>
<Route path="" view=|cx| view! { cx, <FormExample/> }/>
<Route path="" view=FormExample
</Routes>
</main>
</Router>

View File

@@ -64,7 +64,7 @@ If youre using server-side rendering, the synchronous mode is almost never wh
5. **Partially-blocked streaming**: “Partially-blocked” streaming is useful when you have multiple separate `<Suspense/>` components on the page. If one of them reads from one or more “blocking resources” (see below), the fallback will not be sent; rather, the server will wait until that `<Suspense/>` has resolved and then replace the fallback with the resolved fragment on the server, which means that it is included in the initial HTML response and appears even if JavaScript is disabled or not supported. Other `<Suspense/>` stream in out of order as usual.
This is useful when you have multiple `<Suspense/>` on the page, and one is more important than the other: think of a blog post and comments, or product information and reviews. It is *not* useful if theres only one `<Suspense/>`, or if every `<Suspense/>` reads from blocking resources. In those cases it is a slower form of `async` rendering.
This is useful when you have multiple `<Suspense/>` on the page, and one is more important than the other: think of a blog post and comments, or product information and reviews. It is _not_ useful if theres only one `<Suspense/>`, or if every `<Suspense/>` reads from blocking resources. In those cases it is a slower form of `async` rendering.
- _Pros_: Works if JavaScript is disabled or not supported on the users device.
- _Cons_
@@ -79,13 +79,13 @@ Because it offers the best blend of performance characteristics, Leptos defaults
```rust
<Routes>
// Well load the home page with out-of-order streaming and <Suspense/>
<Route path="" view=|cx| view! { cx, <HomePage/> }/>
<Route path="" view=HomePage
// We'll load the posts with async rendering, so they can set
// the title and metadata *after* loading the data
<Route
path="/post/:id"
view=|cx| view! { cx, <Post/> }
view=Post
ssr=SsrMode::Async
/>
</Routes>

View File

@@ -139,7 +139,7 @@ view! { cx,
Remember—and this is _very important_—only functions are reactive. This means that
`{count}` and `{count()}` do very different things in your view. `{count}` passes
in a function, telling the framework to update the view every time `count` changes.
`{count()}` access the value of `count` once, and passes an `i32` into the view,
`{count()}` accesses the value of `count` once, and passes an `i32` into the view,
rendering it once, unreactively. You can see the difference in the CodeSandbox below!
Lets make one final change. `set_count(3)` is a pretty useless thing for a click handler to do. Lets replace “set this value to 3” with “increment this value by 1”:

View File

@@ -115,11 +115,11 @@ Calling it like this will create a list:
```rust
view! { cx,
<WrappedChildren>
<WrapsChildren>
"A"
"B"
"C"
</WrappedChildren>
</WrapsChildren>
}
```

View File

@@ -45,3 +45,75 @@ grep -v gtk |
jq -R -s -c 'split("\n")[:-1]')
echo "CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = $examples"
'''
[tasks.test-info]
workspace = false
description = "report ci test runners for each example - Option [all]"
script = '''
BOLD="\e[1m"
GREEN="\e[0;32m"
ITALIC="\e[3m"
YELLOW="\e[0;33m"
RESET="\e[0m"
echo
echo "${YELLOW}CI test runners by example...${RESET}"
echo
examples=$(ls |
grep -v README.md |
grep -v Makefile.toml |
grep -v cargo-make |
grep -v gtk |
sort -u |
awk '{print $0 ", "}')
example_root_dir=$(pwd)
for example_dir in $examples
do
clean_name=$(echo $example_dir | sed 's%,%%')
cd $clean_name
c_tests=$(grep -rl --fixed-strings "#[test]" | wc -l)
rs_tests=$(grep -rl --fixed-strings "#[rstest]" | wc -l)
w_configs=$(grep -rl "\/wasm-test.toml\"" | wc -l)
pw_configs=$(grep -rl "\/playwright-test.toml\"" | wc -l)
cl_configs=$(grep -rl "\/cargo-leptos-test.toml\"" | wc -l)
test_runner=
if [ $c_tests -gt 0 ]; then
test_runner="-C"
fi
if [ $rs_tests -gt 0 ]; then
test_runner=$test_runner"-R"
fi
if [ $w_configs -gt 0 ]; then
test_runner=$test_runner"-W"
fi
if [ $pw_configs -gt 0 ]; then
test_runner=$test_runner"-P"
fi
if [ $cl_configs -gt 0 ]; then
test_runner=$test_runner"-L"
fi
if [ ! -z "$1" ]; then
# Show all examples
echo "$clean_name ${BOLD}${test_runner}${RESET}"
elif [ ! -z $test_runner ]; then
# Filter out examples that do not run tests in `ci`
echo "$clean_name ${BOLD}${test_runner}${RESET}"
fi
cd $example_root_dir
done
echo
echo "${ITALIC}Test Runners: C = Cargo Test, L = Cargo Leptos Test, P = Playwright Test, R = RS Test, W = WASM Test${RESET}"
echo
'''

View File

@@ -34,7 +34,7 @@ pub fn App(cx: Scope) -> impl IntoView {
</header>
<main>
<Routes>
<Route path="" view=|cx| view! { cx, <ExampleErrors/> }/>
<Route path="" view=ExampleErrors/>
</Routes>
</main>
</Router>

View File

@@ -170,7 +170,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
<hr/>
<main>
<Routes>
<Route path="" view=|cx| view! { cx, <Todos/> }/> //Route
<Route path="" view=Todos/> //Route
<Route path="signup" view=move |cx| view! {
cx,
<Signup action=signup/>

View File

@@ -104,7 +104,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
</header>
<main>
<Routes>
<Route path="" view=|cx| view! { cx, <Todos/> }/>
<Route path="" view=Todos/>
</Routes>
</main>
</Router>
@@ -126,10 +126,6 @@ pub fn Todos(cx: Scope) -> impl IntoView {
view! {
cx,
<form method="POST" action="/weird">
<input type="text" name="hi" value="John"/>
<input type="submit"/>
</form>
<div>
<MultiActionForm action=add_todo>
<label>

View File

@@ -104,10 +104,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
</header>
<main>
<Routes>
<Route path="" view=|cx| view! {
cx,
<Todos/>
}/> //Route
<Route path="" view=Todos/> //Route
</Routes>
</main>
</Router>

View File

@@ -52,7 +52,7 @@ pub fn html_parts(
let pkg_path = &options.site_pkg_dir;
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
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to maintain 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 at compile time
// Otherwise we need to add _bg because wasm_pack always does.
let mut wasm_output_name = output_name.clone();
@@ -60,7 +60,7 @@ pub fn html_parts(
wasm_output_name.push_str("_bg");
}
let leptos_autoreload = autoreload("".into(), options);
let leptos_autoreload = autoreload("", options);
let html_metadata =
meta.and_then(|mc| mc.html.as_string()).unwrap_or_default();
@@ -94,7 +94,7 @@ pub fn html_parts_separated(
.map(|nonce| format!(" nonce=\"{nonce}\""))
.unwrap_or_default();
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to maintain 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 at compile time
// Otherwise we need to add _bg because wasm_pack always does.
let mut wasm_output_name = output_name.clone();

View File

@@ -152,6 +152,7 @@ pub use leptos_config::{self, get_configuration, LeptosOptions};
any(feature = "csr", feature = "hydrate")
)))]
/// Utilities for server-side rendering HTML.
#[cfg(any(doc, not(feature = "csr")))]
pub mod ssr {
pub use leptos_dom::{ssr::*, ssr_in_order::*};
}

View File

@@ -76,7 +76,10 @@ where
let current_id = current_id;
let children = Rc::new(orig_children(cx).into_view(cx));
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
#[cfg(all(
feature = "ssr",
not(any(feature = "csr", feature = "hydrate"))
))]
let orig_children = Rc::clone(&orig_children);
move || {
#[cfg(any(feature = "csr", feature = "hydrate"))]
@@ -108,6 +111,7 @@ where
else {
HydrationCtx::continue_from(current_id);
#[cfg(feature = "ssr")]
cx.register_suspense(
context,
&current_id.to_string(),

View File

@@ -52,12 +52,12 @@ pub fn App(cx: Scope) -> impl IntoView {
<Outlet/>
}
>
<Route path="" view=|cx| view! { cx, <Nested/> }/>
<Route path="inside" view=|cx| view! { cx, <NestedResourceInside/> }/>
<Route path="single" view=|cx| view! { cx, <Single/> }/>
<Route path="parallel" view=|cx| view! { cx, <Parallel/> }/>
<Route path="inside-component" view=|cx| view! { cx, <InsideComponent/> }/>
<Route path="none" view=|cx| view! { cx, <None/> }/>
<Route path="" view=Nested
<Route path="inside" view=NestedResourceInside
<Route path="single" view=Single
<Route path="parallel" view=Parallel
<Route path="inside-component" view=InsideComponent
<Route path="none" view=None
</Route>
// in-order
<Route
@@ -69,12 +69,12 @@ pub fn App(cx: Scope) -> impl IntoView {
<Outlet/>
}
>
<Route path="" view=|cx| view! { cx, <Nested/> }/>
<Route path="inside" view=|cx| view! { cx, <NestedResourceInside/> }/>
<Route path="single" view=|cx| view! { cx, <Single/> }/>
<Route path="parallel" view=|cx| view! { cx, <Parallel/> }/>
<Route path="inside-component" view=|cx| view! { cx, <InsideComponent/> }/>
<Route path="none" view=|cx| view! { cx, <None/> }/>
<Route path="" view=Nested
<Route path="inside" view=NestedResourceInside
<Route path="single" view=Single
<Route path="parallel" view=Parallel
<Route path="inside-component" view=InsideComponent
<Route path="none" view=None
</Route>
// async
<Route
@@ -86,12 +86,12 @@ pub fn App(cx: Scope) -> impl IntoView {
<Outlet/>
}
>
<Route path="" view=|cx| view! { cx, <Nested/> }/>
<Route path="inside" view=|cx| view! { cx, <NestedResourceInside/> }/>
<Route path="single" view=|cx| view! { cx, <Single/> }/>
<Route path="parallel" view=|cx| view! { cx, <Parallel/> }/>
<Route path="inside-component" view=|cx| view! { cx, <InsideComponent/> }/>
<Route path="none" view=|cx| view! { cx, <None/> }/>
<Route path="" view=Nested
<Route path="inside" view=NestedResourceInside
<Route path="single" view=Single
<Route path="parallel" view=Parallel
<Route path="inside-component" view=InsideComponent
<Route path="none" view=None
</Route>
</Routes>
</main>

View File

@@ -179,9 +179,9 @@ impl TryFrom<String> for Env {
/// Loads [LeptosOptions] from a Cargo.toml text content with layered overrides.
/// If an env var is specified, like `LEPTOS_ENV`, it will override a setting in the file.
pub fn get_config_from_str(text: &str) -> Result<ConfFile, LeptosConfigError> {
let re: Regex = Regex::new(r#"(?m)^\[package.metadata.leptos\]"#).unwrap();
let re: Regex = Regex::new(r"(?m)^\[package.metadata.leptos\]").unwrap();
let re_workspace: Regex =
Regex::new(r#"(?m)^\[\[workspace.metadata.leptos\]\]"#).unwrap();
Regex::new(r"(?m)^\[\[workspace.metadata.leptos\]\]").unwrap();
let metadata_name;
let start;

View File

@@ -196,7 +196,7 @@ impl TimeoutHandle {
/// Executes the given function after the given duration of time has passed.
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all, fields(duration = ?duration))
)]
pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
@@ -206,7 +206,7 @@ pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
/// Executes the given function after the given duration of time has passed, returning a cancelable handle.
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all, fields(duration = ?duration))
)]
#[inline(always)]
@@ -325,11 +325,10 @@ impl IntervalHandle {
}
}
/// Repeatedly calls the given function, with a delay of the given duration between calls,
/// returning a cancelable handle.
/// Repeatedly calls the given function, with a delay of the given duration between calls.
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all, fields(duration = ?duration))
)]
pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
@@ -340,7 +339,7 @@ pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
/// returning a cancelable handle.
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all, fields(duration = ?duration))
)]
#[inline(always)]

View File

@@ -1,3 +1,4 @@
#![allow(clippy::incorrect_clone_impl_on_copy_type)]
#![deny(missing_docs)]
#![forbid(unsafe_code)]
#![cfg_attr(feature = "nightly", feature(fn_traits))]
@@ -20,7 +21,9 @@ pub mod math;
mod node_ref;
/// Utilities for exporting nonces to be used for a Content Security Policy.
pub mod nonce;
#[cfg(not(feature = "csr"))]
pub mod ssr;
#[cfg(not(feature = "csr"))]
pub mod ssr_in_order;
pub mod svg;
mod transparent;

View File

@@ -234,6 +234,7 @@ impl View {
self.into_stream_chunks_helper(cx, &mut chunks, false);
chunks
}
#[tracing::instrument(level = "trace", skip_all)]
fn into_stream_chunks_helper(
self,

View File

@@ -351,7 +351,10 @@ fn root_element_to_tokens_ssr(
Ident::new("Custom", Span::call_site())
} else {
let camel_cased = camel_case_tag_name(
&tag_name.replace("svg::", "").replace("math::", ""),
tag_name
.trim_start_matches("svg::")
.trim_start_matches("math::")
.trim_end_matches('_'),
);
Ident::new(&camel_cased, Span::call_site())
};
@@ -517,6 +520,17 @@ fn element_to_tokens_ssr(
&value.replace('{', "\\{").replace('}', "\\}"),
);
}
Node::RawText(r) => {
let value = r.to_string_best();
let value = if is_script_or_style {
value.into()
} else {
html_escape::encode_safe(&value)
};
template.push_str(
&value.replace('{', "\\{").replace('}', "\\}"),
);
}
Node::Block(NodeBlock::ValidBlock(block)) => {
if let Some(value) =
block_to_primitive_expression(block)
@@ -551,7 +565,7 @@ fn element_to_tokens_ssr(
}
template.push_str("</");
template.push_str(&node.name().to_string());
template.push_str(tag_name);
template.push('>');
}
}
@@ -1841,11 +1855,7 @@ fn is_self_closing(node: &NodeElement) -> bool {
fn camel_case_tag_name(tag_name: &str) -> String {
let mut chars = tag_name.chars();
let first = chars.next();
let underscore = if tag_name == "option" || tag_name == "use" {
"_"
} else {
""
};
let underscore = if tag_name == "option" { "_" } else { "" };
first
.map(|f| f.to_ascii_uppercase())
.into_iter()

View File

@@ -1,3 +1,4 @@
#![allow(clippy::incorrect_clone_impl_on_copy_type)]
#![deny(missing_docs)]
#![cfg_attr(feature = "nightly", feature(fn_traits))]
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
@@ -79,6 +80,7 @@ mod context;
#[macro_use]
mod diagnostics;
mod effect;
#[cfg(not(feature = "csr"))]
mod hydration;
mod memo;
mod node;
@@ -100,6 +102,7 @@ mod watch;
pub use context::*;
pub use diagnostics::SpecialNonReactiveZone;
pub use effect::*;
#[cfg(not(feature = "csr"))]
pub use hydration::FragmentData;
pub use memo::*;
pub use resource::*;

View File

@@ -57,15 +57,15 @@ use std::{any::Any, cell::RefCell, fmt, marker::PhantomData, rc::Rc};
/// });
///
/// // instead, we create a memo
/// // 🆗 run #1: the calculation runs once immediately
/// // ✅ creation: the computation does not run on creation, because memos are lazy
/// let memoized = create_memo(cx, move |_| really_expensive_computation(value.get()));
/// create_effect(cx, move |_| {
/// // 🆗 reads the current value of the memo
/// // 🆗 run #1: reading the memo for the first time causes the computation to run for the first time
/// // can be `memoized()` on nightly
/// log::debug!("memoized = {}", memoized.get());
/// });
/// create_effect(cx, move |_| {
/// // ✅ reads the current value **without re-running the calculation**
/// // ✅ reads the current value again **without re-running the calculation**
/// let value = memoized.get();
/// // do something else...
/// });
@@ -149,15 +149,15 @@ where
/// });
///
/// // instead, we create a memo
/// // 🆗 run #1: the calculation runs once immediately
// // ✅ creation: the computation does not run on creation, because memos are lazy
/// let memoized = create_memo(cx, move |_| really_expensive_computation(value.get()));
/// create_effect(cx, move |_| {
/// // 🆗 reads the current value of the memo
/// // 🆗 run #1: reading the memo for the first time causes the computation to run for the first time
/// // can be `memoized()` on nightly
/// log::debug!("memoized = {}", memoized.get());
/// });
/// create_effect(cx, move |_| {
/// // ✅ reads the current value **without re-running the calculation**
/// // can be `memoized()` on nightly
/// // ✅ reads the current value again **without re-running the calculation**
/// let value = memoized.get();
/// // do something else...
/// });

View File

@@ -6,8 +6,8 @@ use crate::{
serialization::Serializable,
spawn::spawn_local,
use_context, GlobalSuspenseContext, Memo, ReadSignal, Scope, ScopeProperty,
SignalGetUntracked, SignalSet, SignalUpdate, SignalWith, SuspenseContext,
WriteSignal,
SignalDispose, SignalGetUntracked, SignalSet, SignalUpdate, SignalWith,
SuspenseContext, WriteSignal,
};
use std::{
any::Any,
@@ -237,7 +237,7 @@ where
id,
source_ty: PhantomData,
out_ty: PhantomData,
#[cfg(any(debug_assertions, features = "ssr"))]
#[cfg(any(debug_assertions, feature = "ssr"))]
defined_at: std::panic::Location::caller(),
}
}
@@ -373,12 +373,12 @@ where
id,
source_ty: PhantomData,
out_ty: PhantomData,
#[cfg(any(debug_assertions, features = "ssr"))]
#[cfg(any(debug_assertions, feature = "ssr"))]
defined_at: std::panic::Location::caller(),
}
}
#[cfg(not(feature = "hydrate"))]
#[cfg(not(all(feature = "hydrate", not(feature = "csr"))))]
fn load_resource<S, T>(_cx: Scope, _id: ResourceId, r: Rc<ResourceState<S, T>>)
where
S: PartialEq + Clone + 'static,
@@ -391,7 +391,7 @@ where
});
}
#[cfg(feature = "hydrate")]
#[cfg(all(feature = "hydrate", not(feature = "csr")))]
fn load_resource<S, T>(cx: Scope, id: ResourceId, r: Rc<ResourceState<S, T>>)
where
S: PartialEq + Clone + 'static,
@@ -719,7 +719,7 @@ where
pub(crate) id: ResourceId,
pub(crate) source_ty: PhantomData<S>,
pub(crate) out_ty: PhantomData<T>,
#[cfg(any(debug_assertions, features = "ssr"))]
#[cfg(any(debug_assertions, feature = "ssr"))]
pub(crate) defined_at: &'static std::panic::Location<'static>,
}
@@ -744,7 +744,7 @@ where
id: self.id,
source_ty: PhantomData,
out_ty: PhantomData,
#[cfg(any(debug_assertions, features = "ssr"))]
#[cfg(any(debug_assertions, feature = "ssr"))]
defined_at: self.defined_at,
}
}
@@ -1093,3 +1093,25 @@ thread_local! {
pub fn suppress_resource_load(suppress: bool) {
SUPPRESS_RESOURCE_LOAD.with(|w| w.set(suppress));
}
impl<S, T> SignalDispose for Resource<S, T>
where
S: 'static,
T: 'static,
{
#[track_caller]
fn dispose(self) {
let res = with_runtime(self.runtime, |runtime| {
let mut resources = runtime.resources.borrow_mut();
resources.remove(self.id)
});
if res.ok().flatten().is_none() {
crate::macros::debug_warn!(
"At {}, you are calling Resource::dispose() on a resource \
that no longer exists, probably because its Scope has \
already been disposed.",
std::panic::Location::caller()
);
}
}
}

View File

@@ -1,7 +1,8 @@
#![forbid(unsafe_code)]
#[cfg(not(feature = "csr"))]
use crate::hydration::SharedContext;
use crate::{
hydration::SharedContext,
node::{NodeId, ReactiveNode, ReactiveNodeState, ReactiveNodeType},
AnyComputation, AnyResource, Effect, Memo, MemoState, ReadSignal,
ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId,
@@ -44,6 +45,7 @@ type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
// and other data included in the reactive system.
#[derive(Default)]
pub(crate) struct Runtime {
#[cfg(not(feature = "csr"))]
pub shared_context: RefCell<SharedContext>,
pub observer: Cell<Option<NodeId>>,
pub scopes: RefCell<SlotMap<ScopeId, RefCell<Vec<ScopeProperty>>>>,
@@ -350,9 +352,12 @@ impl Runtime {
impl Debug for Runtime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Runtime")
.field("shared_context", &self.shared_context)
.field("observer", &self.observer)
let mut s = f.debug_struct("Runtime");
#[cfg(not(feature = "csr"))]
s.field("shared_context", &self.shared_context);
s.field("observer", &self.observer)
.field("scopes", &self.scopes)
.field("scope_parents", &self.scope_parents)
.field("scope_children", &self.scope_children)
@@ -476,7 +481,7 @@ impl RuntimeId {
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]

View File

@@ -1,17 +1,16 @@
#![forbid(unsafe_code)]
use crate::{
console_warn,
hydration::FragmentData,
node::NodeId,
runtime::{with_runtime, RuntimeId},
suspense::StreamChunk,
PinnedFuture, ResourceId, StoredValueId, SuspenseContext,
PinnedFuture, ResourceId, StoredValueId,
};
#[cfg(not(feature = "csr"))]
use crate::{hydration::FragmentData, suspense::StreamChunk, SuspenseContext};
use futures::stream::FuturesUnordered;
use std::{
collections::{HashMap, VecDeque},
fmt,
};
#[cfg(not(feature = "csr"))]
use std::collections::{HashMap, VecDeque};
use std::fmt;
#[doc(hidden)]
#[must_use = "Scope will leak memory if the disposer function is never called"]
@@ -37,7 +36,7 @@ pub fn create_scope(
///
/// You usually don't need to call this manually.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn raw_scope_and_disposer(runtime: RuntimeId) -> (Scope, ScopeDisposer) {
@@ -52,7 +51,7 @@ pub fn raw_scope_and_disposer(runtime: RuntimeId) -> (Scope, ScopeDisposer) {
///
/// You usually don't need to call this manually.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn run_scope<T>(
@@ -69,7 +68,7 @@ pub fn run_scope<T>(
///
/// You usually don't need to call this manually.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn run_scope_undisposed<T>(
@@ -128,7 +127,7 @@ impl Scope {
/// dispose of them when they are no longer needed (e.g., a list item has been destroyed or the user
/// has navigated away from the route.)
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
@@ -147,7 +146,7 @@ impl Scope {
/// dispose of them when they are no longer needed (e.g., a list item has been destroyed or the user
/// has navigated away from the route.)
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
@@ -203,7 +202,7 @@ impl Scope {
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]
@@ -229,7 +228,7 @@ impl Scope {
/// 2. run all cleanup functions defined for this scope by [`on_cleanup`](crate::on_cleanup).
/// 3. dispose of all signals, effects, and resources owned by this `Scope`.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn dispose(self) {
@@ -306,7 +305,7 @@ impl Scope {
})
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub(crate) fn push_scope_property(&self, prop: ScopeProperty) {
@@ -322,7 +321,7 @@ impl Scope {
})
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub(crate) fn remove_scope_property(&self, prop: ScopeProperty) {
@@ -343,7 +342,7 @@ impl Scope {
})
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
/// Returns the the parent Scope, if any.
@@ -361,7 +360,7 @@ impl Scope {
}
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
fn push_cleanup(cx: Scope, cleanup_fn: Box<dyn FnOnce()>) {
@@ -424,7 +423,7 @@ impl ScopeDisposer {
impl Scope {
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn all_resources(&self) -> Vec<ResourceId> {
@@ -435,7 +434,7 @@ impl Scope {
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope that are
/// pending from the server.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn pending_resources(&self) -> Vec<ResourceId> {
@@ -445,7 +444,7 @@ impl Scope {
/// Returns IDs for all [`Resource`](crate::Resource)s found on any scope.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn serialization_resolvers(
@@ -459,8 +458,9 @@ impl Scope {
/// Registers the given [`SuspenseContext`](crate::SuspenseContext) with the current scope,
/// calling the `resolver` when its resources are all resolved.
#[cfg(not(feature = "csr"))]
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn register_suspense(
@@ -516,8 +516,9 @@ impl Scope {
///
/// The keys are hydration IDs. Values are tuples of two pinned
/// `Future`s that return content for out-of-order and in-order streaming, respectively.
#[cfg(not(feature = "csr"))]
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn pending_fragments(&self) -> HashMap<String, FragmentData> {
@@ -529,8 +530,9 @@ impl Scope {
}
/// A future that will resolve when all blocking fragments are ready.
#[cfg(not(feature = "csr"))]
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn blocking_fragments_ready(self) -> PinnedFuture<()> {
@@ -556,8 +558,9 @@ impl Scope {
///
/// Returns a tuple of two pinned `Future`s that return content for out-of-order
/// and in-order streaming, respectively.
#[cfg(not(feature = "csr"))]
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
pub fn take_pending_fragment(&self, id: &str) -> Option<FragmentData> {
@@ -576,7 +579,7 @@ impl Scope {
/// # Panics
/// Panics if the runtime this scope belongs to has already been disposed.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
instrument(level = "trace", skip_all,)
)]
#[inline(always)]

View File

@@ -95,7 +95,7 @@ cfg_if! {
fn de(json: &str) -> Result<Self, SerializationError> {
let intermediate =
serde_json::from_str(&json).map_err(|e| SerializationError::Deserialize(Rc::new(e)))?;
serde_json::from_str(json).map_err(|e| SerializationError::Deserialize(Rc::new(e)))?;
Self::deserialize(&intermediate).map_err(|e| SerializationError::Deserialize(Rc::new(e)))
}
}

View File

@@ -1,9 +1,9 @@
use crate::{Scope, ScopeProperty};
/// A version of [`create_effect`] that listens to any dependency that is accessed inside `deps` and returns
/// A version of [`create_effect`](crate::create_effect) that listens to any dependency that is accessed inside `deps` and returns
/// a stop handler.
/// The return value of `deps` is passed into `callback` as an argument together with the previous value.
/// Additionally the last return value of `callback` is provided as a third argument as is done in [`create_effect`].
/// Additionally the last return value of `callback` is provided as a third argument as is done in [`create_effect`](crate::create_effect).
///
/// ## Usage
///

View File

@@ -1,3 +1,4 @@
#![allow(clippy::incorrect_clone_impl_on_copy_type)]
#![deny(missing_docs)]
#![forbid(unsafe_code)]

View File

@@ -95,7 +95,7 @@ where
{
/// Calls the `async` function with a reference to the input type as its argument.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn dispatch(&self, input: I) {
@@ -104,7 +104,7 @@ where
/// The set of all submissions to this multi-action.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn submissions(&self) -> ReadSignal<Vec<Submission<I, O>>> {
@@ -119,7 +119,7 @@ where
/// How many times an action has successfully resolved.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn version(&self) -> RwSignal<usize> {
@@ -129,7 +129,7 @@ where
/// Associates the URL of the given server function with this action.
/// This enables integration with the `MultiActionForm` component in `leptos_router`.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn using_server_fn<T: ServerFn>(self) -> Self {
@@ -212,7 +212,7 @@ where
{
/// Calls the `async` function with a reference to the input type as its argument.
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn dispatch(&self, input: I) {
@@ -304,7 +304,7 @@ where
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn create_multi_action<I, O, F, Fu>(
@@ -351,7 +351,7 @@ where
/// # });
/// ```
#[cfg_attr(
any(debug_assertions, features = "ssr"),
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn create_server_multi_action<S>(

View File

@@ -1,20 +1,20 @@
use cfg_if::cfg_if;
use leptos::*;
#[cfg(feature = "ssr")]
#[cfg(all(feature = "ssr", not(feature = "csr")))]
use std::{cell::RefCell, rc::Rc};
/// Contains the current metadata for the document's `<body>`.
#[derive(Clone, Default)]
pub struct BodyContext {
#[cfg(feature = "ssr")]
#[cfg(all(feature = "ssr", not(feature = "csr")))]
class: Rc<RefCell<Option<TextProp>>>,
#[cfg(feature = "ssr")]
#[cfg(all(feature = "ssr", not(feature = "csr")))]
attributes: Rc<RefCell<Option<MaybeSignal<AdditionalAttributes>>>>,
}
impl BodyContext {
/// Converts the `<body>` metadata into an HTML string.
#[cfg(any(feature = "ssr", doc))]
#[cfg(all(any(feature = "ssr", doc), not(feature = "csr")))]
pub fn as_string(&self) -> Option<String> {
let class = self.class.borrow().as_ref().map(|val| {
format!(

View File

@@ -1,24 +1,24 @@
use cfg_if::cfg_if;
use leptos::*;
#[cfg(feature = "ssr")]
#[cfg(all(feature = "ssr", not(feature = "csr")))]
use std::{cell::RefCell, rc::Rc};
/// Contains the current metadata for the document's `<html>`.
#[derive(Clone, Default)]
pub struct HtmlContext {
#[cfg(feature = "ssr")]
#[cfg(all(feature = "ssr", not(feature = "csr")))]
lang: Rc<RefCell<Option<TextProp>>>,
#[cfg(feature = "ssr")]
#[cfg(all(feature = "ssr", not(feature = "csr")))]
dir: Rc<RefCell<Option<TextProp>>>,
#[cfg(feature = "ssr")]
#[cfg(all(feature = "ssr", not(feature = "csr")))]
class: Rc<RefCell<Option<TextProp>>>,
#[cfg(feature = "ssr")]
#[cfg(all(feature = "ssr", not(feature = "csr")))]
attributes: Rc<RefCell<Option<MaybeSignal<AdditionalAttributes>>>>,
}
impl HtmlContext {
/// Converts the `<html>` metadata into an HTML string.
#[cfg(any(feature = "ssr", doc))]
#[cfg(all(any(feature = "ssr", doc), not(feature = "csr")))]
pub fn as_string(&self) -> Option<String> {
let lang = self.lang.borrow().as_ref().map(|val| {
format!(
@@ -151,7 +151,7 @@ pub fn Html(
});
}
}
} else if #[cfg(feature = "ssr")] {
} else if #[cfg(all(feature = "ssr", not(feature = "csr")))] {
let meta = crate::use_head(cx);
*meta.html.lang.borrow_mut() = lang;
*meta.html.dir.borrow_mut() = dir;

View File

@@ -115,7 +115,7 @@ impl std::fmt::Debug for MetaTagsContext {
impl MetaTagsContext {
/// Converts metadata tags into an HTML string.
#[cfg(any(feature = "ssr", docs))]
#[cfg(all(any(feature = "ssr", doc), not(feature = "csr")))]
pub fn as_string(&self) -> String {
self.els
.borrow()
@@ -231,7 +231,7 @@ impl MetaContext {
Default::default()
}
#[cfg(feature = "ssr")]
#[cfg(all(any(doc, feature = "ssr"), not(feature = "csr")))]
/// Converts the existing metadata tags into HTML that can be injected into the document head.
///
/// This should be called *after* the apps component tree has been rendered into HTML, so that
@@ -284,7 +284,7 @@ impl MetaContext {
/// Extracts the metadata that should be used to close the `<head>` tag
/// and open the `<body>` tag. This is a helper function used in implementing
/// server-side HTML rendering across crates.
#[cfg(feature = "ssr")]
#[cfg(all(any(doc, feature = "ssr"), not(feature = "csr")))]
pub fn generate_head_metadata(cx: Scope) -> String {
let (head, body) = generate_head_metadata_separated(cx);
format!("{head}</head><{body}>")
@@ -293,7 +293,7 @@ pub fn generate_head_metadata(cx: Scope) -> String {
/// Extracts the metadata that should be inserted at the beginning of the `<head>` tag
/// and on the opening `<body>` tag. This is a helper function used in implementing
/// server-side HTML rendering across crates.
#[cfg(feature = "ssr")]
#[cfg(all(any(doc, feature = "ssr"), not(feature = "csr")))]
pub fn generate_head_metadata_separated(cx: Scope) -> (String, String) {
let meta = use_context::<MetaContext>(cx);
let head = meta

View File

@@ -36,9 +36,8 @@ impl TryFrom<&str> for Url {
type Error = String;
fn try_from(url: &str) -> Result<Self, Self::Error> {
let fake_host = String::from("http://leptos");
let url =
web_sys::Url::new_with_base(url, &fake_host).map_js_error()?;
let fake_host = "http://leptos";
let url = web_sys::Url::new_with_base(url, fake_host).map_js_error()?;
Ok(Self {
origin: url.origin(),
pathname: url.pathname(),

View File

@@ -56,23 +56,23 @@
//! // our root route: the contact list is always shown
//! <Route
//! path=""
//! view=move |cx| view! { cx, <ContactList/> }
//! view=ContactList
//! >
//! // users like /gbj or /bob
//! <Route
//! path=":id"
//! view=move |cx| view! { cx, <Contact/> }
//! view=Contact
//! />
//! // a fallback if the /:id segment is missing from the URL
//! <Route
//! path=""
//! view=move |_| view! { cx, <p class="contact">"Select a contact."</p> }
//! view=move |_| view! { cx, <p class="contact">"Select a contact."</p> }
//! />
//! </Route>
//! // LR will automatically use this for /about, not the /:id match above
//! <Route
//! path="about"
//! view=move |cx| view! { cx, <About/> }
//! view=About
//! />
//! </Routes>
//! </main>

View File

@@ -101,5 +101,5 @@ pub fn expand_optionals(pattern: &str) -> Vec<Cow<str>> {
}
}
const OPTIONAL: &str = r#"(/?:[^/]+)\?"#;
const OPTIONAL_2: &str = r#"^(/:[^/]+)\?"#;
const OPTIONAL: &str = r"(/?:[^/]+)\?";
const OPTIONAL_2: &str = r"^(/:[^/]+)\?";