mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 19:32:37 -05:00
Compare commits
1 Commits
csr-hydrat
...
1403
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9713422682 |
@@ -70,25 +70,6 @@ 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).
|
||||
|
||||
@@ -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 shouldn’t 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?
|
||||
> You shouldn’t 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?
|
||||
|
||||
Let’s start with a simple `<App/>` component using the router:
|
||||
|
||||
@@ -87,17 +87,15 @@ The `view` is a function that takes a `Scope` and returns a view.
|
||||
|
||||
```rust
|
||||
<Routes>
|
||||
<Route path="/" view=Home/>
|
||||
<Route path="/users" view=Users/>
|
||||
<Route path="/users/:id" view=UserProfile/>
|
||||
<Route path="/*any" view=NotFound/>
|
||||
<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/> }/>
|
||||
</Routes>
|
||||
```
|
||||
|
||||
> `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/> }`.
|
||||
> The router scores each route to see how good a match it is, so you can define your routes in any order.
|
||||
|
||||
Now if you navigate to `/` or to `/users` you’ll get the home page or the `<Users/>`. If you go to `/users/3` or `/blahblah` you’ll 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?
|
||||
|
||||
@@ -4,10 +4,10 @@ We just defined the following set of routes:
|
||||
|
||||
```rust
|
||||
<Routes>
|
||||
<Route path="/" view=Home
|
||||
<Route path="/users" view=Users
|
||||
<Route path="/users/:id" view=UserProfile
|
||||
<Route path="/*any" view=NotFound
|
||||
<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 /> }/>
|
||||
</Routes>
|
||||
```
|
||||
|
||||
@@ -17,11 +17,11 @@ Well... you can!
|
||||
|
||||
```rust
|
||||
<Routes>
|
||||
<Route path="/" view=Home
|
||||
<Route path="/users" view=Users
|
||||
<Route path=":id" view=UserProfile
|
||||
<Route path="/" view=|cx| view! { cx, <Home /> }/>
|
||||
<Route path="/users" view=|cx| view! { cx, <Users /> }>
|
||||
<Route path=":id" view=|cx| view! { cx, <UserProfile /> }/>
|
||||
</Route>
|
||||
<Route path="/*any" view=NotFound
|
||||
<Route path="/*any" view=|cx| view! { cx, <NotFound /> }/>
|
||||
</Routes>
|
||||
```
|
||||
|
||||
@@ -39,8 +39,8 @@ Let’s look back at our practical example.
|
||||
|
||||
```rust
|
||||
<Routes>
|
||||
<Route path="/users" view=Users
|
||||
<Route path="/users/:id" view=UserProfile
|
||||
<Route path="/users" view=|cx| view! { cx, <Users /> }/>
|
||||
<Route path="/users/:id" view=|cx| view! { cx, <UserProfile /> }/>
|
||||
</Routes>
|
||||
```
|
||||
|
||||
@@ -53,8 +53,8 @@ Let’s say I use nested routes instead:
|
||||
|
||||
```rust
|
||||
<Routes>
|
||||
<Route path="/users" view=Users
|
||||
<Route path=":id" view=UserProfile
|
||||
<Route path="/users" view=|cx| view! { cx, <Users /> }>
|
||||
<Route path=":id" view=|cx| view! { cx, <UserProfile /> }/>
|
||||
</Route>
|
||||
</Routes>
|
||||
```
|
||||
@@ -68,9 +68,9 @@ I actually need to add a fallback route
|
||||
|
||||
```rust
|
||||
<Routes>
|
||||
<Route path="/users" view=Users
|
||||
<Route path=":id" view=UserProfile
|
||||
<Route path="" view=NoUser
|
||||
<Route path="/users" view=|cx| view! { cx, <Users /> }>
|
||||
<Route path=":id" view=|cx| view! { cx, <UserProfile /> }/>
|
||||
<Route path="" view=|cx| view! { cx, <NoUser /> }/>
|
||||
</Route>
|
||||
</Routes>
|
||||
```
|
||||
@@ -94,8 +94,8 @@ You can easily define this with nested routes
|
||||
|
||||
```rust
|
||||
<Routes>
|
||||
<Route path="/contacts" view=ContactList
|
||||
<Route path=":id" view=ContactInfo
|
||||
<Route path="/contacts" view=|cx| view! { cx, <ContactList/> }>
|
||||
<Route path=":id" view=|cx| view! { cx, <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 contact’s address,
|
||||
|
||||
```rust
|
||||
<Routes>
|
||||
<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 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>
|
||||
<Route path="" view=|cx| view! { cx,
|
||||
<p>"Select a contact to view more info."</p>
|
||||
@@ -201,9 +201,12 @@ fn App(cx: Scope) -> impl IntoView {
|
||||
// /contacts has nested routes
|
||||
<Route
|
||||
path="/contacts"
|
||||
view=ContactList
|
||||
view=|cx| view! { cx, <ContactList/> }
|
||||
>
|
||||
// if no id specified, fall back
|
||||
<Route path=":id" view=ContactInfo
|
||||
<Route path=":id" view=|cx| view! { cx,
|
||||
<ContactInfo/>
|
||||
}>
|
||||
<Route path="" view=|cx| view! { cx,
|
||||
<div class="tab">
|
||||
"(Contact Info)"
|
||||
|
||||
@@ -36,12 +36,6 @@ 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 you’re 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`.
|
||||
|
||||
@@ -114,9 +108,12 @@ fn App(cx: Scope) -> impl IntoView {
|
||||
// /contacts has nested routes
|
||||
<Route
|
||||
path="/contacts"
|
||||
view=ContactList
|
||||
view=|cx| view! { cx, <ContactList/> }
|
||||
>
|
||||
// if no id specified, fall back
|
||||
<Route path=":id" view=ContactInfo
|
||||
<Route path=":id" view=|cx| view! { cx,
|
||||
<ContactInfo/>
|
||||
}>
|
||||
<Route path="" view=|cx| view! { cx,
|
||||
<div class="tab">
|
||||
"(Contact Info)"
|
||||
|
||||
@@ -52,9 +52,12 @@ fn App(cx: Scope) -> impl IntoView {
|
||||
// /contacts has nested routes
|
||||
<Route
|
||||
path="/contacts"
|
||||
view=ContactList
|
||||
view=|cx| view! { cx, <ContactList/> }
|
||||
>
|
||||
// if no id specified, fall back
|
||||
<Route path=":id" view=ContactInfo
|
||||
<Route path=":id" view=|cx| view! { cx,
|
||||
<ContactInfo/>
|
||||
}>
|
||||
<Route path="" view=|cx| view! { cx,
|
||||
<div class="tab">
|
||||
"(Contact Info)"
|
||||
|
||||
@@ -80,7 +80,7 @@ fn App(cx: Scope) -> impl IntoView {
|
||||
<h1><code>"<Form/>"</code></h1>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=FormExample
|
||||
<Route path="" view=|cx| view! { cx, <FormExample/> }/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -64,7 +64,7 @@ If you’re 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 there’s 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 there’s 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 user’s device.
|
||||
- _Cons_
|
||||
@@ -79,13 +79,13 @@ Because it offers the best blend of performance characteristics, Leptos defaults
|
||||
```rust
|
||||
<Routes>
|
||||
// We’ll load the home page with out-of-order streaming and <Suspense/>
|
||||
<Route path="" view=HomePage
|
||||
<Route path="" view=|cx| view! { cx, <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=Post
|
||||
view=|cx| view! { cx, <Post/> }
|
||||
ssr=SsrMode::Async
|
||||
/>
|
||||
</Routes>
|
||||
|
||||
@@ -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()}` accesses the value of `count` once, and passes an `i32` into the view,
|
||||
`{count()}` access 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!
|
||||
|
||||
Let’s make one final change. `set_count(3)` is a pretty useless thing for a click handler to do. Let’s replace “set this value to 3” with “increment this value by 1”:
|
||||
|
||||
@@ -115,11 +115,11 @@ Calling it like this will create a list:
|
||||
|
||||
```rust
|
||||
view! { cx,
|
||||
<WrapsChildren>
|
||||
<WrappedChildren>
|
||||
"A"
|
||||
"B"
|
||||
"C"
|
||||
</WrapsChildren>
|
||||
</WrappedChildren>
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -45,75 +45,3 @@ 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
|
||||
'''
|
||||
|
||||
@@ -34,7 +34,7 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
</header>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=ExampleErrors/>
|
||||
<Route path="" view=|cx| view! { cx, <ExampleErrors/> }/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -170,7 +170,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
|
||||
<hr/>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=Todos/> //Route
|
||||
<Route path="" view=|cx| view! { cx, <Todos/> }/> //Route
|
||||
<Route path="signup" view=move |cx| view! {
|
||||
cx,
|
||||
<Signup action=signup/>
|
||||
|
||||
@@ -104,7 +104,7 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
|
||||
</header>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=Todos/>
|
||||
<Route path="" view=|cx| view! { cx, <Todos/> }/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -104,7 +104,10 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
|
||||
</header>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=Todos/> //Route
|
||||
<Route path="" view=|cx| view! {
|
||||
cx,
|
||||
<Todos/>
|
||||
}/> //Route
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -152,7 +152,6 @@ 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::*};
|
||||
}
|
||||
|
||||
@@ -76,10 +76,7 @@ where
|
||||
let current_id = current_id;
|
||||
|
||||
let children = Rc::new(orig_children(cx).into_view(cx));
|
||||
#[cfg(all(
|
||||
feature = "ssr",
|
||||
not(any(feature = "csr", feature = "hydrate"))
|
||||
))]
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
let orig_children = Rc::clone(&orig_children);
|
||||
move || {
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
@@ -111,7 +108,6 @@ where
|
||||
else {
|
||||
HydrationCtx::continue_from(current_id);
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
cx.register_suspense(
|
||||
context,
|
||||
¤t_id.to_string(),
|
||||
|
||||
@@ -52,12 +52,12 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
<Outlet/>
|
||||
}
|
||||
>
|
||||
<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 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>
|
||||
// in-order
|
||||
<Route
|
||||
@@ -69,12 +69,12 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
<Outlet/>
|
||||
}
|
||||
>
|
||||
<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 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>
|
||||
// async
|
||||
<Route
|
||||
@@ -86,12 +86,12 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
<Outlet/>
|
||||
}
|
||||
>
|
||||
<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 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>
|
||||
</Routes>
|
||||
</main>
|
||||
|
||||
@@ -21,9 +21,7 @@ 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;
|
||||
|
||||
@@ -234,7 +234,6 @@ impl View {
|
||||
self.into_stream_chunks_helper(cx, &mut chunks, false);
|
||||
chunks
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
fn into_stream_chunks_helper(
|
||||
self,
|
||||
|
||||
@@ -351,10 +351,7 @@ fn root_element_to_tokens_ssr(
|
||||
Ident::new("Custom", Span::call_site())
|
||||
} else {
|
||||
let camel_cased = camel_case_tag_name(
|
||||
tag_name
|
||||
.trim_start_matches("svg::")
|
||||
.trim_start_matches("math::")
|
||||
.trim_end_matches('_'),
|
||||
&tag_name.replace("svg::", "").replace("math::", ""),
|
||||
);
|
||||
Ident::new(&camel_cased, Span::call_site())
|
||||
};
|
||||
@@ -520,17 +517,6 @@ 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)
|
||||
@@ -1855,7 +1841,11 @@ 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" { "_" } else { "" };
|
||||
let underscore = if tag_name == "option" || tag_name == "use" {
|
||||
"_"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
first
|
||||
.map(|f| f.to_ascii_uppercase())
|
||||
.into_iter()
|
||||
|
||||
@@ -80,7 +80,6 @@ mod context;
|
||||
#[macro_use]
|
||||
mod diagnostics;
|
||||
mod effect;
|
||||
#[cfg(not(feature = "csr"))]
|
||||
mod hydration;
|
||||
mod memo;
|
||||
mod node;
|
||||
@@ -102,7 +101,6 @@ 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::*;
|
||||
|
||||
@@ -378,7 +378,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(feature = "hydrate", not(feature = "csr"))))]
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
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(all(feature = "hydrate", not(feature = "csr")))]
|
||||
#[cfg(feature = "hydrate")]
|
||||
fn load_resource<S, T>(cx: Scope, id: ResourceId, r: Rc<ResourceState<S, T>>)
|
||||
where
|
||||
S: PartialEq + Clone + 'static,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#![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,
|
||||
@@ -45,7 +44,6 @@ 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>>>>,
|
||||
@@ -352,12 +350,9 @@ impl Runtime {
|
||||
|
||||
impl Debug for Runtime {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut s = f.debug_struct("Runtime");
|
||||
|
||||
#[cfg(not(feature = "csr"))]
|
||||
s.field("shared_context", &self.shared_context);
|
||||
|
||||
s.field("observer", &self.observer)
|
||||
f.debug_struct("Runtime")
|
||||
.field("shared_context", &self.shared_context)
|
||||
.field("observer", &self.observer)
|
||||
.field("scopes", &self.scopes)
|
||||
.field("scope_parents", &self.scope_parents)
|
||||
.field("scope_children", &self.scope_children)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{
|
||||
console_warn,
|
||||
hydration::FragmentData,
|
||||
node::NodeId,
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
PinnedFuture, ResourceId, StoredValueId,
|
||||
suspense::StreamChunk,
|
||||
PinnedFuture, ResourceId, StoredValueId, SuspenseContext,
|
||||
};
|
||||
#[cfg(not(feature = "csr"))]
|
||||
use crate::{hydration::FragmentData, suspense::StreamChunk, SuspenseContext};
|
||||
use futures::stream::FuturesUnordered;
|
||||
#[cfg(not(feature = "csr"))]
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt,
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
#[must_use = "Scope will leak memory if the disposer function is never called"]
|
||||
@@ -458,7 +459,6 @@ 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, feature = "ssr"),
|
||||
instrument(level = "trace", skip_all,)
|
||||
@@ -516,7 +516,6 @@ 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, feature = "ssr"),
|
||||
instrument(level = "trace", skip_all,)
|
||||
@@ -530,7 +529,6 @@ impl Scope {
|
||||
}
|
||||
|
||||
/// A future that will resolve when all blocking fragments are ready.
|
||||
#[cfg(not(feature = "csr"))]
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
instrument(level = "trace", skip_all,)
|
||||
@@ -558,7 +556,6 @@ 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, feature = "ssr"),
|
||||
instrument(level = "trace", skip_all,)
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
#[cfg(all(feature = "ssr", not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
/// Contains the current metadata for the document's `<body>`.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct BodyContext {
|
||||
#[cfg(all(feature = "ssr", not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
class: Rc<RefCell<Option<TextProp>>>,
|
||||
#[cfg(all(feature = "ssr", not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
attributes: Rc<RefCell<Option<MaybeSignal<AdditionalAttributes>>>>,
|
||||
}
|
||||
|
||||
impl BodyContext {
|
||||
/// Converts the `<body>` metadata into an HTML string.
|
||||
#[cfg(all(any(feature = "ssr", doc), not(feature = "csr")))]
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
pub fn as_string(&self) -> Option<String> {
|
||||
let class = self.class.borrow().as_ref().map(|val| {
|
||||
format!(
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
#[cfg(all(feature = "ssr", not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
/// Contains the current metadata for the document's `<html>`.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct HtmlContext {
|
||||
#[cfg(all(feature = "ssr", not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
lang: Rc<RefCell<Option<TextProp>>>,
|
||||
#[cfg(all(feature = "ssr", not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
dir: Rc<RefCell<Option<TextProp>>>,
|
||||
#[cfg(all(feature = "ssr", not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
class: Rc<RefCell<Option<TextProp>>>,
|
||||
#[cfg(all(feature = "ssr", not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
attributes: Rc<RefCell<Option<MaybeSignal<AdditionalAttributes>>>>,
|
||||
}
|
||||
|
||||
impl HtmlContext {
|
||||
/// Converts the `<html>` metadata into an HTML string.
|
||||
#[cfg(all(any(feature = "ssr", doc), not(feature = "csr")))]
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
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(all(feature = "ssr", not(feature = "csr")))] {
|
||||
} else if #[cfg(feature = "ssr")] {
|
||||
let meta = crate::use_head(cx);
|
||||
*meta.html.lang.borrow_mut() = lang;
|
||||
*meta.html.dir.borrow_mut() = dir;
|
||||
|
||||
@@ -115,7 +115,7 @@ impl std::fmt::Debug for MetaTagsContext {
|
||||
|
||||
impl MetaTagsContext {
|
||||
/// Converts metadata tags into an HTML string.
|
||||
#[cfg(all(any(feature = "ssr", doc), not(feature = "csr")))]
|
||||
#[cfg(any(feature = "ssr", docs))]
|
||||
pub fn as_string(&self) -> String {
|
||||
self.els
|
||||
.borrow()
|
||||
@@ -231,7 +231,7 @@ impl MetaContext {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
#[cfg(all(any(doc, feature = "ssr"), not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
/// Converts the existing metadata tags into HTML that can be injected into the document head.
|
||||
///
|
||||
/// This should be called *after* the app’s 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(all(any(doc, feature = "ssr"), not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
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(all(any(doc, feature = "ssr"), not(feature = "csr")))]
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn generate_head_metadata_separated(cx: Scope) -> (String, String) {
|
||||
let meta = use_context::<MetaContext>(cx);
|
||||
let head = meta
|
||||
|
||||
@@ -56,23 +56,23 @@
|
||||
//! // our root route: the contact list is always shown
|
||||
//! <Route
|
||||
//! path=""
|
||||
//! view=ContactList
|
||||
//! view=move |cx| view! { cx, <ContactList/> }
|
||||
//! >
|
||||
//! // users like /gbj or /bob
|
||||
//! <Route
|
||||
//! path=":id"
|
||||
//! view=Contact
|
||||
//! view=move |cx| view! { cx, <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=About
|
||||
//! view=move |cx| view! { cx, <About/> }
|
||||
//! />
|
||||
//! </Routes>
|
||||
//! </main>
|
||||
|
||||
Reference in New Issue
Block a user